mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 22:19:42 +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:
@@ -99,11 +99,11 @@ const BUILTIN_RESPAWN_PRESETS = [
|
||||
description: 'Unattended overnight runs with full context reset between cycles',
|
||||
config: {
|
||||
idleTimeoutMs: 10000,
|
||||
updatePrompt: 'summarize what you accomplished so far and write key progress notes to CLAUDE.md, then continue working on the task.',
|
||||
updatePrompt: 'summarize what you accomplished so far and write key progress notes to CLAUDE.md so the next cycle can pick up where you left off.',
|
||||
interStepDelayMs: 3000,
|
||||
sendClear: true,
|
||||
sendInit: true,
|
||||
kickstartPrompt: 'read CLAUDE.md for context on what was accomplished in previous cycles, then pick up where you left off.',
|
||||
kickstartPrompt: 'continue working on the task. Pick up where you left off based on the context above.',
|
||||
autoAcceptPrompts: true,
|
||||
},
|
||||
durationMinutes: 480,
|
||||
@@ -7471,7 +7471,7 @@ class ClaudemanApp {
|
||||
if (config.enableRespawn) {
|
||||
const ralphUpdatePrompt = 'Before /clear: Update CLAUDE.md with discoveries and notes, mark completed tasks in @fix_plan.md, write a brief progress summary to a file so the next iteration can continue seamlessly.';
|
||||
|
||||
const ralphKickstartPrompt = `You are in a Ralph Wiggum loop. Read @fix_plan.md for task status, check CLAUDE.md for notes from previous iterations, continue on the next uncompleted task, output <promise>${config.completionPhrase}</promise> when ALL tasks are complete.`;
|
||||
const ralphKickstartPrompt = `You are in a Ralph Wiggum loop. Read @fix_plan.md for task status, continue on the next uncompleted task, output <promise>${config.completionPhrase}</promise> when ALL tasks are complete.`;
|
||||
|
||||
await fetch(`/api/sessions/${sessionId}/respawn/enable`, {
|
||||
method: 'POST',
|
||||
|
||||
@@ -32,6 +32,7 @@ import { writeHooksConfig, updateCaseEnvVars } from '../hooks-config.js';
|
||||
import { subagentWatcher, type SubagentInfo, type SubagentToolCall, type SubagentProgress, type SubagentMessage, type SubagentToolResult } from '../subagent-watcher.js';
|
||||
import { imageWatcher } from '../image-watcher.js';
|
||||
import { TranscriptWatcher } from '../transcript-watcher.js';
|
||||
import { TeamWatcher } from '../team-watcher.js';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { createRequire } from 'node:module';
|
||||
import { RunSummaryTracker } from '../run-summary.js';
|
||||
@@ -413,6 +414,13 @@ export class WebServer extends EventEmitter {
|
||||
detected: (event: ImageDetectedEvent) => void;
|
||||
error: (error: Error, sessionId?: string) => void;
|
||||
} | null = null;
|
||||
private teamWatcher: TeamWatcher = new TeamWatcher();
|
||||
private teamWatcherHandlers: {
|
||||
teamCreated: (config: unknown) => void;
|
||||
teamUpdated: (config: unknown) => void;
|
||||
teamRemoved: (config: unknown) => void;
|
||||
taskUpdated: (data: unknown) => void;
|
||||
} | null = null;
|
||||
constructor(port: number = 3000, https: boolean = false, testMode: boolean = false) {
|
||||
super();
|
||||
this.setMaxListeners(0);
|
||||
@@ -447,6 +455,9 @@ export class WebServer extends EventEmitter {
|
||||
|
||||
// Set up image watcher listeners
|
||||
this.setupImageWatcherListeners();
|
||||
|
||||
// Set up team watcher listeners
|
||||
this.setupTeamWatcherListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -527,6 +538,37 @@ export class WebServer extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up event listeners for team watcher.
|
||||
* Broadcasts team activity events to SSE clients.
|
||||
*/
|
||||
private setupTeamWatcherListeners(): void {
|
||||
this.teamWatcherHandlers = {
|
||||
teamCreated: (config: unknown) => this.broadcast('team:created', config),
|
||||
teamUpdated: (config: unknown) => this.broadcast('team:updated', config),
|
||||
teamRemoved: (config: unknown) => this.broadcast('team:removed', config),
|
||||
taskUpdated: (data: unknown) => this.broadcast('team:taskUpdated', data),
|
||||
};
|
||||
|
||||
this.teamWatcher.on('teamCreated', this.teamWatcherHandlers.teamCreated);
|
||||
this.teamWatcher.on('teamUpdated', this.teamWatcherHandlers.teamUpdated);
|
||||
this.teamWatcher.on('teamRemoved', this.teamWatcherHandlers.teamRemoved);
|
||||
this.teamWatcher.on('taskUpdated', this.teamWatcherHandlers.taskUpdated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up team watcher listeners to prevent memory leaks.
|
||||
*/
|
||||
private cleanupTeamWatcherListeners(): void {
|
||||
if (this.teamWatcherHandlers) {
|
||||
this.teamWatcher.off('teamCreated', this.teamWatcherHandlers.teamCreated);
|
||||
this.teamWatcher.off('teamUpdated', this.teamWatcherHandlers.teamUpdated);
|
||||
this.teamWatcher.off('teamRemoved', this.teamWatcherHandlers.teamRemoved);
|
||||
this.teamWatcher.off('taskUpdated', this.teamWatcherHandlers.taskUpdated);
|
||||
this.teamWatcherHandlers = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async setupRoutes(): Promise<void> {
|
||||
// Allow multipart/form-data for screenshot uploads — skip Fastify's body parser
|
||||
// so the route handler can read the raw stream directly.
|
||||
@@ -3579,6 +3621,20 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
});
|
||||
|
||||
|
||||
// ========== Agent Teams ==========
|
||||
|
||||
// List all discovered teams
|
||||
this.app.get('/api/teams', async () => {
|
||||
return { success: true, data: this.teamWatcher.getTeams() };
|
||||
});
|
||||
|
||||
// Get tasks for a specific team
|
||||
this.app.get('/api/teams/:name/tasks', async (req) => {
|
||||
const { name } = req.params as { name: string };
|
||||
return { success: true, data: this.teamWatcher.getTeamTasks(name) };
|
||||
});
|
||||
|
||||
|
||||
// ========== Hook Events ==========
|
||||
|
||||
this.app.post('/api/hook-event', async (req) => {
|
||||
@@ -4324,6 +4380,9 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
}
|
||||
|
||||
private setupRespawnListeners(sessionId: string, controller: RespawnController): void {
|
||||
// Wire team watcher for team-aware idle detection
|
||||
controller.setTeamWatcher(this.teamWatcher);
|
||||
|
||||
// Helper to get tracker lazily (may not exist at setup time for restored sessions)
|
||||
const getTracker = () => this.runSummaryTrackers.get(sessionId);
|
||||
|
||||
@@ -5150,6 +5209,10 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
console.log('Image watcher disabled by user settings');
|
||||
}
|
||||
|
||||
// Start team watcher for agent team awareness (always on — lightweight polling)
|
||||
this.teamWatcher.start();
|
||||
console.log('Team watcher started - monitoring ~/.claude/teams/ for agent team activity');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -5462,6 +5525,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
// Clean up watcher listeners to prevent memory leaks
|
||||
this.cleanupSubagentWatcherListeners();
|
||||
this.cleanupImageWatcherListeners();
|
||||
this.cleanupTeamWatcherListeners();
|
||||
|
||||
// Stop subagent watcher
|
||||
subagentWatcher.stop();
|
||||
@@ -5469,6 +5533,9 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
// Stop image watcher
|
||||
imageWatcher.stop();
|
||||
|
||||
// Stop team watcher
|
||||
this.teamWatcher.stop();
|
||||
|
||||
// Destroy file stream manager (clears cleanup timer and kills remaining tail processes)
|
||||
fileStreamManager.destroy();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user