无头链路能跑审批的修改/退回,不再只能投 approve

`agent-swarm-test-chat.mjs --plan` 的自动审批把动作写死成 approve,原注释让人
「要跑那两条分支就手工调 --plan-gdd-decide」。但那条路走不通:decide 要求 plan
根 run 仍是当前唯一 active 的,而它恰好是本进程持有的 CLI 子进程——CLI 一死,
再 decide 就是 PLAN_STALE_APPROVAL,实测过。于是 revise/reject 在无头侧没有任何
入口,e43133875 / 9e2648893 / b27ffce9b 三条修复都只能靠单测。

加 AGC_PLAN_GDD_DECISION + AGC_PLAN_GDD_COMMENT 两个环境变量,默认仍是 approve,
既有调用行为一字不变。原注释那条约束保留并落成断言:非 approve 时缺意见直接抛,
不给机器编一段的机会——意见必须由跑的人自己给。`runCapturedCargo` 顺带支持 stdin,
决定报告从写死的「已批准」改成打印实际动作。

用它跑通了一条完整真链路(两轮澄清 → 出稿 → 修改 → 返工 → v2):
- session successor 与 gdd.v2.json 同一秒落盘,phase=awaiting_gdd_approval、
  latestSubmittedRef=v2、activeRunId 已清空,不再 PLAN_SESSION_RECOVERY_REQUIRED
- 意见原文进了 Supervisor 会话,也进了返工委派正文
- 标题「脉冲避航:微型核心危局」→「エネルギー・ランナー」,意见是「改成日文」

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-26 09:04:30 +00:00
parent b27ffce9b4
commit 92e571e1dc
@@ -143,7 +143,12 @@ export const usage = `用法:
--plan 走「做方案」立项策划入口,不做游戏,不做产物验收和试玩
--timeout-minutes <分钟> 设置本次执行期限;自动任务默认 50 分钟,--plan 默认 6 分钟,手工模式默认不限时
--dry-run 只检查目录发现和项目准备,不启动 LLM
-h, --help 显示帮助`;
-h, --help 显示帮助
环境变量:
AGC_PLAN_GDD_DECISION 审批卡自动应答动作,默认 approverevise/reject 必须
同时用 AGC_PLAN_GDD_COMMENT 给出真实修改意见
AGC_PLAN_GDD_COMMENT revise/reject 的意见原文`;
function readOptionValue(args, index, option) {
const value = args[index + 1]?.trim();
@@ -911,12 +916,15 @@ export function parseSettledSwarmTurnReport(output) {
async function runCapturedCargo(
cliArguments,
setActiveChild,
{ timeoutMs = null, label = 'Cargo 子命令' } = {},
{ timeoutMs = null, label = 'Cargo 子命令', stdin = null } = {},
) {
const child = spawnChild(cargoCommand, buildCargoCliArguments(cliArguments), {
stdio: ['ignore', 'pipe', 'pipe'],
stdio: [stdin === null ? 'ignore' : 'pipe', 'pipe', 'pipe'],
});
setActiveChild(child);
if (stdin !== null) {
child.stdin.end(stdin);
}
let stdout = '';
let stderr = '';
child.stdout.setEncoding('utf8');
@@ -1007,7 +1015,9 @@ async function runTaskCargo(
let planGddApprovalPromise = null;
const startPlanGddApproval = () => {
planGddApprovalStarted = true;
console.log('[自动审批] 检测到 Fast GDD 审批位,正在提交 approve');
console.log(
`[自动审批] 检测到 Fast GDD 审批位,正在提交 ${resolvePlanGddAutoDecision().action}`,
);
planGddApprovalPromise = onPlanGddApprovalWait()
.then((value) => {
planGddApproval = value;
@@ -1842,13 +1852,30 @@ export function parsePlanGddDecisionOutput(output) {
);
}
// 审批卡是这条链路唯一的人类判据,所以自动应答只投 approve,且只在投影确实有一张
// 待决定审批时出手。revise/reject 需要一段真实的修改意见,让机器编一段等于把判据
// 换成噪声;要跑那两条分支就手工调 --plan-gdd-decide。
// 审批卡是这条链路唯一的人类判据,所以自动应答默认只投 approve,且只在投影确实有
// 一张待决定审批时出手。revise/reject 需要一段真实的修改意见,让机器编一段等于把
// 判据换成噪声——所以那两条分支只在跑的人自己用 AGC_PLAN_GDD_COMMENT 给出意见时
// 才走。手工调 --plan-gdd-decide 也能达到同样效果,但那要求 plan 根 run 仍然活着,
// 而它恰好是本进程持有的 CLI 子进程。
export function planGddAutoApprovalIsPending(state) {
return Boolean(state?.pendingApproval);
}
export function resolvePlanGddAutoDecision(env = process.env) {
const action = (env.AGC_PLAN_GDD_DECISION ?? 'approve').trim();
if (!['approve', 'revise', 'reject'].includes(action)) {
throw new Error('AGC_PLAN_GDD_DECISION 只能是 approve / revise / reject');
}
const comment = (env.AGC_PLAN_GDD_COMMENT ?? '').trim();
if (action === 'approve') return { action, comment: null };
if (!comment) {
throw new Error(
`${action} 必须同时设 AGC_PLAN_GDD_COMMENT 提供真实修改意见`,
);
}
return { action, comment };
}
async function settlePlanGddApproval(
projectPath,
runtimeConfigPath,
@@ -1875,18 +1902,21 @@ async function settlePlanGddApproval(
if (!planGddAutoApprovalIsPending(before)) {
return { decided: false, state: before };
}
const { action, comment } = resolvePlanGddAutoDecision();
const decision = await runCapturedCargo(
[
'--config-dir',
runtimeConfigPath,
'--plan-gdd-decide',
projectPath,
'approve',
action,
...(comment === null ? [] : ['--stdin']),
],
setActiveChild,
{
timeoutMs: planGddApprovalTimeoutMs,
label: 'Fast GDD 审批决定',
stdin: comment,
},
);
if (decision.code !== 0 || decision.signal) {
@@ -1926,7 +1956,7 @@ async function reportPlanGddApproval(approval) {
return;
}
console.log(
` [已批准] outcome=${approval.receipt.outcome} v${approval.receipt.decisionRef.version} 投影状态=${state.state}`,
` [已决定 ${approval.receipt.decisionRef.action}] outcome=${approval.receipt.outcome} v${approval.receipt.decisionRef.version} 投影状态=${state.state}`,
);
if (approval.recovered) {
console.log(