mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 08:29:42 +02:00
refactor: pass 3 — extract helpers, split long functions, deduplicate patterns
Backend: - subagent-watcher: split 176L processEntry() into 5 focused methods; extract _resolveDescription() deduplicating 3 call sites for description acquisition - bash-tool-parser: split 152L processCleanLine() into 4 handlers; extract _createActiveTool() factory and _scheduleAutoRemove() helper - session: extract _setupOrAttachMuxSession() deduplicating ~80L between startInteractive/startShell; extract _handleTerminalOutput() - respawn-controller: split 180L handleTerminalData() into 3 detection layers; data-driven validation loop replacing 9 individual calls - plan-orchestrator: extract _extractJsonFromResponse(), _emitAgentFailure(), _formatResearchSection() helpers - orchestrator-loop: extract _finalizeTask() unifying task completion/failure; _clearTimer() utility for correct clearInterval/clearTimeout dispatch - ralph-status-parser: config-driven FIELD_PARSERS[] replacing 8 near-identical field-matching blocks; split updateCircuitBreaker() into focused handlers - state-store: extract _mergeWithInitialState() and _resetCircuitBreaker() Frontend: - app.js: add _notifySession() helper used by 18 call sites across 5 modules - panels-ui.js: extract _addActivityEntry() replacing 4 duplicate blocks - settings-ui.js: extract _updateTunnelUrlRow() deduplicating 2 blocks - ralph-panel.js, respawn-ui.js: convert to _notifySession() Routes: - route-helpers: add toggleService() helper - system-routes: use toggleService() for watcher toggles; extract collectActiveTokens() - orchestrator-routes: data-driven EVENT_MAP replacing 10 identical listeners Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -39,43 +39,37 @@ export function registerOrchestratorRoutes(app: FastifyInstance, ctx: Orchestrat
|
||||
return loop;
|
||||
}
|
||||
|
||||
const EVENT_MAP: [string, (typeof SseEvent)[keyof typeof SseEvent], string[]][] = [
|
||||
['stateChanged', SseEvent.OrchestratorStateChanged, ['state', 'prevState']],
|
||||
['planProgress', SseEvent.OrchestratorPlanProgress, ['phase', 'detail']],
|
||||
['planReady', SseEvent.OrchestratorPlanReady, ['plan']],
|
||||
['phaseStarted', SseEvent.OrchestratorPhaseStarted, ['phase']],
|
||||
['phaseCompleted', SseEvent.OrchestratorPhaseCompleted, ['phase']],
|
||||
['phaseFailed', SseEvent.OrchestratorPhaseFailed, ['phase', 'reason']],
|
||||
['taskAssigned', SseEvent.OrchestratorTaskAssigned, ['task', 'sessionId']],
|
||||
['taskCompleted', SseEvent.OrchestratorTaskCompleted, ['task']],
|
||||
['taskFailed', SseEvent.OrchestratorTaskFailed, ['task', 'error']],
|
||||
['completed', SseEvent.OrchestratorCompleted, ['stats']],
|
||||
];
|
||||
|
||||
let forwardingLoop: import('../../orchestrator-loop.js').OrchestratorLoop | null = null;
|
||||
function setupEventForwarding(loop: import('../../orchestrator-loop.js').OrchestratorLoop) {
|
||||
if (forwardingLoop === loop) return; // Already attached to this loop instance
|
||||
forwardingLoop = loop;
|
||||
loop.on('stateChanged', (state, prevState) => {
|
||||
ctx.broadcast(SseEvent.OrchestratorStateChanged, { state, prevState });
|
||||
});
|
||||
loop.on('planProgress', (phase, detail) => {
|
||||
ctx.broadcast(SseEvent.OrchestratorPlanProgress, { phase, detail });
|
||||
});
|
||||
loop.on('planReady', (plan) => {
|
||||
ctx.broadcast(SseEvent.OrchestratorPlanReady, { plan });
|
||||
});
|
||||
loop.on('phaseStarted', (phase) => {
|
||||
ctx.broadcast(SseEvent.OrchestratorPhaseStarted, { phase });
|
||||
});
|
||||
loop.on('phaseCompleted', (phase) => {
|
||||
ctx.broadcast(SseEvent.OrchestratorPhaseCompleted, { phase });
|
||||
});
|
||||
loop.on('phaseFailed', (phase, reason) => {
|
||||
ctx.broadcast(SseEvent.OrchestratorPhaseFailed, { phase, reason });
|
||||
});
|
||||
for (const [event, sseEvent, argNames] of EVENT_MAP) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
loop.on(event, (...args: any[]) => {
|
||||
const payload: Record<string, unknown> = {};
|
||||
argNames.forEach((name, i) => {
|
||||
payload[name] = args[i];
|
||||
});
|
||||
ctx.broadcast(sseEvent, payload);
|
||||
});
|
||||
}
|
||||
// Special cases with non-trivial payload transforms
|
||||
loop.on('verificationResult', (phase, result) => {
|
||||
ctx.broadcast(SseEvent.OrchestratorVerification, { phaseId: phase.id, result });
|
||||
});
|
||||
loop.on('taskAssigned', (task, sessionId) => {
|
||||
ctx.broadcast(SseEvent.OrchestratorTaskAssigned, { task, sessionId });
|
||||
});
|
||||
loop.on('taskCompleted', (task) => {
|
||||
ctx.broadcast(SseEvent.OrchestratorTaskCompleted, { task });
|
||||
});
|
||||
loop.on('taskFailed', (task, error) => {
|
||||
ctx.broadcast(SseEvent.OrchestratorTaskFailed, { task, error });
|
||||
});
|
||||
loop.on('completed', (stats) => {
|
||||
ctx.broadcast(SseEvent.OrchestratorCompleted, { stats });
|
||||
});
|
||||
loop.on('error', (error) => {
|
||||
ctx.broadcast(SseEvent.OrchestratorError, { error: error.message });
|
||||
});
|
||||
|
||||
@@ -24,7 +24,14 @@ import {
|
||||
import { subagentWatcher } from '../../subagent-watcher.js';
|
||||
import { imageWatcher } from '../../image-watcher.js';
|
||||
import { getLifecycleLog } from '../../session-lifecycle-log.js';
|
||||
import { findSessionOrFail, formatUptime, parseBody, readJsonConfig, SETTINGS_PATH } from '../route-helpers.js';
|
||||
import {
|
||||
findSessionOrFail,
|
||||
formatUptime,
|
||||
parseBody,
|
||||
readJsonConfig,
|
||||
toggleService,
|
||||
SETTINGS_PATH,
|
||||
} from '../route-helpers.js';
|
||||
import { SseEvent } from '../sse-events.js';
|
||||
import type { SessionPort, EventPort, ConfigPort, InfraPort, AuthPort } from '../ports/index.js';
|
||||
import { AUTH_COOKIE_NAME } from '../middleware/auth.js';
|
||||
@@ -281,15 +288,20 @@ export function registerSystemRoutes(
|
||||
|
||||
// ========== Stats ==========
|
||||
|
||||
app.get('/api/stats', async () => {
|
||||
const activeSessionTokens: Record<string, { inputTokens?: number; outputTokens?: number; totalCost?: number }> = {};
|
||||
function collectActiveTokens(): Record<string, { inputTokens?: number; outputTokens?: number; totalCost?: number }> {
|
||||
const tokens: Record<string, { inputTokens?: number; outputTokens?: number; totalCost?: number }> = {};
|
||||
for (const [sessionId, session] of ctx.sessions) {
|
||||
activeSessionTokens[sessionId] = {
|
||||
tokens[sessionId] = {
|
||||
inputTokens: session.inputTokens,
|
||||
outputTokens: session.outputTokens,
|
||||
totalCost: session.totalCost,
|
||||
};
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
app.get('/api/stats', async () => {
|
||||
const activeSessionTokens = collectActiveTokens();
|
||||
return {
|
||||
success: true,
|
||||
stats: ctx.store.getAggregateStats(activeSessionTokens),
|
||||
@@ -298,14 +310,7 @@ export function registerSystemRoutes(
|
||||
});
|
||||
|
||||
app.get('/api/token-stats', async () => {
|
||||
const activeSessionTokens: Record<string, { inputTokens?: number; outputTokens?: number; totalCost?: number }> = {};
|
||||
for (const [sessionId, session] of ctx.sessions) {
|
||||
activeSessionTokens[sessionId] = {
|
||||
inputTokens: session.inputTokens,
|
||||
outputTokens: session.outputTokens,
|
||||
totalCost: session.totalCost,
|
||||
};
|
||||
}
|
||||
const activeSessionTokens = collectActiveTokens();
|
||||
return {
|
||||
success: true,
|
||||
daily: ctx.store.getDailyStats(30),
|
||||
@@ -414,30 +419,17 @@ export function registerSystemRoutes(
|
||||
await fs.writeFile(SETTINGS_PATH, JSON.stringify(merged, null, 2));
|
||||
|
||||
// Handle subagent tracking toggle dynamically
|
||||
const subagentEnabled = settings.subagentTrackingEnabled ?? true;
|
||||
if (subagentEnabled && !subagentWatcher.isRunning()) {
|
||||
subagentWatcher.start();
|
||||
console.log('Subagent watcher started via settings change');
|
||||
} else if (!subagentEnabled && subagentWatcher.isRunning()) {
|
||||
subagentWatcher.stop();
|
||||
console.log('Subagent watcher stopped via settings change');
|
||||
}
|
||||
toggleService((settings.subagentTrackingEnabled as boolean) ?? true, subagentWatcher, 'Subagent watcher');
|
||||
|
||||
// Handle image watcher toggle dynamically
|
||||
const imageWatcherEnabled = settings.imageWatcherEnabled ?? false;
|
||||
if (imageWatcherEnabled && !imageWatcher.isRunning()) {
|
||||
imageWatcher.start();
|
||||
toggleService((settings.imageWatcherEnabled as boolean) ?? false, imageWatcher, 'Image watcher', () => {
|
||||
// Re-watch all active sessions that have image watcher enabled
|
||||
for (const session of ctx.sessions.values()) {
|
||||
if (session.imageWatcherEnabled) {
|
||||
imageWatcher.watchSession(session.id, session.workingDir);
|
||||
}
|
||||
}
|
||||
console.log('Image watcher started via settings change');
|
||||
} else if (!imageWatcherEnabled && imageWatcher.isRunning()) {
|
||||
imageWatcher.stop();
|
||||
console.log('Image watcher stopped via settings change');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle tunnel toggle dynamically
|
||||
if ('tunnelEnabled' in settings) {
|
||||
|
||||
Reference in New Issue
Block a user