feat: auto-accept plan mode and question prompts in respawn controller

When Claude enters plan mode or asks a question (AskUserQuestion), output
stops without a completion message. The new autoAcceptPrompts feature
detects this state and sends Enter after a configurable delay (default 8s)
to accept the plan or select the default option, keeping Claude working
autonomously.

Enabled by default. Adds UI checkbox in the respawn config panel.
Safety: only fires once per silence period, requires prior output,
and won't fire during active respawn cycles.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-01-23 01:25:09 +01:00
co-authored by Claude Opus 4.5
parent 619f2a3403
commit 0e8c8b8c60
6 changed files with 354 additions and 2 deletions
+11
View File
@@ -503,6 +503,13 @@ class ClaudemanApp {
}
});
this.eventSource.addEventListener('respawn:autoAcceptSent', (e) => {
const data = JSON.parse(e.data);
if (data.sessionId === this.activeSessionId) {
document.getElementById('respawnStep').textContent = '⏎ Auto-accepted prompt';
}
});
this.eventSource.addEventListener('respawn:detectionUpdate', (e) => {
const data = JSON.parse(e.data);
if (this.respawnStatus[data.sessionId]) {
@@ -1667,6 +1674,7 @@ class ClaudemanApp {
sendClear: document.getElementById('modalRespawnSendClear').checked,
sendInit: document.getElementById('modalRespawnSendInit').checked,
kickstartPrompt: document.getElementById('modalRespawnKickstart').value.trim() || undefined,
autoAcceptPrompts: document.getElementById('modalRespawnAutoAccept').checked,
};
try {
await fetch(`/api/sessions/${this.editingSessionId}/respawn/config`, {
@@ -1689,6 +1697,7 @@ class ClaudemanApp {
document.getElementById('modalRespawnSendClear').checked = c.sendClear ?? true;
document.getElementById('modalRespawnSendInit').checked = c.sendInit ?? true;
document.getElementById('modalRespawnKickstart').value = c.kickstartPrompt || '';
document.getElementById('modalRespawnAutoAccept').checked = c.autoAcceptPrompts ?? true;
// Restore duration if set
if (c.durationMinutes) {
const presetBtn = document.querySelector(`.duration-preset-btn[data-minutes="${c.durationMinutes}"]`);
@@ -1749,6 +1758,7 @@ class ClaudemanApp {
const sendClear = document.getElementById('modalRespawnSendClear').checked;
const sendInit = document.getElementById('modalRespawnSendInit').checked;
const kickstartPrompt = document.getElementById('modalRespawnKickstart').value.trim() || undefined;
const autoAcceptPrompts = document.getElementById('modalRespawnAutoAccept').checked;
const durationMinutes = this.getSelectedDuration();
// Auto-compact settings
@@ -1766,6 +1776,7 @@ class ClaudemanApp {
sendClear,
sendInit,
kickstartPrompt,
autoAcceptPrompts,
},
durationMinutes,
autoCompactEnabled,
+11
View File
@@ -333,6 +333,17 @@
<span class="form-hint">Sent only when /init completes but Claude stays idle</span>
</div>
<div class="form-row">
<label>Auto-Accept Prompts</label>
<div class="checkbox-group-inline">
<label class="checkbox-inline">
<input type="checkbox" id="modalRespawnAutoAccept" checked onchange="app.autoSaveRespawnConfig()">
<span>Accept plan mode &amp; questions</span>
</label>
</div>
<span class="form-hint">Presses Enter to accept plan approvals and default question options when Claude is waiting</span>
</div>
<div class="form-row">
<label>Auto-Compact Context</label>
<div class="auto-clear-row">
+12
View File
@@ -690,6 +690,8 @@ export class WebServer extends EventEmitter {
sendClear: config.sendClear ?? currentConfig?.sendClear ?? true,
sendInit: config.sendInit ?? currentConfig?.sendInit ?? true,
kickstartPrompt: config.kickstartPrompt ?? currentConfig?.kickstartPrompt,
autoAcceptPrompts: config.autoAcceptPrompts ?? currentConfig?.autoAcceptPrompts ?? true,
autoAcceptDelayMs: config.autoAcceptDelayMs ?? currentConfig?.autoAcceptDelayMs ?? 8000,
durationMinutes: currentConfig?.durationMinutes,
};
this.screenManager.updateRespawnConfig(id, merged);
@@ -1306,6 +1308,8 @@ export class WebServer extends EventEmitter {
sendClear: config.sendClear,
sendInit: config.sendInit,
kickstartPrompt: config.kickstartPrompt,
autoAcceptPrompts: config.autoAcceptPrompts,
autoAcceptDelayMs: config.autoAcceptDelayMs,
durationMinutes,
};
this.screenManager.updateRespawnConfig(sessionId, persistedConfig);
@@ -1537,6 +1541,10 @@ export class WebServer extends EventEmitter {
this.broadcast('respawn:detectionUpdate', { sessionId, detection });
});
controller.on('autoAcceptSent', () => {
this.broadcast('respawn:autoAcceptSent', { sessionId });
});
controller.on('log', (message: string) => {
this.broadcast('respawn:log', { sessionId, message });
});
@@ -2008,6 +2016,8 @@ export class WebServer extends EventEmitter {
kickstartPrompt: savedState.respawnConfig.kickstartPrompt,
completionConfirmMs: savedState.respawnConfig.completionConfirmMs,
noOutputTimeoutMs: savedState.respawnConfig.noOutputTimeoutMs,
autoAcceptPrompts: savedState.respawnConfig.autoAcceptPrompts ?? true,
autoAcceptDelayMs: savedState.respawnConfig.autoAcceptDelayMs ?? 8000,
});
this.respawnControllers.set(session.id, controller);
this.setupRespawnListeners(session.id, controller);
@@ -2035,6 +2045,8 @@ export class WebServer extends EventEmitter {
sendClear: screen.respawnConfig.sendClear,
sendInit: screen.respawnConfig.sendInit,
kickstartPrompt: screen.respawnConfig.kickstartPrompt,
autoAcceptPrompts: screen.respawnConfig.autoAcceptPrompts ?? true,
autoAcceptDelayMs: screen.respawnConfig.autoAcceptDelayMs ?? 8000,
});
this.respawnControllers.set(session.id, controller);
this.setupRespawnListeners(session.id, controller);