mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 22:19:42 +02:00
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:
@@ -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 = '';
|
||||
|
||||
@@ -65,6 +65,11 @@
|
||||
<div class="respawn-banner" id="respawnBanner" style="display: none;">
|
||||
<span class="respawn-indicator">↻</span>
|
||||
<span class="respawn-state" id="respawnState">Watching</span>
|
||||
<span class="respawn-detection" id="respawnDetection">
|
||||
<span class="detection-status" id="detectionStatus"></span>
|
||||
<span class="detection-waiting" id="detectionWaiting"></span>
|
||||
<span class="detection-confidence" id="detectionConfidence"></span>
|
||||
</span>
|
||||
<span class="respawn-cycles">Cycle <span id="respawnCycleCount">0</span></span>
|
||||
<span class="respawn-timer" id="respawnTimer" style="display: none;"></span>
|
||||
<span class="respawn-tokens" id="respawnTokens" style="display: none;"></span>
|
||||
|
||||
@@ -486,6 +486,44 @@ body {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.respawn-detection {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0 0.5rem;
|
||||
border-left: 1px solid rgba(34, 197, 94, 0.2);
|
||||
}
|
||||
|
||||
.detection-status {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.detection-waiting {
|
||||
color: var(--accent);
|
||||
font-size: 0.7rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.detection-confidence {
|
||||
color: var(--text-dim);
|
||||
font-family: 'SF Mono', Monaco, monospace;
|
||||
font-size: 0.65rem;
|
||||
padding: 0.1rem 0.3rem;
|
||||
background: rgba(34, 197, 94, 0.15);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.detection-confidence.high {
|
||||
background: rgba(34, 197, 94, 0.3);
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.detection-confidence.medium {
|
||||
background: rgba(234, 179, 8, 0.2);
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@@ -1271,6 +1271,8 @@ export class WebServer extends EventEmitter {
|
||||
controller.stop();
|
||||
controller.removeAllListeners();
|
||||
this.respawnControllers.delete(sessionId);
|
||||
// Notify UI that respawn is stopped for this session
|
||||
this.broadcast('respawn:stopped', { sessionId, reason: 'session_cleanup' });
|
||||
}
|
||||
|
||||
// Clear respawn timer
|
||||
@@ -1445,6 +1447,10 @@ export class WebServer extends EventEmitter {
|
||||
this.broadcast('respawn:stepCompleted', { sessionId, step });
|
||||
});
|
||||
|
||||
controller.on('detectionUpdate', (detection: unknown) => {
|
||||
this.broadcast('respawn:detectionUpdate', { sessionId, detection });
|
||||
});
|
||||
|
||||
controller.on('log', (message: string) => {
|
||||
this.broadcast('respawn:log', { sessionId, message });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user