handle session id passing + better error messages

This commit is contained in:
Peter Steinberger 2025-06-21 18:55:34 +02:00
parent 34fc38d13f
commit d820a4b147
5 changed files with 112 additions and 24 deletions

View file

@ -615,7 +615,7 @@ final class TerminalLauncher {
// For Bun server, use fwd to create sessions
logger.info("Using Bun server session creation via fwd")
let bunPath = findBunExecutable()
let bunCommand = buildBunCommand(bunPath: bunPath, userCommand: command, workingDir: escapedWorkingDir)
let bunCommand = buildBunCommand(bunPath: bunPath, userCommand: command, workingDir: escapedWorkingDir, sessionId: sessionId)
let fullCommand = "cd \"\(escapedWorkingDir)\" && \(bunCommand) && exit"
// Get the preferred terminal or fallback
@ -674,17 +674,18 @@ final class TerminalLauncher {
let bunCommand = buildBunCommand(
bunPath: bunPath,
userCommand: actualCommand,
workingDir: escapedDir
workingDir: escapedDir,
sessionId: sessionId
)
fullCommand = "cd \"\(escapedDir)\" && \(bunCommand) && exit"
} else {
// Fallback if format is different
let bunCommand = buildBunCommand(bunPath: bunPath, userCommand: command, workingDir: escapedDir)
let bunCommand = buildBunCommand(bunPath: bunPath, userCommand: command, workingDir: escapedDir, sessionId: sessionId)
fullCommand = "cd \"\(escapedDir)\" && \(bunCommand) && exit"
}
} else {
// Command is just the user command
let bunCommand = buildBunCommand(bunPath: bunPath, userCommand: command, workingDir: escapedDir)
let bunCommand = buildBunCommand(bunPath: bunPath, userCommand: command, workingDir: escapedDir, sessionId: sessionId)
fullCommand = "cd \"\(escapedDir)\" && \(bunCommand) && exit"
}
} else {
@ -751,9 +752,14 @@ final class TerminalLauncher {
return "echo 'VibeTunnel: Bun executable not found in app bundle'; false"
}
private func buildBunCommand(bunPath: String, userCommand: String, workingDir: String) -> String {
private func buildBunCommand(bunPath: String, userCommand: String, workingDir: String, sessionId: String? = nil) -> String {
// Bun executable has fwd command built-in
logger.info("Using Bun executable for session creation")
return "\"\(bunPath)\" fwd \(userCommand)"
if let sessionId = sessionId {
// Pass the pre-generated session ID to fwd
return "\"\(bunPath)\" fwd --session-id \(sessionId) \(userCommand)"
} else {
return "\"\(bunPath)\" fwd \(userCommand)"
}
}
}

View file

@ -492,6 +492,7 @@ export class VibeTunnelApp extends LitElement {
<file-browser-enhanced
.visible=${this.showFileBrowser}
.mode=${'browse'}
.session=${null}
@browser-cancel=${() => (this.showFileBrowser = false)}
></file-browser-enhanced>
`;

View file

@ -19,16 +19,18 @@ function showUsage() {
console.log('VibeTunnel Forward (fwd.ts)');
console.log('');
console.log('Usage:');
console.log(' npx tsx src/fwd.ts <command> [args...]');
console.log(' npx tsx src/fwd.ts --monitor-only <command> [args...]');
console.log(' npx tsx src/fwd.ts [--session-id <id>] <command> [args...]');
console.log(' npx tsx src/fwd.ts [--session-id <id>] --monitor-only <command> [args...]');
console.log('');
console.log('Options:');
console.log(' --monitor-only Just create session and monitor, no interactive I/O');
console.log(' --session-id <id> Use a pre-generated session ID');
console.log(' --monitor-only Just create session and monitor, no interactive I/O');
console.log('');
console.log('Examples:');
console.log(' npx tsx src/fwd.ts claude --resume');
console.log(' npx tsx src/fwd.ts bash -l');
console.log(' npx tsx src/fwd.ts python3 -i');
console.log(' npx tsx src/fwd.ts --session-id abc123 claude');
console.log(' npx tsx src/fwd.ts --monitor-only long-running-command');
console.log('');
console.log('The command will be spawned in the current working directory');
@ -42,8 +44,17 @@ export async function startVibeTunnelForward(args: string[]) {
process.exit(0);
}
const monitorOnly = args[0] === '--monitor-only';
const command = monitorOnly ? args.slice(1) : args;
// Check for --session-id parameter
let sessionId: string | undefined;
let remainingArgs = args;
if (args[0] === '--session-id' && args.length > 1) {
sessionId = args[1];
remainingArgs = args.slice(2);
}
const monitorOnly = remainingArgs[0] === '--monitor-only';
const command = monitorOnly ? remainingArgs.slice(1) : remainingArgs;
if (command.length === 0) {
console.error('Error: No command specified');
@ -61,11 +72,41 @@ export async function startVibeTunnelForward(args: string[]) {
const ptyManager = new PtyManager(controlPath);
try {
// Handle shell expansion for single commands that aren't common shells
let finalCommand = command;
const isShell =
command.length === 1 &&
(command[0].endsWith('bash') || command[0].endsWith('zsh') || command[0].endsWith('sh'));
// Match Linux implementation - add -i for shells
if (isShell && command.length === 1) {
// For shells, add -i flag to ensure interactive mode
console.log(`Adding -i flag for interactive shell: ${command[0]}`);
finalCommand = [...command, '-i'];
} else if (command.length === 1 && command[0] === 'claude') {
// Special handling for claude - it needs to run in a shell environment
const userShell = process.env.SHELL || '/bin/zsh';
console.log(`Running claude through shell for proper terminal setup: ${userShell}`);
// Run claude through an interactive shell
finalCommand = [userShell, '-i', '-c', 'claude'];
} else {
// All other commands - spawn as-is (like Linux does)
console.log(`Spawning command as-is: ${command.join(' ')}`);
finalCommand = command;
}
// Create the session
const sessionName = `fwd_${command[0]}_${Date.now()}`;
console.log(`Creating session: ${sessionName}`);
if (sessionId) {
console.log(`Using pre-generated session ID: ${sessionId}`);
}
const result = await ptyManager.createSession(command, {
console.log(`Final command to spawn: ${JSON.stringify(finalCommand)}`);
console.log(`Environment TERM: ${process.env.TERM}`);
const result = await ptyManager.createSession(finalCommand, {
sessionId, // Use the pre-generated session ID if provided
sessionName,
workingDir: cwd,
term: process.env.TERM || 'xterm-256color',
@ -149,10 +190,19 @@ export async function startVibeTunnelForward(args: string[]) {
if (!fs.existsSync(controlPath)) {
if (!isWindows) {
const { spawnSync } = require('child_process');
const result = spawnSync('mkfifo', [controlPath], { stdio: 'ignore' });
if (result.status === 0) {
useFifo = true;
try {
const { spawnSync } = require('child_process');
const result = spawnSync('mkfifo', [controlPath], { stdio: 'pipe' });
if (result.status === 0) {
useFifo = true;
console.log(`Created control FIFO at: ${controlPath}`);
} else {
console.warn(
`Failed to create FIFO: ${result.stderr?.toString() || 'Unknown error'}`
);
}
} catch (e) {
console.warn(`Error creating FIFO: ${e}`);
}
}

View file

@ -109,16 +109,46 @@ export class PtyManager {
);
// Create PTY process
const ptyProcess = pty.spawn(command[0], command.slice(1), {
name: term,
cols,
rows,
cwd: workingDir,
env: {
let ptyProcess;
try {
// Log the command we're about to spawn for debugging
console.log(
`[PTY] Spawning command: ${command[0]} with args: ${JSON.stringify(command.slice(1))}`
);
console.log(`[PTY] Working directory: ${workingDir}`);
console.log(`[PTY] Terminal: ${term}, Size: ${cols}x${rows}`);
// Set up environment like Linux implementation
const ptyEnv = {
...process.env,
TERM: term,
},
});
SHELL: command[0], // Set SHELL to the command being run (like Linux does)
};
ptyProcess = pty.spawn(command[0], command.slice(1), {
name: term,
cols,
rows,
cwd: workingDir,
env: ptyEnv,
});
} catch (spawnError: any) {
// Provide better error messages for common issues
let errorMessage = spawnError.message;
if (spawnError.code === 'ENOENT' || errorMessage.includes('ENOENT')) {
errorMessage = `Command not found: '${command[0]}'. Please ensure the command exists and is in your PATH.`;
} else if (spawnError.code === 'EACCES' || errorMessage.includes('EACCES')) {
errorMessage = `Permission denied: '${command[0]}'. The command exists but is not executable.`;
} else if (spawnError.code === 'ENXIO' || errorMessage.includes('ENXIO')) {
errorMessage = `Failed to allocate terminal for '${command[0]}'. This may occur if the command doesn't exist or the system cannot create a pseudo-terminal.`;
} else if (errorMessage.includes('cwd') || errorMessage.includes('working directory')) {
errorMessage = `Working directory does not exist: '${workingDir}'`;
}
console.error(`PTY spawn error for command '${command.join(' ')}':`, spawnError);
throw new PtyError(errorMessage, 'SPAWN_FAILED');
}
// Create session object
const session: PtySession = {

View file

@ -136,6 +136,7 @@ export function createSessionRoutes(config: SessionRoutesConfig): Router {
const sessionName = name || `session_${Date.now()}`;
// Request Mac app to spawn terminal
console.log(`Requesting terminal spawn with command: ${JSON.stringify(command)}`);
const spawnResult = await requestTerminalSpawn({
sessionId,
sessionName,