feat(P04): pipeline stage delegation — EXECUTE=3 agents, TEST=tester, VERIFY=verifier, COMPLETE=doc-writer+ship
---ci--- phase: 4 milestone: v1.0 plan: 04 task: PIPE-01-04 status: execute ---/ci---
This commit is contained in:
+96
-18
@@ -44,12 +44,13 @@ export class OrchestratorAgent extends BaseAgent {
|
|||||||
private phaseResults: PhaseResult[] = [];
|
private phaseResults: PhaseResult[] = [];
|
||||||
private totalPhases: number = 1;
|
private totalPhases: number = 1;
|
||||||
|
|
||||||
private static readonly STAGE_AGENT_MAP: Partial<Record<PipelineStage, AgentName>> = {
|
private static readonly STAGE_AGENT_MAP: Partial<Record<PipelineStage, AgentName[]>> = {
|
||||||
research: "researcher",
|
research: ["researcher"],
|
||||||
plan: "planner",
|
plan: ["planner"],
|
||||||
execute: "executor",
|
execute: ["executor", "code-reviewer", "security-auditor"],
|
||||||
test: "tester",
|
test: ["tester"],
|
||||||
verify: "verifier",
|
verify: ["verifier"],
|
||||||
|
complete: ["doc-writer"],
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(config?: CIAgentConfig) {
|
constructor(config?: CIAgentConfig) {
|
||||||
@@ -331,29 +332,82 @@ export class OrchestratorAgent extends BaseAgent {
|
|||||||
context: AgentContext
|
context: AgentContext
|
||||||
): Promise<PhaseResult> {
|
): Promise<PhaseResult> {
|
||||||
const stageStart = Date.now();
|
const stageStart = Date.now();
|
||||||
const agentName = OrchestratorAgent.STAGE_AGENT_MAP[stage];
|
const agentNames = OrchestratorAgent.STAGE_AGENT_MAP[stage];
|
||||||
|
|
||||||
if (agentName && context.backend) {
|
if (agentNames && agentNames.length > 0 && context.backend) {
|
||||||
this.log(`Delegating ${stage} to ${agentName} agent via backend...`);
|
this.log(`Delegating ${stage} to ${agentNames.join(", ")} agent(s) via backend...`);
|
||||||
try {
|
try {
|
||||||
const agent = getAgent(agentName);
|
let primaryResult: AgentResult | null = null;
|
||||||
const gitContext = this.buildGitAgentContext(context);
|
const allArtifacts: string[] = [];
|
||||||
const result = await agent.execute(gitContext);
|
let totalDecisions = 0;
|
||||||
|
let totalEscalations = 0;
|
||||||
|
let lastError: string | undefined;
|
||||||
|
|
||||||
|
for (let i = 0; i < agentNames.length; i++) {
|
||||||
|
const agentName = agentNames[i];
|
||||||
|
const agent = getAgent(agentName);
|
||||||
|
const gitContext = this.buildGitAgentContext(context);
|
||||||
|
|
||||||
|
if (i === 0) {
|
||||||
|
const result = await agent.execute(gitContext);
|
||||||
|
primaryResult = result;
|
||||||
|
if (Array.isArray(result.artifacts_created)) {
|
||||||
|
allArtifacts.push(...result.artifacts_created);
|
||||||
|
}
|
||||||
|
totalDecisions += result.decisions;
|
||||||
|
totalEscalations += result.escalations;
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
this.warn(`Primary agent ${agentName} failed for ${stage}`);
|
||||||
|
return {
|
||||||
|
phase: this.pipelineState!.current_phase,
|
||||||
|
stage,
|
||||||
|
success: false,
|
||||||
|
artifacts_created: allArtifacts,
|
||||||
|
decisions_made: totalDecisions,
|
||||||
|
escalations_raised: totalEscalations,
|
||||||
|
duration_ms: Date.now() - stageStart,
|
||||||
|
error: result.error || `Primary agent ${agentName} failed`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const reviewContext: AgentContext = {
|
||||||
|
...gitContext,
|
||||||
|
specification: `${context.specification}\n\nPrimary agent (${agentNames[0]}) completed. Review context:\n- Success: ${primaryResult!.success}\n- Output: ${primaryResult!.output}\n- Artifacts: ${Array.isArray(primaryResult!.artifacts_created) ? primaryResult!.artifacts_created.join(", ") : String(primaryResult!.artifacts_created)}`,
|
||||||
|
};
|
||||||
|
const result = await agent.execute(reviewContext);
|
||||||
|
if (Array.isArray(result.artifacts_created)) {
|
||||||
|
allArtifacts.push(...result.artifacts_created);
|
||||||
|
}
|
||||||
|
totalDecisions += result.decisions;
|
||||||
|
totalEscalations += result.escalations;
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
this.warn(`Review agent ${agentName} reported issues for ${stage}: ${result.error || "unspecified"}`);
|
||||||
|
lastError = result.error;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.warn(`Review agent ${agentName} failed for ${stage}: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phase: this.pipelineState!.current_phase,
|
phase: this.pipelineState!.current_phase,
|
||||||
stage,
|
stage,
|
||||||
success: result.success,
|
success: primaryResult?.success ?? false,
|
||||||
artifacts_created: Array.isArray(result.artifacts_created) ? result.artifacts_created : [],
|
artifacts_created: allArtifacts,
|
||||||
decisions_made: result.decisions,
|
decisions_made: totalDecisions,
|
||||||
escalations_raised: result.escalations,
|
escalations_raised: totalEscalations,
|
||||||
duration_ms: Date.now() - stageStart,
|
duration_ms: Date.now() - stageStart,
|
||||||
error: result.error,
|
error: lastError,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof BackendUnavailableError) {
|
if (err instanceof BackendUnavailableError) {
|
||||||
this.warn(`Backend unavailable for ${stage}, falling back to mechanical execution`);
|
this.warn(`Backend unavailable for ${stage}, falling back to mechanical execution`);
|
||||||
} else {
|
} else {
|
||||||
this.warn(`Agent ${agentName} failed for ${stage}: ${err instanceof Error ? err.message : String(err)}`);
|
this.warn(`Agents failed for ${stage}: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -609,6 +663,30 @@ export class OrchestratorAgent extends BaseAgent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const versionTag = `${this.currentMilestone}-P${String(this.pipelineState!.current_phase).padStart(2, "0")}`;
|
||||||
|
try {
|
||||||
|
execSync(`git tag "${versionTag}"`, {
|
||||||
|
cwd: context.project_path,
|
||||||
|
stdio: "pipe",
|
||||||
|
});
|
||||||
|
this.log(`Created version tag: ${versionTag}`);
|
||||||
|
artifactsCreated.push(`tag:${versionTag}`);
|
||||||
|
} catch (err) {
|
||||||
|
this.warn(`Version tag creation failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.config.git.auto_push && this.gitContext!.isGitRepo()) {
|
||||||
|
try {
|
||||||
|
execSync(`git push origin ${versionTag}`, {
|
||||||
|
cwd: context.project_path,
|
||||||
|
stdio: "pipe",
|
||||||
|
});
|
||||||
|
this.log(`Pushed version tag: ${versionTag}`);
|
||||||
|
} catch (err) {
|
||||||
|
this.warn(`Version tag push failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user