feat: per-role agent config + phased planner/coder in solve-issue template

- SolveIssueRolesConfig.agents allows per-role AgentFn overrides
- PlannerMeta now outputs phases (name, description, acceptance)
- CoderMeta reports completedPhase, works one phase at a time
- Moderator routes coder→coder until all phases done, then reviewer

小橘 <xiaoju@shazhou.work>
This commit is contained in:
2026-05-06 11:35:45 +00:00
parent c7b0beb6be
commit 45bb5af99a
4 changed files with 141 additions and 27 deletions
@@ -1,8 +1,30 @@
import type { Moderator } from "@uncaged/workflow";
import type { Moderator, ThreadContext } from "@uncaged/workflow";
import { END } from "@uncaged/workflow";
import type { SolveIssueMeta } from "./roles.js";
function nextAfterCoder(
ctx: ThreadContext<SolveIssueMeta>,
maxRounds: number,
): (keyof SolveIssueMeta & string) | typeof END {
const plannerStep = ctx.steps.find((s) => s.role === "planner");
if (plannerStep === undefined) {
return "reviewer";
}
const phases = plannerStep.meta.phases;
const completedPhases = new Set(
ctx.steps.filter((s) => s.role === "coder").map((s) => s.meta.completedPhase),
);
const allDone = phases.every((p) => completedPhases.has(p.name));
if (allDone) {
return "reviewer";
}
if (ctx.steps.length < maxRounds - 1) {
return "coder";
}
return END;
}
export const solveIssueModerator: Moderator<SolveIssueMeta> = (ctx) => {
const maxRounds = ctx.start.meta.maxRounds;
@@ -17,7 +39,7 @@ export const solveIssueModerator: Moderator<SolveIssueMeta> = (ctx) => {
}
if (last.role === "coder") {
return "reviewer";
return nextAfterCoder(ctx, maxRounds);
}
if (last.role === "reviewer") {