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
+32
View File
@@ -15,6 +15,31 @@ export interface IngestLogger {
error(msg: string): void;
}
/** 自动放行 plan 的"改动面小"阈值:子任务数不超过此值才允许跳过人审。 */
const AUTO_APPROVE_PLAN_MAX_SUBTASKS = 5;
/**
* 是否对该拆解结果自动放行(跳过 plan_review):
* ① 项目 autonomy=auto-approved 且 autoApprovePlan 开关打开;
* ② 子任务全部成功建出(created===列表长度,无建库失败);
* ③ 全 easy(无 medium/hard,即无需进一步规划);
* ④ 改动面小(子任务数 ≤ AUTO_APPROVE_PLAN_MAX_SUBTASKS)。
* 任一不满足 → 仍走人审。默认关(autoApprovePlan 缺省 false)。
*/
function shouldAutoApprovePlan(
store: Store,
projectId: string,
subtasks: Array<{ complexity: 'easy' | 'medium' | 'hard' }>,
created: number,
): boolean {
const project = store.getProject(projectId);
if (!project) return false;
if (project.autonomy !== 'auto-approved' || !project.autoApprovePlan) return false;
if (subtasks.length === 0 || created !== subtasks.length) return false;
if (subtasks.length > AUTO_APPROVE_PLAN_MAX_SUBTASKS) return false;
return subtasks.every((s) => s.complexity === 'easy');
}
/**
* 摄取单个 run 的新 outbox 记录(seq>run.lastSeq),按序落库。
* run 不存在则忽略(已被删/异常)。每条处理后推进 lastSeq 游标。
@@ -121,6 +146,13 @@ function applyRecord(store: Store, log: IngestLogger, taskId: string, runId: str
claudeSessionId: rec.sessionId ?? undefined,
});
log.info(`task=${taskId} run=${runId} 拆解完成 → plan_review${created} 个子任务)`);
// 自动放行(默认关):auto-approved 项目 + autoApprovePlan 开 + 拆解结果"全 easy 且改动面小"
// → 跳过 plan_review 人审,直接 acceptplan_review→decomposed)。
if (shouldAutoApprovePlan(store, task.projectId, rec.subtasks, created)) {
store.decide(taskId, 'accept', 'auto-approve-plan', null);
log.info(`task=${taskId} 拆解全 easy 且改动面小 → 自动放行(跳过 plan_review`);
}
return;
}