feat: Add graceful interrupt with silent restart
- ESC now sends SIGINT to interrupt generation gracefully - Automatic silent restart with --continue after interrupt - Added isRestarting flag to prevent session_ended during restart - No UI messages during interrupt/restart cycle - Removed SIGKILL fallback - SIGINT is sufficient 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -597,6 +597,7 @@ wss.on('connection', async (ws, req) => {
|
||||
let currentProject = null;
|
||||
let currentHostId = null; // Track current host for restart
|
||||
let isInitialized = false;
|
||||
let isRestarting = false; // Flag to prevent session_ended during interrupt restart
|
||||
let currentPermissionMode = 'default';
|
||||
let savedPermissionMode = 'default'; // Store mode before plan mode switch
|
||||
let inPlanMode = false; // Track if we're in plan mode (to require approval for ExitPlanMode)
|
||||
@@ -646,7 +647,7 @@ wss.on('connection', async (ws, req) => {
|
||||
}
|
||||
};
|
||||
|
||||
const startClaudeSession = (projectPath, resume = true, hostId = null) => {
|
||||
const startClaudeSession = (projectPath, resume = true, hostId = null, silent = false) => {
|
||||
if (claudeProcess) {
|
||||
console.log(`[${sessionId}] Killing existing Claude process`);
|
||||
claudeProcess.kill();
|
||||
@@ -710,10 +711,13 @@ wss.on('connection', async (ws, req) => {
|
||||
|
||||
sessions.set(sessionId, { process: claudeProcess, project: projectPath, host: host, hostId: hostId, user: wsUser });
|
||||
|
||||
sendToClient('session_started', {
|
||||
sessionId,
|
||||
project: projectPath
|
||||
});
|
||||
// Only send session_started if not a silent restart (e.g., after interrupt)
|
||||
if (!silent) {
|
||||
sendToClient('session_started', {
|
||||
sessionId,
|
||||
project: projectPath
|
||||
});
|
||||
}
|
||||
|
||||
// Send control protocol initialization after process starts
|
||||
const initializeControlProtocol = () => {
|
||||
@@ -937,8 +941,11 @@ wss.on('connection', async (ws, req) => {
|
||||
|
||||
claudeProcess.on('close', (code) => {
|
||||
console.log(`[${sessionId}] Claude process exited with code ${code}`);
|
||||
sendToClient('session_ended', { code });
|
||||
sessions.delete(sessionId);
|
||||
// Don't send session_ended if we're restarting after interrupt
|
||||
if (!isRestarting) {
|
||||
sendToClient('session_ended', { code });
|
||||
sessions.delete(sessionId);
|
||||
}
|
||||
claudeProcess = null;
|
||||
});
|
||||
|
||||
@@ -984,34 +991,40 @@ wss.on('connection', async (ws, req) => {
|
||||
break;
|
||||
|
||||
case 'stop_generation':
|
||||
// Kill the process and restart with --continue to resume session
|
||||
// Interrupt Claude and restart with --continue
|
||||
// In JSON mode, SIGINT causes Claude to exit (unlike TUI mode where it just stops output)
|
||||
// So we need to restart the session automatically
|
||||
if (claudeProcess) {
|
||||
console.log(`[${sessionId}] Stop generation: killing process and restarting`);
|
||||
console.log(`[${sessionId}] Stop generation: sending SIGINT and will restart`);
|
||||
|
||||
// Set flag to prevent session_ended from being sent
|
||||
isRestarting = true;
|
||||
|
||||
// Save current state for restart
|
||||
const restartProject = currentProject;
|
||||
const restartHost = currentHostId;
|
||||
const restartPermissionMode = currentPermissionMode;
|
||||
|
||||
// Kill the process
|
||||
claudeProcess.kill('SIGKILL');
|
||||
claudeProcess = null;
|
||||
isInitialized = false;
|
||||
|
||||
// Notify frontend
|
||||
// Notify frontend (no message - silent interrupt)
|
||||
sendToClient('generation_stopped', {
|
||||
message: 'Generation stopped, reconnecting...',
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
// Restart after a short delay
|
||||
setTimeout(() => {
|
||||
console.log(`[${sessionId}] Restarting session with --continue`);
|
||||
startClaudeSession(restartProject, true, restartHost);
|
||||
// Listen for exit and restart
|
||||
claudeProcess.once('exit', (code) => {
|
||||
console.log(`[${sessionId}] Claude exited with code ${code}, restarting with --continue`);
|
||||
isInitialized = false;
|
||||
|
||||
// Restore permission mode after initialization
|
||||
savedPermissionMode = restartPermissionMode;
|
||||
}, 500);
|
||||
// Restart with --continue to resume conversation (silent = no session_started message)
|
||||
setTimeout(() => {
|
||||
startClaudeSession(restartProject, true, restartHost, true); // silent=true
|
||||
savedPermissionMode = restartPermissionMode;
|
||||
isRestarting = false; // Clear flag after restart
|
||||
}, 200);
|
||||
});
|
||||
|
||||
// Send SIGINT (graceful interrupt)
|
||||
claudeProcess.kill('SIGINT');
|
||||
} else {
|
||||
sendToClient('generation_stopped', {
|
||||
message: 'No active process',
|
||||
|
||||
Reference in New Issue
Block a user