mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
fix(respawn): use screen -X stuff for reliable input delivery
When writing to an attached PTY connected to screen, the input doesn't reliably reach Claude CLI. This fix uses `screen -X stuff` command to send input directly to the screen session, bypassing the PTY attachment. Research findings: - Claude CLI uses Ink (React for CLI) for terminal UI - Ink's parseKeypress detects \r as key.return (Enter) - screen -X stuff with $'...' syntax reliably sends literal characters Changes: - Add ScreenManager.sendInput() using screen -X stuff - Modify Session.write() to prefer screen -X stuff when available - Keep \r as Enter key (what Ink expects for key.return) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -355,7 +355,7 @@ export class RespawnController extends EventEmitter {
|
||||
this.stepTimer = setTimeout(() => {
|
||||
const prompt = this.config.kickstartPrompt!;
|
||||
this.log(`Sending kickstart prompt: "${prompt}"`);
|
||||
this.session.write(prompt + '\r'); // \r for PTY Enter key
|
||||
this.session.write(prompt + '\r'); // \r triggers key.return in Ink/Claude CLI
|
||||
this.emit('stepSent', 'kickstart', prompt);
|
||||
this.setState('waiting_kickstart');
|
||||
this.promptDetected = false;
|
||||
@@ -416,7 +416,7 @@ export class RespawnController extends EventEmitter {
|
||||
this.terminalBuffer = ''; // Clear buffer for fresh detection
|
||||
|
||||
this.stepTimer = setTimeout(() => {
|
||||
const input = this.config.updatePrompt + '\r'; // \r for PTY Enter key
|
||||
const input = this.config.updatePrompt + '\r\n'; // CRLF for screen + Claude CLI
|
||||
this.log(`Sending update prompt: "${this.config.updatePrompt}"`);
|
||||
this.session.write(input);
|
||||
this.emit('stepSent', 'update', this.config.updatePrompt);
|
||||
@@ -432,7 +432,7 @@ export class RespawnController extends EventEmitter {
|
||||
|
||||
this.stepTimer = setTimeout(() => {
|
||||
this.log('Sending /clear');
|
||||
this.session.write('/clear\r'); // \r for PTY Enter key
|
||||
this.session.write('/clear\r\n'); // CRLF for screen + Claude CLI
|
||||
this.emit('stepSent', 'clear', '/clear');
|
||||
this.setState('waiting_clear');
|
||||
this.promptDetected = false;
|
||||
@@ -445,7 +445,7 @@ export class RespawnController extends EventEmitter {
|
||||
|
||||
this.stepTimer = setTimeout(() => {
|
||||
this.log('Sending /init');
|
||||
this.session.write('/init\r'); // \r for PTY Enter key
|
||||
this.session.write('/init\r\n'); // CRLF for screen + Claude CLI
|
||||
this.emit('stepSent', 'init', '/init');
|
||||
this.setState('waiting_init');
|
||||
this.promptDetected = false;
|
||||
|
||||
@@ -457,4 +457,26 @@ export class ScreenManager extends EventEmitter {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Send input directly to screen session using screen -X stuff
|
||||
// This bypasses the attached PTY and sends input directly to the screen
|
||||
sendInput(sessionId: string, input: string): boolean {
|
||||
const screen = this.screens.get(sessionId);
|
||||
if (!screen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Use screen -X stuff with $'...' syntax to send literal characters
|
||||
// \r (carriage return) is what Claude CLI expects for Enter
|
||||
execSync(`screen -S ${screen.screenName} -X stuff $'${input.replace(/'/g, "'\\''")}'`, {
|
||||
encoding: 'utf-8',
|
||||
timeout: 5000
|
||||
});
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error('[ScreenManager] Failed to send input:', err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -898,7 +898,18 @@ export class Session extends EventEmitter {
|
||||
}
|
||||
|
||||
// Send input to the PTY (for interactive sessions)
|
||||
// When using screen, prefers screen -X stuff for reliable input delivery
|
||||
write(data: string): void {
|
||||
// Try screen -X stuff first if we have a screen session
|
||||
// This is more reliable for sending Enter key to Claude CLI
|
||||
if (this._screenManager && this._screenSession) {
|
||||
const success = this._screenManager.sendInput(this.id, data);
|
||||
if (success) {
|
||||
return;
|
||||
}
|
||||
// Fall through to PTY write if screen command failed
|
||||
}
|
||||
|
||||
if (this.ptyProcess) {
|
||||
this.ptyProcess.write(data);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user