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
+50
View File
@@ -939,6 +939,36 @@ class ClaudemanApp {
}
});
this.eventSource.addEventListener('respawn:aiCheckStarted', (e) => {
const data = JSON.parse(e.data);
if (data.sessionId === this.activeSessionId) {
document.getElementById('respawnStep').textContent = 'AI Check: Analyzing...';
}
});
this.eventSource.addEventListener('respawn:aiCheckCompleted', (e) => {
const data = JSON.parse(e.data);
if (data.sessionId === this.activeSessionId) {
const icon = data.verdict === 'IDLE' ? '✓' : '⏳';
document.getElementById('respawnStep').textContent = `AI Check: ${data.verdict} ${icon}`;
}
});
this.eventSource.addEventListener('respawn:aiCheckFailed', (e) => {
const data = JSON.parse(e.data);
if (data.sessionId === this.activeSessionId) {
document.getElementById('respawnStep').textContent = `AI Check: Error - ${data.error?.substring(0, 50)}`;
}
});
this.eventSource.addEventListener('respawn:aiCheckCooldown', (e) => {
const data = JSON.parse(e.data);
if (data.sessionId === this.activeSessionId && data.active) {
const remaining = Math.ceil((data.endsAt - Date.now()) / 1000);
document.getElementById('respawnStep').textContent = `AI Check: WORKING (cooldown ${remaining}s)`;
}
});
// Respawn timer events
this.eventSource.addEventListener('respawn:timerStarted', (e) => {
const data = JSON.parse(e.data);
@@ -1904,6 +1934,26 @@ class ClaudemanApp {
} else {
confidenceEl.style.display = 'none';
}
// Show AI check info if available
const aiCheckEl = document.getElementById('detectionAiCheck');
if (aiCheckEl && detection.aiCheck) {
const ai = detection.aiCheck;
let aiText = '';
if (ai.status === 'checking') {
aiText = 'AI: Analyzing...';
} else if (ai.status === 'cooldown' && ai.cooldownEndsAt) {
const remaining = Math.ceil((ai.cooldownEndsAt - Date.now()) / 1000);
aiText = `AI: WORKING (${remaining}s cooldown)`;
} else if (ai.status === 'disabled') {
aiText = `AI: Disabled (${ai.disabledReason || 'errors'})`;
} else if (ai.lastVerdict) {
aiText = `AI: Last=${ai.lastVerdict}`;
if (ai.lastCheckDurationMs) aiText += ` (${Math.round(ai.lastCheckDurationMs / 1000)}s)`;
}
aiCheckEl.textContent = aiText;
aiCheckEl.style.display = aiText ? '' : 'none';
}
}
showRespawnTimer() {
+1
View File
@@ -73,6 +73,7 @@
<span class="detection-status" id="detectionStatus"></span>
<span class="detection-waiting" id="detectionWaiting"></span>
<span class="detection-confidence" id="detectionConfidence"></span>
<span class="detection-ai-check" id="detectionAiCheck" style="display: none;"></span>
</span>
<span class="respawn-cycles">Cycle <span id="respawnCycleCount">0</span></span>
<span class="respawn-timer" id="respawnTimer" style="display: none;"></span>