mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-04 06:29:42 +02:00
refactor: simplify ralph wizard from 9 agents to 2
Remove execution layer that never actually controlled execution: - execution-bridge.ts (model param was ignored) - group-scheduler.ts (task-tool mode never used) - model-selector.ts (recommendations were display-only) - context-manager.ts (never invoked) - execution-limits.ts Remove redundant agent prompts (overlapping outputs): - requirements-analyst, architecture-planner, risk-analyst - testing-specialist, verification (merged into planner.ts) - execution-optimizer (output was ignored) - final-review (scores were cosmetic) Simplify plan-orchestrator.ts from 2400 LOC to 520 LOC: - Before: 9 agents, 6 phases, ~40-60 minutes - After: 2 agents (research + planner), ~18 minutes Remove /api/execution/* endpoints and related server code. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -34,13 +34,6 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
import { createRequire } from 'node:module';
|
||||
import { RunSummaryTracker } from '../run-summary.js';
|
||||
import { PlanOrchestrator, type DetailedPlanResult } from '../plan-orchestrator.js';
|
||||
import {
|
||||
ExecutionBridge,
|
||||
getExecutionBridge,
|
||||
type PlanItem as ExecutionPlanItem,
|
||||
type AgentSpawner,
|
||||
} from '../execution-bridge.js';
|
||||
import { type ModelConfig } from '../model-selector.js';
|
||||
|
||||
// Load version from package.json
|
||||
const require = createRequire(import.meta.url);
|
||||
@@ -314,8 +307,6 @@ export class WebServer extends EventEmitter {
|
||||
private pendingRespawnStarts: Map<string, NodeJS.Timeout> = new Map();
|
||||
// Active plan orchestrators (for cancellation via API)
|
||||
private activePlanOrchestrators: Map<string, PlanOrchestrator> = new Map();
|
||||
// Execution bridge for parallel task execution
|
||||
private executionBridge: ExecutionBridge;
|
||||
// Grace period before starting restored respawn controllers (2 minutes)
|
||||
private static readonly RESPAWN_RESTORE_GRACE_PERIOD_MS = 2 * 60 * 1000;
|
||||
|
||||
@@ -346,10 +337,6 @@ export class WebServer extends EventEmitter {
|
||||
this.broadcast('screen:statsUpdated', screens);
|
||||
});
|
||||
|
||||
// Initialize execution bridge with model config from settings
|
||||
this.executionBridge = getExecutionBridge(this.loadModelConfig());
|
||||
this.setupExecutionBridgeListeners();
|
||||
|
||||
// Set up subagent watcher listeners
|
||||
this.setupSubagentWatcherListeners();
|
||||
|
||||
@@ -3045,148 +3032,6 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
return this.getSystemStats();
|
||||
});
|
||||
|
||||
// ========== Execution Bridge Endpoints ==========
|
||||
|
||||
// Get execution status
|
||||
this.app.get('/api/execution/status', async () => {
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
status: this.executionBridge.status,
|
||||
progress: this.executionBridge.getProgress(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Get execution schedule (the current plan)
|
||||
this.app.get('/api/execution/schedule', async () => {
|
||||
const schedule = this.executionBridge['_scheduler'].schedule;
|
||||
return { success: true, data: schedule };
|
||||
});
|
||||
|
||||
// Load a plan for execution
|
||||
// Accepts either ExecutionPlanItem format (id, title, description) or
|
||||
// PlanOrchestrator PlanItem format (id, content) and converts as needed
|
||||
this.app.post('/api/execution/load', async (req) => {
|
||||
const { items, workingDir } = req.body as {
|
||||
items: Array<{
|
||||
id?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
content?: string;
|
||||
parallelGroup?: number;
|
||||
agentType?: string;
|
||||
recommendedModel?: string;
|
||||
requiresFreshContext?: boolean;
|
||||
estimatedTokens?: number;
|
||||
inputFiles?: string[];
|
||||
outputFiles?: string[];
|
||||
dependencies?: string[];
|
||||
}>;
|
||||
workingDir?: string;
|
||||
};
|
||||
if (!items || !Array.isArray(items)) {
|
||||
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'items array is required');
|
||||
}
|
||||
try {
|
||||
if (workingDir) {
|
||||
this.executionBridge.setWorkingDir(workingDir);
|
||||
}
|
||||
// Convert to ExecutionPlanItem format
|
||||
const execItems: ExecutionPlanItem[] = items.map((item, idx) => ({
|
||||
id: item.id || `task-${idx}`,
|
||||
title: item.title || item.content?.slice(0, 50) || `Task ${idx + 1}`,
|
||||
description: item.description || item.content || '',
|
||||
parallelGroup: item.parallelGroup,
|
||||
agentType: item.agentType,
|
||||
recommendedModel: item.recommendedModel,
|
||||
requiresFreshContext: item.requiresFreshContext,
|
||||
estimatedTokens: item.estimatedTokens,
|
||||
inputFiles: item.inputFiles,
|
||||
outputFiles: item.outputFiles,
|
||||
dependencies: item.dependencies,
|
||||
}));
|
||||
const schedule = this.executionBridge.loadPlan(execItems);
|
||||
return { success: true, data: schedule };
|
||||
} catch (err) {
|
||||
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, getErrorMessage(err));
|
||||
}
|
||||
});
|
||||
|
||||
// Start execution
|
||||
this.app.post('/api/execution/start', async () => {
|
||||
try {
|
||||
await this.executionBridge.start();
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, getErrorMessage(err));
|
||||
}
|
||||
});
|
||||
|
||||
// Pause execution
|
||||
this.app.post('/api/execution/pause', async () => {
|
||||
this.executionBridge.pause();
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
// Resume execution
|
||||
this.app.post('/api/execution/resume', async () => {
|
||||
this.executionBridge.resume();
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
// Cancel execution
|
||||
this.app.post('/api/execution/cancel', async (req) => {
|
||||
const { reason } = (req.body as { reason?: string }) || {};
|
||||
await this.executionBridge.cancel(reason || 'Cancelled via API');
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
// Reset execution bridge
|
||||
this.app.post('/api/execution/reset', async () => {
|
||||
this.executionBridge.reset();
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
// Get execution history
|
||||
this.app.get('/api/execution/history', async () => {
|
||||
return { success: true, data: this.executionBridge.getHistory() };
|
||||
});
|
||||
|
||||
// Get model configuration
|
||||
this.app.get('/api/execution/model-config', async () => {
|
||||
return { success: true, data: this.executionBridge.getModelConfig() };
|
||||
});
|
||||
|
||||
// Update model configuration
|
||||
this.app.put('/api/execution/model-config', async (req) => {
|
||||
const config = req.body as Partial<ModelConfig>;
|
||||
try {
|
||||
this.executionBridge.updateModelConfig(config);
|
||||
const fullConfig = this.executionBridge.getModelConfig();
|
||||
this.saveModelConfig(fullConfig);
|
||||
this.broadcast('execution:modelConfigUpdated', fullConfig);
|
||||
return { success: true, data: fullConfig };
|
||||
} catch (err) {
|
||||
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, getErrorMessage(err));
|
||||
}
|
||||
});
|
||||
|
||||
// Mark task complete (external signal)
|
||||
this.app.post('/api/execution/tasks/:taskId/complete', async (req) => {
|
||||
const { taskId } = req.params as { taskId: string };
|
||||
this.executionBridge.markTaskComplete(taskId);
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
// Mark task failed (external signal)
|
||||
this.app.post('/api/execution/tasks/:taskId/failed', async (req) => {
|
||||
const { taskId } = req.params as { taskId: string };
|
||||
const { error } = (req.body as { error?: string }) || {};
|
||||
this.executionBridge.markTaskFailed(taskId, error || 'Failed via API');
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
// ========== Subagent Monitoring (Claude Code Background Agents) ==========
|
||||
|
||||
// List all known subagents
|
||||
@@ -3910,193 +3755,6 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load model configuration from settings file.
|
||||
*/
|
||||
private loadModelConfig(): Partial<ModelConfig> {
|
||||
const settingsPath = join(homedir(), '.claudeman', 'settings.json');
|
||||
try {
|
||||
if (existsSync(settingsPath)) {
|
||||
const content = readFileSync(settingsPath, 'utf-8');
|
||||
const settings = JSON.parse(content);
|
||||
return settings.modelConfig || {};
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load model config:', getErrorMessage(err));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Save model configuration to settings file.
|
||||
*/
|
||||
private saveModelConfig(config: ModelConfig): void {
|
||||
const settingsPath = join(homedir(), '.claudeman', 'settings.json');
|
||||
try {
|
||||
const dir = dirname(settingsPath);
|
||||
if (!existsSync(dir)) {
|
||||
mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
let settings: Record<string, unknown> = {};
|
||||
if (existsSync(settingsPath)) {
|
||||
settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
||||
}
|
||||
settings.modelConfig = config;
|
||||
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
||||
} catch (err) {
|
||||
console.error('Failed to save model config:', getErrorMessage(err));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up event listeners for execution bridge.
|
||||
* Broadcasts execution progress to SSE clients.
|
||||
*/
|
||||
private setupExecutionBridgeListeners(): void {
|
||||
// Create agent spawner interface
|
||||
const spawner: AgentSpawner = {
|
||||
spawnAgentWithModel: async (taskId, workingDir, prompt, _model, _options) => {
|
||||
// Note: model and options are reserved for future model selection integration
|
||||
const globalNice = this.getGlobalNiceConfig();
|
||||
const session = new Session({
|
||||
workingDir,
|
||||
screenManager: this.screenManager,
|
||||
useScreen: true,
|
||||
mode: 'claude',
|
||||
name: `exec:${taskId}`,
|
||||
niceConfig: globalNice,
|
||||
});
|
||||
|
||||
this.sessions.set(session.id, session);
|
||||
this.store.incrementSessionsCreated();
|
||||
this.setupSessionListeners(session);
|
||||
|
||||
await session.startInteractive();
|
||||
this.broadcast('session:created', session.toDetailedState());
|
||||
this.broadcast('session:interactive', { id: session.id });
|
||||
this.persistSessionState(session);
|
||||
|
||||
// Configure ralph tracker for completion detection
|
||||
session.ralphTracker.enable();
|
||||
|
||||
// Set up completion listener for execution bridge
|
||||
session.on('ralphCompletionDetected', () => {
|
||||
this.executionBridge.markTaskComplete(taskId);
|
||||
});
|
||||
|
||||
// Send the prompt
|
||||
setTimeout(() => {
|
||||
session.writeViaScreen(prompt + '\r');
|
||||
}, 2000);
|
||||
|
||||
return { sessionId: session.id };
|
||||
},
|
||||
useTaskTool: async (_sessionId, _taskId, _prompt, _model) => {
|
||||
// Task tool mode - not yet implemented, falls back to session mode
|
||||
throw new Error('Task tool mode not yet implemented');
|
||||
},
|
||||
isTaskComplete: (taskId) => {
|
||||
// Check if task is marked complete in scheduler
|
||||
const schedule = this.executionBridge['_scheduler'].schedule;
|
||||
if (!schedule) return false;
|
||||
for (const group of schedule.groups) {
|
||||
const task = group.tasks.find(t => t.id === taskId);
|
||||
if (task) return task.status === 'completed';
|
||||
}
|
||||
return false;
|
||||
},
|
||||
getTaskResult: (taskId) => {
|
||||
const schedule = this.executionBridge['_scheduler'].schedule;
|
||||
if (!schedule) return null;
|
||||
for (const group of schedule.groups) {
|
||||
const task = group.tasks.find(t => t.id === taskId);
|
||||
if (task) {
|
||||
if (task.status === 'completed') {
|
||||
return { success: true };
|
||||
} else if (task.status === 'failed') {
|
||||
return { success: false, error: task.error };
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
this.executionBridge.setAgentSpawner(spawner);
|
||||
|
||||
// Set up session writer for context management
|
||||
this.executionBridge.setSessionWriter({
|
||||
writeToSession: (sessionId, text) => {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (session) {
|
||||
session.writeViaScreen(text);
|
||||
}
|
||||
},
|
||||
createSession: async (workingDir, name) => {
|
||||
const globalNice = this.getGlobalNiceConfig();
|
||||
const session = new Session({
|
||||
workingDir,
|
||||
screenManager: this.screenManager,
|
||||
useScreen: true,
|
||||
mode: 'claude',
|
||||
name: name || 'exec-context',
|
||||
niceConfig: globalNice,
|
||||
});
|
||||
this.sessions.set(session.id, session);
|
||||
this.store.incrementSessionsCreated();
|
||||
this.setupSessionListeners(session);
|
||||
await session.startInteractive();
|
||||
this.broadcast('session:created', session.toDetailedState());
|
||||
this.persistSessionState(session);
|
||||
return { sessionId: session.id };
|
||||
},
|
||||
});
|
||||
|
||||
// Forward execution bridge events as SSE broadcasts
|
||||
this.executionBridge.on('planLoaded', (schedule) => {
|
||||
this.broadcast('execution:planLoaded', { schedule });
|
||||
});
|
||||
this.executionBridge.on('started', () => {
|
||||
this.broadcast('execution:started', {});
|
||||
});
|
||||
this.executionBridge.on('paused', () => {
|
||||
this.broadcast('execution:paused', {});
|
||||
});
|
||||
this.executionBridge.on('resumed', () => {
|
||||
this.broadcast('execution:resumed', {});
|
||||
});
|
||||
this.executionBridge.on('completed', (data) => {
|
||||
this.broadcast('execution:completed', data);
|
||||
});
|
||||
this.executionBridge.on('cancelled', (reason) => {
|
||||
this.broadcast('execution:cancelled', { reason });
|
||||
});
|
||||
this.executionBridge.on('groupStarted', (data) => {
|
||||
this.broadcast('execution:groupStarted', data);
|
||||
});
|
||||
this.executionBridge.on('groupCompleted', (data) => {
|
||||
this.broadcast('execution:groupCompleted', data);
|
||||
});
|
||||
this.executionBridge.on('taskAssigned', (data) => {
|
||||
this.broadcast('execution:taskAssigned', data);
|
||||
});
|
||||
this.executionBridge.on('taskCompleted', (data) => {
|
||||
this.broadcast('execution:taskCompleted', data);
|
||||
});
|
||||
this.executionBridge.on('taskFailed', (data) => {
|
||||
this.broadcast('execution:taskFailed', data);
|
||||
});
|
||||
this.executionBridge.on('freshContext', (data) => {
|
||||
this.broadcast('execution:freshContext', data);
|
||||
});
|
||||
this.executionBridge.on('modelSelected', (data) => {
|
||||
this.broadcast('execution:modelSelected', data);
|
||||
});
|
||||
this.executionBridge.on('progress', (progress) => {
|
||||
this.broadcast('execution:progress', progress);
|
||||
});
|
||||
}
|
||||
|
||||
private setupTimedRespawn(sessionId: string, durationMinutes: number): void {
|
||||
// Clear existing timer if any
|
||||
const existing = this.respawnTimers.get(sessionId);
|
||||
|
||||
Reference in New Issue
Block a user