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
+3
View File
@@ -98,6 +98,9 @@ export function buildServer(opts: ApiOptions): FastifyInstance {
if (b.timeoutMs !== undefined) patch.timeoutMs = Number(b.timeoutMs);
if (b.budgetUsd !== undefined) patch.budgetUsd = b.budgetUsd === null || b.budgetUsd === '' ? null : Number(b.budgetUsd);
if (b.budgetPeriod !== undefined) patch.budgetPeriod = b.budgetPeriod as 'day' | 'month';
if (b.autoApprovePlan !== undefined) patch.autoApprovePlan = Boolean(b.autoApprovePlan);
if (b.autoApproveExec !== undefined) patch.autoApproveExec = Boolean(b.autoApproveExec);
if (b.checks !== undefined) patch.checks = b.checks === null || b.checks === '' ? null : String(b.checks);
return projectOut(store.patchProject(id, patch));
});
+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;
}
+8
View File
@@ -45,6 +45,7 @@ export interface PatchProjectInput {
model?: string | null; status?: 'active' | 'paused'; logo?: string | null;
maxRetries?: number; timeoutMs?: number;
budgetUsd?: number | null; budgetPeriod?: 'day' | 'month';
autoApprovePlan?: boolean; autoApproveExec?: boolean; checks?: string | null;
}
export interface PatchTaskInput {
title?: string; priority?: number; complexity?: Complexity; deps?: string[];
@@ -230,6 +231,13 @@ export class Store {
}
sets.push('budget_period = @budget_period'); args.budget_period = patch.budgetPeriod;
}
if (patch.autoApprovePlan !== undefined) {
sets.push('auto_approve_plan = @auto_approve_plan'); args.auto_approve_plan = patch.autoApprovePlan ? 1 : 0;
}
if (patch.autoApproveExec !== undefined) {
sets.push('auto_approve_exec = @auto_approve_exec'); args.auto_approve_exec = patch.autoApproveExec ? 1 : 0;
}
if (patch.checks !== undefined) { sets.push('checks = @checks'); args.checks = patch.checks; }
if (sets.length > 0) {
this.db.prepare(`UPDATE projects SET ${sets.join(', ')} WHERE id = @id`).run(args);