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 currentProject = null;
|
||||||
let currentHostId = null; // Track current host for restart
|
let currentHostId = null; // Track current host for restart
|
||||||
let isInitialized = false;
|
let isInitialized = false;
|
||||||
|
let isRestarting = false; // Flag to prevent session_ended during interrupt restart
|
||||||
let currentPermissionMode = 'default';
|
let currentPermissionMode = 'default';
|
||||||
let savedPermissionMode = 'default'; // Store mode before plan mode switch
|
let savedPermissionMode = 'default'; // Store mode before plan mode switch
|
||||||
let inPlanMode = false; // Track if we're in plan mode (to require approval for ExitPlanMode)
|
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) {
|
if (claudeProcess) {
|
||||||
console.log(`[${sessionId}] Killing existing Claude process`);
|
console.log(`[${sessionId}] Killing existing Claude process`);
|
||||||
claudeProcess.kill();
|
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 });
|
sessions.set(sessionId, { process: claudeProcess, project: projectPath, host: host, hostId: hostId, user: wsUser });
|
||||||
|
|
||||||
|
// Only send session_started if not a silent restart (e.g., after interrupt)
|
||||||
|
if (!silent) {
|
||||||
sendToClient('session_started', {
|
sendToClient('session_started', {
|
||||||
sessionId,
|
sessionId,
|
||||||
project: projectPath
|
project: projectPath
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Send control protocol initialization after process starts
|
// Send control protocol initialization after process starts
|
||||||
const initializeControlProtocol = () => {
|
const initializeControlProtocol = () => {
|
||||||
@@ -937,8 +941,11 @@ wss.on('connection', async (ws, req) => {
|
|||||||
|
|
||||||
claudeProcess.on('close', (code) => {
|
claudeProcess.on('close', (code) => {
|
||||||
console.log(`[${sessionId}] Claude process exited with code ${code}`);
|
console.log(`[${sessionId}] Claude process exited with code ${code}`);
|
||||||
|
// Don't send session_ended if we're restarting after interrupt
|
||||||
|
if (!isRestarting) {
|
||||||
sendToClient('session_ended', { code });
|
sendToClient('session_ended', { code });
|
||||||
sessions.delete(sessionId);
|
sessions.delete(sessionId);
|
||||||
|
}
|
||||||
claudeProcess = null;
|
claudeProcess = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -984,34 +991,40 @@ wss.on('connection', async (ws, req) => {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'stop_generation':
|
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) {
|
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
|
// Save current state for restart
|
||||||
const restartProject = currentProject;
|
const restartProject = currentProject;
|
||||||
const restartHost = currentHostId;
|
const restartHost = currentHostId;
|
||||||
const restartPermissionMode = currentPermissionMode;
|
const restartPermissionMode = currentPermissionMode;
|
||||||
|
|
||||||
// Kill the process
|
// Notify frontend (no message - silent interrupt)
|
||||||
claudeProcess.kill('SIGKILL');
|
|
||||||
claudeProcess = null;
|
|
||||||
isInitialized = false;
|
|
||||||
|
|
||||||
// Notify frontend
|
|
||||||
sendToClient('generation_stopped', {
|
sendToClient('generation_stopped', {
|
||||||
message: 'Generation stopped, reconnecting...',
|
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
});
|
});
|
||||||
|
|
||||||
// Restart after a short delay
|
// Listen for exit and restart
|
||||||
setTimeout(() => {
|
claudeProcess.once('exit', (code) => {
|
||||||
console.log(`[${sessionId}] Restarting session with --continue`);
|
console.log(`[${sessionId}] Claude exited with code ${code}, restarting with --continue`);
|
||||||
startClaudeSession(restartProject, true, restartHost);
|
isInitialized = false;
|
||||||
|
|
||||||
// Restore permission mode after initialization
|
// Restart with --continue to resume conversation (silent = no session_started message)
|
||||||
|
setTimeout(() => {
|
||||||
|
startClaudeSession(restartProject, true, restartHost, true); // silent=true
|
||||||
savedPermissionMode = restartPermissionMode;
|
savedPermissionMode = restartPermissionMode;
|
||||||
}, 500);
|
isRestarting = false; // Clear flag after restart
|
||||||
|
}, 200);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send SIGINT (graceful interrupt)
|
||||||
|
claudeProcess.kill('SIGINT');
|
||||||
} else {
|
} else {
|
||||||
sendToClient('generation_stopped', {
|
sendToClient('generation_stopped', {
|
||||||
message: 'No active process',
|
message: 'No active process',
|
||||||
|
|||||||
Reference in New Issue
Block a user