From 8754cdea98a1d6f145ebfbb6d9954268e90a56fc Mon Sep 17 00:00:00 2001 From: Lukas May Date: Sun, 1 Feb 2026 11:43:55 +0100 Subject: [PATCH] feat(12-03): add decompose mode support to ClaudeAgentManager - Import decomposeOutputSchema and decomposeOutputJsonSchema from schema.ts - Update getJsonSchemaForMode() to handle 'decompose' mode - Add handleDecomposeOutput() method following pattern of handleBreakdownOutput() - Update handleAgentCompletion() switch to call handleDecomposeOutput for decompose mode - Handle decompose_complete/questions/unrecoverable_error statuses --- src/agent/manager.ts | 96 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/src/agent/manager.ts b/src/agent/manager.ts index a8c43f4..b25a26e 100644 --- a/src/agent/manager.ts +++ b/src/agent/manager.ts @@ -33,6 +33,8 @@ import { discussOutputJsonSchema, breakdownOutputSchema, breakdownOutputJsonSchema, + decomposeOutputSchema, + decomposeOutputJsonSchema, } from './schema.js'; /** @@ -83,6 +85,8 @@ export class ClaudeAgentManager implements AgentManager { return discussOutputJsonSchema; case 'breakdown': return breakdownOutputJsonSchema; + case 'decompose': + return decomposeOutputJsonSchema; case 'execute': default: return agentOutputJsonSchema; @@ -194,6 +198,9 @@ export class ClaudeAgentManager implements AgentManager { case 'breakdown': await this.handleBreakdownOutput(agent, rawOutput); break; + case 'decompose': + await this.handleDecomposeOutput(agent, rawOutput); + break; case 'execute': default: await this.handleExecuteOutput(agent, rawOutput, cliResult.session_id); @@ -473,6 +480,95 @@ export class ClaudeAgentManager implements AgentManager { } } + /** + * Handle output for decompose mode. + * Outputs tasks array when phase decomposition is complete. + */ + private async handleDecomposeOutput( + agent: { id: string; name: string; taskId: string | null }, + rawOutput: unknown + ): Promise { + const decomposeOutput = decomposeOutputSchema.parse(rawOutput); + const active = this.activeAgents.get(agent.id); + + switch (decomposeOutput.status) { + case 'decompose_complete': { + if (active) { + active.result = { + success: true, + message: `Decompose complete with ${decomposeOutput.tasks.length} tasks`, + }; + } + await this.repository.updateStatus(agent.id, 'idle'); + + if (this.eventBus) { + const event: AgentStoppedEvent = { + type: 'agent:stopped', + timestamp: new Date(), + payload: { + agentId: agent.id, + name: agent.name, + taskId: agent.taskId ?? '', + reason: 'decompose_complete', + }, + }; + this.eventBus.emit(event); + } + break; + } + + case 'questions': { + if (active) { + active.pendingQuestions = { + questions: decomposeOutput.questions, + }; + } + await this.repository.updateStatus(agent.id, 'waiting_for_input'); + + if (this.eventBus) { + const event: AgentWaitingEvent = { + type: 'agent:waiting', + timestamp: new Date(), + payload: { + agentId: agent.id, + name: agent.name, + taskId: agent.taskId ?? '', + sessionId: '', + questions: decomposeOutput.questions, + }, + }; + this.eventBus.emit(event); + } + break; + } + + case 'unrecoverable_error': { + if (active) { + active.result = { + success: false, + message: decomposeOutput.error, + }; + } + await this.repository.updateStatus(agent.id, 'crashed'); + + if (this.eventBus) { + const event: AgentCrashedEvent = { + type: 'agent:crashed', + timestamp: new Date(), + payload: { + agentId: agent.id, + name: agent.name, + taskId: agent.taskId ?? '', + error: decomposeOutput.error, + }, + }; + this.eventBus.emit(event); + } + break; + } + } + } + /** * Handle agent errors - actual crashes (not waiting for input). * With structured output via --json-schema, question status is handled in