fix: respawn controller multi-layer detection and cleanup

- Update idle detection from legacy '↵ send' to completion message pattern
  ("for Xm Xs" time patterns like "Worked for 2m 46s")
- Add confirming_idle state for false positive prevention
- Add completionConfirmMs (5s) and noOutputTimeoutMs (30s) config options
- Add multi-layer detection with confidence scoring (0-100%)
- Fix null pointer error in extractTokenCount with guard clause
- Fix respawn controller not stopping on session cleanup (broadcast respawn:stopped)
- Update tests to use new completion message patterns
- Add detection status UI display (confidence level, waiting state)
- Update CLAUDE.md with new detection documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-01-22 19:24:36 +01:00
co-authored by Claude Opus 4.5
parent 34fb33fbc6
commit ec92e5b1cf
8 changed files with 678 additions and 261 deletions
+43
View File
@@ -503,6 +503,16 @@ class ClaudemanApp {
}
});
this.eventSource.addEventListener('respawn:detectionUpdate', (e) => {
const data = JSON.parse(e.data);
if (this.respawnStatus[data.sessionId]) {
this.respawnStatus[data.sessionId].detection = data.detection;
}
if (data.sessionId === this.activeSessionId) {
this.updateDetectionDisplay(data.detection);
}
});
// Respawn timer events
this.eventSource.addEventListener('respawn:timerStarted', (e) => {
const data = JSON.parse(e.data);
@@ -1287,6 +1297,39 @@ class ClaudemanApp {
this.$('respawnState').textContent = state.replace(/_/g, ' ');
}
updateDetectionDisplay(detection) {
if (!detection) return;
const statusEl = this.$('detectionStatus');
const waitingEl = this.$('detectionWaiting');
const confidenceEl = this.$('detectionConfidence');
// Update status text
statusEl.textContent = detection.statusText || '';
// Update waiting for text
if (detection.waitingFor) {
waitingEl.textContent = `→ ${detection.waitingFor}`;
} else {
waitingEl.textContent = '';
}
// Update confidence level
const confidence = detection.confidenceLevel || 0;
if (confidence > 0) {
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');
}
} else {
confidenceEl.style.display = 'none';
}
}
showRespawnTimer() {
const timerEl = this.$('respawnTimer');
timerEl.style.display = '';