feat(agent): 接通 plan 自动放行(autoApprovePlan)+ 项目配置可写

之前 auto_approve_plan/auto_approve_exec/checks 列与 Project 类型已存在,但
PatchProjectInput 不含它们 → 无法启用(dead config)。本次接通:
- PatchProjectInput + patchProject 支持 autoApprovePlan/autoApproveExec/checks
- API PATCH /projects 透传三字段
- ingest decompose-result:auto-approved + autoApprovePlan + 全 easy + 子任务≤5
  → store.decide(accept) 跳过 plan_review 直达 decomposed(默认关)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-24 22:13:18 +08:00
parent ecea1ae9b6
commit 73f80d27c9
107 changed files with 10970 additions and 0 deletions
+49
View File
@@ -255,6 +255,55 @@ test('ingest planner-decomposesetPlan + 建子任务 + 转 plan_review', () =
});
});
test('autoApprovePlanauto-approved + 开关 + 全 easy + 改动面小 → 跳过 plan_review 直接 decomposed', () => {
withTmpDataDir(() => {
const { store, taskId, runId } = setupPlanning('hard');
const pid = store.getTask(taskId)!.projectId;
store.patchProject(pid, { autoApprovePlan: true });
appendOutbox(runId, {
type: 'decompose-result', plan: '拆成两步',
subtasks: [{ title: 'a', complexity: 'easy' }, { title: 'b', complexity: 'easy' }],
transcriptRef: null, sessionId: null,
});
appendOutbox(runId, { type: 'done' });
ingestRun(store, noopLog, runId);
assert.equal(store.getTask(taskId)!.status, 'decomposed', '全 easy 自动放行');
store.close();
});
});
test('autoApprovePlan:开关关 → 仍走 plan_review 人审', () => {
withTmpDataDir(() => {
const { store, taskId, runId } = setupPlanning('hard'); // autoApprovePlan 默认 false
appendOutbox(runId, {
type: 'decompose-result', plan: 'x',
subtasks: [{ title: 'a', complexity: 'easy' }],
transcriptRef: null, sessionId: null,
});
appendOutbox(runId, { type: 'done' });
ingestRun(store, noopLog, runId);
assert.equal(store.getTask(taskId)!.status, 'plan_review', '开关关 → 人审');
store.close();
});
});
test('autoApprovePlan:含 medium 子任务 → 不放行(改动面/复杂度不满足)', () => {
withTmpDataDir(() => {
const { store, taskId, runId } = setupPlanning('hard');
const pid = store.getTask(taskId)!.projectId;
store.patchProject(pid, { autoApprovePlan: true });
appendOutbox(runId, {
type: 'decompose-result', plan: 'x',
subtasks: [{ title: 'a', complexity: 'easy' }, { title: 'b', complexity: 'medium' }],
transcriptRef: null, sessionId: null,
});
appendOutbox(runId, { type: 'done' });
ingestRun(store, noopLog, runId);
assert.equal(store.getTask(taskId)!.status, 'plan_review', '有 medium → 人审');
store.close();
});
});
test('ingest planner failed:走 failPlanAttempt(退避,任务留 speccing,不进 failed', () => {
withTmpDataDir(() => {
const { store, taskId, runId } = setupPlanning('medium');