feat: add AI-powered idle check for respawn controller

Replace the "Worked for Xm Xs" pattern as the sole primary idle
detection signal with a final AI-powered check. When pre-filter
conditions are met (output silence, no working patterns, tokens
stable), a fresh Claude CLI session is spawned in a screen to
analyze terminal output and provide a definitive IDLE/WORKING
verdict before proceeding with the respawn cycle.

New state in state machine: `ai_checking` (between pre-filter
confirmation and `sending_update`). WORKING verdict triggers a
3-minute cooldown. Errors auto-disable after 3 consecutive
failures, falling back to the existing noOutputTimeoutMs safety net.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-01-24 18:48:37 +01:00
co-authored by Claude Opus 4.5
parent de8033fed5
commit 0e56e590f3
8 changed files with 1602 additions and 17 deletions
+36
View File
@@ -815,6 +815,11 @@ export class WebServer extends EventEmitter {
kickstartPrompt: config.kickstartPrompt ?? currentConfig?.kickstartPrompt,
autoAcceptPrompts: config.autoAcceptPrompts ?? currentConfig?.autoAcceptPrompts ?? true,
autoAcceptDelayMs: config.autoAcceptDelayMs ?? currentConfig?.autoAcceptDelayMs ?? 8000,
aiIdleCheckEnabled: config.aiIdleCheckEnabled ?? currentConfig?.aiIdleCheckEnabled ?? true,
aiIdleCheckModel: config.aiIdleCheckModel ?? currentConfig?.aiIdleCheckModel,
aiIdleCheckMaxContext: config.aiIdleCheckMaxContext ?? currentConfig?.aiIdleCheckMaxContext,
aiIdleCheckTimeoutMs: config.aiIdleCheckTimeoutMs ?? currentConfig?.aiIdleCheckTimeoutMs,
aiIdleCheckCooldownMs: config.aiIdleCheckCooldownMs ?? currentConfig?.aiIdleCheckCooldownMs,
durationMinutes: currentConfig?.durationMinutes,
};
this.screenManager.updateRespawnConfig(id, merged);
@@ -1566,6 +1571,11 @@ export class WebServer extends EventEmitter {
kickstartPrompt: config.kickstartPrompt,
autoAcceptPrompts: config.autoAcceptPrompts,
autoAcceptDelayMs: config.autoAcceptDelayMs,
aiIdleCheckEnabled: config.aiIdleCheckEnabled,
aiIdleCheckModel: config.aiIdleCheckModel,
aiIdleCheckMaxContext: config.aiIdleCheckMaxContext,
aiIdleCheckTimeoutMs: config.aiIdleCheckTimeoutMs,
aiIdleCheckCooldownMs: config.aiIdleCheckCooldownMs,
durationMinutes,
};
this.screenManager.updateRespawnConfig(sessionId, persistedConfig);
@@ -1818,6 +1828,22 @@ export class WebServer extends EventEmitter {
this.broadcast('respawn:autoAcceptSent', { sessionId });
});
controller.on('aiCheckStarted', () => {
this.broadcast('respawn:aiCheckStarted', { sessionId });
});
controller.on('aiCheckCompleted', (result: { verdict: string; reasoning: string; durationMs: number }) => {
this.broadcast('respawn:aiCheckCompleted', { sessionId, verdict: result.verdict, reasoning: result.reasoning, durationMs: result.durationMs });
});
controller.on('aiCheckFailed', (error: string) => {
this.broadcast('respawn:aiCheckFailed', { sessionId, error });
});
controller.on('aiCheckCooldown', (active: boolean, endsAt: number | null) => {
this.broadcast('respawn:aiCheckCooldown', { sessionId, active, endsAt });
});
controller.on('log', (message: string) => {
this.broadcast('respawn:log', { sessionId, message });
});
@@ -2401,6 +2427,11 @@ export class WebServer extends EventEmitter {
noOutputTimeoutMs: savedState.respawnConfig.noOutputTimeoutMs,
autoAcceptPrompts: savedState.respawnConfig.autoAcceptPrompts ?? true,
autoAcceptDelayMs: savedState.respawnConfig.autoAcceptDelayMs ?? 8000,
aiIdleCheckEnabled: savedState.respawnConfig.aiIdleCheckEnabled ?? true,
aiIdleCheckModel: savedState.respawnConfig.aiIdleCheckModel ?? 'claude-opus-4-5-20251101',
aiIdleCheckMaxContext: savedState.respawnConfig.aiIdleCheckMaxContext ?? 16000,
aiIdleCheckTimeoutMs: savedState.respawnConfig.aiIdleCheckTimeoutMs ?? 90000,
aiIdleCheckCooldownMs: savedState.respawnConfig.aiIdleCheckCooldownMs ?? 180000,
});
this.respawnControllers.set(session.id, controller);
this.setupRespawnListeners(session.id, controller);
@@ -2430,6 +2461,11 @@ export class WebServer extends EventEmitter {
kickstartPrompt: screen.respawnConfig.kickstartPrompt,
autoAcceptPrompts: screen.respawnConfig.autoAcceptPrompts ?? true,
autoAcceptDelayMs: screen.respawnConfig.autoAcceptDelayMs ?? 8000,
aiIdleCheckEnabled: screen.respawnConfig.aiIdleCheckEnabled ?? true,
aiIdleCheckModel: screen.respawnConfig.aiIdleCheckModel ?? 'claude-opus-4-5-20251101',
aiIdleCheckMaxContext: screen.respawnConfig.aiIdleCheckMaxContext ?? 16000,
aiIdleCheckTimeoutMs: screen.respawnConfig.aiIdleCheckTimeoutMs ?? 90000,
aiIdleCheckCooldownMs: screen.respawnConfig.aiIdleCheckCooldownMs ?? 180000,
});
this.respawnControllers.set(session.id, controller);
this.setupRespawnListeners(session.id, controller);