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
This commit is contained in:
Lukas May
2026-02-01 11:43:55 +01:00
parent fe3fed077e
commit 8754cdea98

View File

@@ -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<void> {
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