mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 14:09:42 +02:00
feat: add hook-based idle detection for respawn controller
Implement multi-phase improvement to respawn controller's idle detection: Phase 1 - Stop Hook Detection: - Add signalStopHook() method - definitive signal when Claude finishes - 3s confirmation timer to handle race conditions - Skip AI check when hook received (100% confidence) Phase 2 - idle_prompt Detection: - Add signalIdlePrompt() method - fires after 60s+ of idle - Immediately confirms idle (no confirmation timer needed) Phase 3 - Transcript File Monitoring: - New TranscriptWatcher class watches session JSONL files - Detects: completion, tool execution, plan mode, errors - Signals respawn controller for supporting detection Web UI Updates: - Hook indicator with purple styling and pulse animation - Shows "Stop hook received" or "idle_prompt hook received" - 100% confidence displayed with hook-confirmed style This significantly improves idle detection reliability by using definitive signals from Claude Code rather than parsing terminal output. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+21
-2
@@ -2153,6 +2153,19 @@ class ClaudemanApp {
|
||||
const waitingEl = this.$('detectionWaiting');
|
||||
const confidenceEl = this.$('detectionConfidence');
|
||||
const aiCheckEl = document.getElementById('detectionAiCheck');
|
||||
const hookEl = document.getElementById('detectionHook');
|
||||
|
||||
// Hook-based detection indicator (highest priority signals)
|
||||
if (hookEl) {
|
||||
if (detection.stopHookReceived || detection.idlePromptReceived) {
|
||||
const hookType = detection.idlePromptReceived ? 'idle_prompt' : 'Stop';
|
||||
hookEl.textContent = `🎯 ${hookType} hook received`;
|
||||
hookEl.className = 'detection-hook hook-active';
|
||||
hookEl.style.display = '';
|
||||
} else {
|
||||
hookEl.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Simplified status - only show when meaningful
|
||||
if (detection.statusText && detection.statusText !== 'Watching...') {
|
||||
@@ -2171,8 +2184,14 @@ class ClaudemanApp {
|
||||
confidenceEl.textContent = `${confidence}%`;
|
||||
confidenceEl.style.display = '';
|
||||
confidenceEl.className = 'detection-confidence';
|
||||
if (confidence >= 60) confidenceEl.classList.add('high');
|
||||
else if (confidence >= 30) confidenceEl.classList.add('medium');
|
||||
// Hook signals give 100% confidence
|
||||
if (detection.stopHookReceived || detection.idlePromptReceived) {
|
||||
confidenceEl.classList.add('hook-confirmed');
|
||||
} else if (confidence >= 60) {
|
||||
confidenceEl.classList.add('high');
|
||||
} else if (confidence >= 30) {
|
||||
confidenceEl.classList.add('medium');
|
||||
}
|
||||
} else {
|
||||
confidenceEl.style.display = 'none';
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
<span class="detection-status" id="detectionStatus"></span>
|
||||
<span class="detection-waiting" id="detectionWaiting" style="display: none;"></span>
|
||||
<span class="detection-confidence" id="detectionConfidence"></span>
|
||||
<span class="detection-hook" id="detectionHook" style="display: none;"></span>
|
||||
<span class="detection-ai-check" id="detectionAiCheck" style="display: none;"></span>
|
||||
<span class="respawn-spacer"></span>
|
||||
<span class="respawn-cycles">#<span id="respawnCycleCount">0</span></span>
|
||||
|
||||
@@ -671,6 +671,31 @@ body {
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
.detection-confidence.hook-confirmed {
|
||||
background: rgba(139, 92, 246, 0.3);
|
||||
color: var(--purple, #a78bfa);
|
||||
}
|
||||
|
||||
.detection-hook {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.1rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
background: rgba(139, 92, 246, 0.15);
|
||||
color: var(--purple, #a78bfa);
|
||||
}
|
||||
|
||||
.detection-hook.hook-active {
|
||||
background: rgba(139, 92, 246, 0.25);
|
||||
color: var(--purple, #a78bfa);
|
||||
font-weight: 500;
|
||||
animation: pulse-hook 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-hook {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user