feat(planner): 编排器把 analyzing/speccing 也当可执行——派 planner run 自动拆解/写方案
修复"hard/medium 任务建后永远停在 analyzing/speccing"的缺口:编排器现在对 analyzing(hard) 派 planner-decompose、对 speccing(medium) 派 planner-spec(auto-approved 下;auto-easy 仍只执行 easy), planner 只读跑 CC(无 worktree/无写)产出 → daemon 落库 + 自动提交到 plan_review/spec_review 闸(仍人审)。 - protocol: JobSpec.runKind + spec-result/decompose-result 两型 outbox + DecomposeResult - runner: runPlanner(只读 Read/Glob/Grep 跑在 repo,opus 档)+ buildPlannerPrompt - pipeline: runKind 分支 + parseDecompose(取末尾 fenced JSON,非法→failed) - orchestrator: claimable 纳入 analyzing/speccing + 排除在途;并发/in-flight 从 countExecuting 泛化为 inflightTaskIds(有 started run 的任务,executor+planner 通用);claimOne 按状态分 executor/planner (planner 不转状态);reap 泛化(死 planner→failPlanAttempt) - ingest: ingestAll 覆盖所有 started run;spec-result→setSpec+spec_review;decompose-result→setPlan+建子任务+plan_review; failed 按 kind 分流(planner→failPlanAttempt 退避留态、超限→needs_attention) - store: inflightTaskIds / liveRunsWithTask / failPlanAttempt;reconcile 泛化到所有 started run - status: analyzing/speccing 加 →needs_attention(planner 失败超限升级) - models: planner 角色(opus);status: RunKind 已含 planner - 测试 +12(pipeline 5 / ingest 3 / orchestrator 4),194 全绿 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -94,3 +94,69 @@ export async function runTask(task: Task, project: Project, worktree: WorktreeIn
|
||||
}
|
||||
return { ok: true, transcriptRef: cc.transcriptRef, sessionId: cc.sessionId, finalText: cc.finalText || null, modelUsed: cc.modelUsed };
|
||||
}
|
||||
|
||||
// ───────────────────────── planner(拆解 Hard / 写方案 Medium)─────────────────────────
|
||||
|
||||
export type PlanKind = 'spec' | 'decompose';
|
||||
|
||||
export interface PlannerResult {
|
||||
ok: boolean;
|
||||
transcriptRef: string | null;
|
||||
sessionId: string | null;
|
||||
finalText: string | null; // CC 的最终文本:spec=方案正文;decompose=分析 + 末尾 fenced JSON
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/** 测试注入点(pipeline 的 planner 分支用) */
|
||||
export type PlannerFn = (task: Task, project: Project, kind: PlanKind, runId: string) => Promise<PlannerResult>;
|
||||
|
||||
/** planner 提示词:只读代码库,spec=写改动方案;decompose=拆子任务并在末尾输出 fenced JSON。 */
|
||||
export function buildPlannerPrompt(task: Task, kind: PlanKind): string {
|
||||
const head = [`# 任务:${task.title}`, `任务 ID:${task.id}`];
|
||||
if (kind === 'spec') {
|
||||
if (task.spec) head.push('', '## 现有方案草稿(可改进/替换)', task.spec);
|
||||
return [
|
||||
...head, '',
|
||||
'## 你的角色:方案作者(只读,不改任何文件)',
|
||||
'只读这个代码库,为上述任务写一份「具体改动方案」。不要编辑文件、不要执行有副作用的命令。',
|
||||
'## 输出(markdown 正文)',
|
||||
'## 改动(要改哪些文件/模块、怎么改)',
|
||||
'## 为什么(这么做的理由、取舍)',
|
||||
'## 验收(怎么算完成、怎么验证)',
|
||||
'这份方案将提交给人审;通过后才会有执行 agent 按它改代码。直接输出方案正文即可。',
|
||||
].join('\n');
|
||||
}
|
||||
if (task.plan) head.push('', '## 现有分析草稿(可改进/替换)', task.plan);
|
||||
return [
|
||||
...head, '',
|
||||
'## 你的角色:任务拆解者(只读,不改任何文件)',
|
||||
'只读这个代码库,分析上述(复杂)任务,拆成 2–6 个更小、可独立交付的子任务。不要编辑文件、不要执行有副作用的命令。',
|
||||
'每个子任务标注复杂度:easy(单文件机械改动/无设计)、medium(需方案、跨几处)、hard(仍需进一步拆解)。',
|
||||
'## 输出(重要)',
|
||||
'先写一段分析与拆解理由;然后在【最后】输出唯一一个 ```json 代码块,严格格式:',
|
||||
'```json',
|
||||
'{"plan":"一句话分析与拆解理由","subtasks":[{"title":"子任务标题","complexity":"easy"}]}',
|
||||
'```',
|
||||
'subtasks 至少 1 个;complexity 只能是 easy|medium|hard;除这个 JSON 块外不要再写其它 ``` 代码块。',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 起 headless CC 跑一次 planner:**只读**跑在 project.repoPath(不建 worktree、不改文件)。
|
||||
* 模型用 planner 角色(默认 opus)。不抛错,失败折叠进 { ok:false, error }。
|
||||
*/
|
||||
export async function runPlanner(task: Task, project: Project, kind: PlanKind, runId: string): Promise<PlannerResult> {
|
||||
const model = pickModel(task, project, 'planner');
|
||||
const timeoutMs = project.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
||||
const cc = await runClaude({
|
||||
prompt: buildPlannerPrompt(task, kind),
|
||||
cwd: project.repoPath, // 只读跑在主仓(planner 不改文件,无需 worktree)
|
||||
model,
|
||||
runId,
|
||||
maxTurns: 60,
|
||||
timeoutMs,
|
||||
permissionMode: 'acceptEdits', // planner 只读,不会触发编辑;保持工具流不被 prompt 卡住
|
||||
allowedTools: ['Read', 'Glob', 'Grep'], // 纯只读:无 Edit/Write/Bash,零副作用
|
||||
});
|
||||
return { ok: cc.ok, transcriptRef: cc.transcriptRef, sessionId: cc.sessionId, finalText: cc.finalText || null, error: cc.error };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user