mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-05 23:19:43 +02:00
fix: ralph tracker — 3 bugs: first-occurrence completion, double-counting, TodoWrite pattern
Bug 1 (CRITICAL): canonicalCount >= 1 always fired on first <promise> tag (prompt echo). Changed to >= 2 so only 2nd+ occurrence triggers completion. Bug 2: checkMultiLinePatterns() re-detected complete tags already handled by processLine(), double-counting. Now only tries completion when partial buffer is non-empty (cross-chunk scenario). Bug 3: TodoWrite ✔ patterns required "Task #N" but real Claude Code output is plain "✔ content". Added TODO_PLAIN_CHECKMARK_PATTERN fallback. Includes 71 new deep tests + real-life verification. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,7 @@ import { randomUUID } from 'node:crypto';
|
||||
import { Session } from './session.js';
|
||||
import { AiIdleChecker, type AiCheckResult, type AiCheckState } from './ai-idle-checker.js';
|
||||
import { AiPlanChecker, type AiPlanCheckResult } from './ai-plan-checker.js';
|
||||
import type { TeamWatcher } from './team-watcher.js';
|
||||
import { BufferAccumulator } from './utils/buffer-accumulator.js';
|
||||
import {
|
||||
ANSI_ESCAPE_PATTERN_SIMPLE,
|
||||
@@ -223,7 +224,7 @@ export interface RespawnConfig {
|
||||
/**
|
||||
* The prompt to send when updating docs.
|
||||
* Sent at the start of each respawn cycle.
|
||||
* @default 'update all the docs and CLAUDE.md'
|
||||
* @default 'write a brief progress summary to CLAUDE.md noting what you accomplished, then continue working.'
|
||||
*/
|
||||
updatePrompt: string;
|
||||
|
||||
@@ -542,7 +543,7 @@ export interface RespawnEvents {
|
||||
/** Default configuration values */
|
||||
const DEFAULT_CONFIG: RespawnConfig = {
|
||||
idleTimeoutMs: 10000, // 10 seconds of no activity after prompt (legacy, still used as fallback)
|
||||
updatePrompt: 'update all the docs and CLAUDE.md',
|
||||
updatePrompt: 'write a brief progress summary to CLAUDE.md noting what you accomplished, then continue working.',
|
||||
interStepDelayMs: 1000, // 1 second between steps
|
||||
enabled: true,
|
||||
sendClear: true, // send /clear after update prompt
|
||||
@@ -638,6 +639,9 @@ export class RespawnController extends EventEmitter {
|
||||
/** The session being controlled */
|
||||
private session: Session;
|
||||
|
||||
/** Optional team watcher for team-aware idle detection */
|
||||
private teamWatcher: TeamWatcher | null = null;
|
||||
|
||||
/** Current configuration */
|
||||
private config: RespawnConfig;
|
||||
|
||||
@@ -1220,6 +1224,11 @@ export class RespawnController extends EventEmitter {
|
||||
this.emit('log', `[${timestamp}] [Respawn] ${message}`);
|
||||
}
|
||||
|
||||
/** Set team watcher for team-aware idle detection */
|
||||
setTeamWatcher(watcher: TeamWatcher): void {
|
||||
this.teamWatcher = watcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start watching the session for idle state.
|
||||
*
|
||||
@@ -2762,6 +2771,19 @@ export class RespawnController extends EventEmitter {
|
||||
`tokensStable=${status.tokensStable}, ` +
|
||||
`noWorking=${status.workingPatternsAbsent}`);
|
||||
|
||||
// ========== Agent Teams Integration ==========
|
||||
// Check if session has active teammates — don't respawn while team is working
|
||||
if (this.teamWatcher?.hasActiveTeammates(this.session.id)) {
|
||||
const count = this.teamWatcher.getActiveTeammateCount(this.session.id);
|
||||
this.log(`Respawn blocked - ${count} active teammate(s) working`);
|
||||
this.logAction('team', `Active teammates: ${count}`);
|
||||
this.emit('respawnBlocked', { reason: 'active_teammates', details: `${count} teammate(s) still working` });
|
||||
this.setState('watching');
|
||||
this.startNoOutputTimer();
|
||||
this.startPreFilterTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
// ========== RALPH_STATUS Integration ==========
|
||||
// Check circuit breaker status - if OPEN, pause respawn
|
||||
const ralphTracker = this.session.ralphTracker;
|
||||
@@ -2879,9 +2901,10 @@ export class RespawnController extends EventEmitter {
|
||||
let updatePrompt = this.config.updatePrompt;
|
||||
|
||||
if (statusBlock?.recommendation) {
|
||||
// Append RECOMMENDATION to the update prompt for context
|
||||
updatePrompt = `${this.config.updatePrompt}\n\nClaude's last recommendation: ${statusBlock.recommendation}`;
|
||||
this.logAction('ralph', `Using RECOMMENDATION: ${statusBlock.recommendation.substring(0, 50)}...`);
|
||||
// Append RECOMMENDATION to the update prompt (single-line — writeViaMux breaks on newlines)
|
||||
const rec = statusBlock.recommendation.replace(/\n/g, ' ').substring(0, 200);
|
||||
updatePrompt = `${this.config.updatePrompt} (Claude's last recommendation: ${rec})`;
|
||||
this.logAction('ralph', `Using RECOMMENDATION: ${rec.substring(0, 50)}...`);
|
||||
}
|
||||
|
||||
const input = updatePrompt + '\r'; // \r triggers Enter in Ink/Claude CLI
|
||||
|
||||
Reference in New Issue
Block a user