fix: wait for PTY ready state before sending commands
Implement proper ready-state handshake to fix command execution timing. Root Cause: WebSocket connection was opening immediately, but the backend PTY (pseudo-terminal) wasn't ready to receive input yet. Commands sent too early were lost, causing claude --dangerously-skip-permissions to never execute. Broken Flow: 1. WebSocket opens → connectTerminal() resolves immediately 2. Command sent → PTY not ready, command lost 3. Terminal shows cursor but Claude CLI never starts Fixed Flow: 1. WebSocket opens → Wait for 'ready' message from backend 2. Backend sends 'ready' → PTY is now initialized 3. Command sent → PTY receives it successfully 4. Claude CLI starts Changes: - Add 'ready' flag to terminal state (default false) - connectTerminal() now waits for 'ready' message before resolving - Add waitForTerminalReady() helper with 5s timeout - launchCommand() checks ready state before sending - Enhanced error handling and console logging Resolves: "terminal does not show or execute claude cli" Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
|
||||
class TerminalManager {
|
||||
constructor() {
|
||||
this.terminals = new Map(); // terminalId -> { terminal, ws, fitAddon, container, mode }
|
||||
this.terminals = new Map(); // terminalId -> { terminal, ws, fitAddon, container, mode, ready }
|
||||
this.activeTerminalId = null;
|
||||
this.xtermLoaded = false;
|
||||
this.terminalsContainer = null;
|
||||
@@ -218,6 +218,7 @@ class TerminalManager {
|
||||
await this.initializeXTerm(terminalId);
|
||||
|
||||
// NOW connect WebSocket (terminal entry exists in map)
|
||||
// This waits for the 'ready' message from backend
|
||||
await this.connectTerminal(terminalId);
|
||||
|
||||
// Switch to new terminal
|
||||
@@ -227,7 +228,7 @@ class TerminalManager {
|
||||
if (selectedTerminalType === 'claude-cli') {
|
||||
// Launch Claude CLI with skip permissions flag
|
||||
// Note: Keep mode as 'mixed' since we're not attaching to a session
|
||||
// WebSocket is already connected (await connectTerminal), so no delay needed
|
||||
// connectTerminal now waits for 'ready' message, so PTY is definitely ready
|
||||
await this.launchCommand(terminalId, 'claude --dangerously-skip-permissions\n');
|
||||
|
||||
if (!silent) {
|
||||
@@ -627,11 +628,21 @@ class TerminalManager {
|
||||
|
||||
/**
|
||||
* Launch a command in the terminal
|
||||
* Waits for terminal to be ready before sending command
|
||||
*/
|
||||
async launchCommand(terminalId, command) {
|
||||
// Wait for terminal to be ready (max 5 seconds)
|
||||
const ready = await this.waitForTerminalReady(terminalId, 5000);
|
||||
|
||||
if (!ready) {
|
||||
console.error('[TerminalManager] Terminal not ready for command launch (timeout)');
|
||||
showToast('Terminal not ready. Please try again.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const terminal = this.terminals.get(terminalId);
|
||||
if (!terminal || !terminal.ws || terminal.ws.readyState !== WebSocket.OPEN) {
|
||||
console.error('[TerminalManager] Terminal not ready for command launch');
|
||||
console.error('[TerminalManager] Terminal not ready for command launch (WebSocket not connected)');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -644,6 +655,36 @@ class TerminalManager {
|
||||
console.log(`[TerminalManager] Launched command in terminal ${terminalId}: ${command.trim()}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for terminal to be ready
|
||||
*/
|
||||
async waitForTerminalReady(terminalId, timeout = 5000) {
|
||||
const startTime = Date.now();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const checkReady = () => {
|
||||
const terminal = this.terminals.get(terminalId);
|
||||
|
||||
if (terminal && terminal.ready) {
|
||||
console.log(`[TerminalManager] Terminal ${terminalId} is ready`);
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Date.now() - startTime > timeout) {
|
||||
console.error(`[TerminalManager] Terminal ${terminalId} ready timeout`);
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check again in 100ms
|
||||
setTimeout(checkReady, 100);
|
||||
};
|
||||
|
||||
checkReady();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract project name from session metadata
|
||||
*/
|
||||
@@ -766,6 +807,7 @@ class TerminalManager {
|
||||
|
||||
/**
|
||||
* Connect terminal WebSocket
|
||||
* Waits for 'ready' message from backend before resolving
|
||||
*/
|
||||
async connectTerminal(terminalId) {
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
@@ -776,7 +818,7 @@ class TerminalManager {
|
||||
const ws = new WebSocket(wsUrl);
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log(`[TerminalManager] Connected to terminal ${terminalId}`);
|
||||
console.log(`[TerminalManager] WebSocket connected for terminal ${terminalId}`);
|
||||
|
||||
// Store WebSocket in terminal entry
|
||||
// NOTE: This assumes initializeXTerm() has already been called
|
||||
@@ -784,18 +826,28 @@ class TerminalManager {
|
||||
const terminal = this.terminals.get(terminalId);
|
||||
if (terminal) {
|
||||
terminal.ws = ws;
|
||||
terminal.ready = false; // Will be set to true when 'ready' message received
|
||||
} else {
|
||||
console.error(`[TerminalManager] CRITICAL: Terminal ${terminalId} not found in map! WebSocket connection will be lost.`);
|
||||
reject(new Error(`Terminal ${terminalId} not initialized`));
|
||||
ws.close();
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const message = JSON.parse(event.data);
|
||||
this.handleTerminalMessage(terminalId, message);
|
||||
|
||||
// If this is the ready message, resolve the promise
|
||||
if (message.type === 'ready') {
|
||||
console.log(`[TerminalManager] Terminal ${terminalId} is ready (PTY initialized)`);
|
||||
const terminal = this.terminals.get(terminalId);
|
||||
if (terminal) {
|
||||
terminal.ready = true;
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
@@ -926,7 +978,8 @@ class TerminalManager {
|
||||
fitAddon,
|
||||
ws: null, // Will be set by connectTerminal()
|
||||
container,
|
||||
mode: 'mixed'
|
||||
mode: 'mixed',
|
||||
ready: false // Will be set to true when 'ready' message received
|
||||
});
|
||||
|
||||
return terminal;
|
||||
|
||||
Reference in New Issue
Block a user