fix: gate main-agent self-stop on auto-next obligation

This commit is contained in:
Eve
2026-04-24 19:34:15 +08:00
parent 1fe6474009
commit a77141f021
3 changed files with 8 additions and 0 deletions

View File

@@ -484,6 +484,8 @@ async function buildApprovedPlanContinuityBlock(workspaceDir: string, wrapperRes
if (result.reason === 'missing_auto_next_dispatch') {
lines.push("- HARD_GATE: Do not stop at this completed-task boundary.");
lines.push("- HARD_GATE: Auto-dispatch the next task in the same approved plan, unless waiting_user, blocked, pending_verification, or high-risk stop applies.");
lines.push("- HARD_GATE: Do not hand control back to the user with an ordinary progress update while auto-next is still obligatory.");
lines.push("- HARD_GATE: If you cannot prove the next dispatch, convert this into an explicit continuity failure instead of a normal status report.");
} else {
lines.push("- HARD_GATE: Route back to continuity failure until a real next dispatch receipt exists, unless closure state is waiting_user, blocked, or pending_verification.");
}

View File

@@ -110,6 +110,8 @@ export function buildContinuityGateBlock(result, options = {}) {
if (result.reason === 'missing_auto_next_dispatch') {
lines.push('- HARD_GATE: Do not stop at this completed-task boundary.');
lines.push(`- HARD_GATE: Auto-dispatch the next task in the same approved plan, unless ${terminalStates.join(', ')}, or high-risk stop applies.`);
lines.push('- HARD_GATE: Do not hand control back to the user with an ordinary progress update while auto-next is still obligatory.');
lines.push('- HARD_GATE: If you cannot prove the next dispatch, convert this into an explicit continuity failure instead of a normal status report.');
} else {
lines.push(`- HARD_GATE: Route back to continuity failure until a real next dispatch receipt exists, unless closure state is ${terminalStates.join(', ')}.`);
}

View File

@@ -65,11 +65,13 @@ async function prepareTempWorkspace() {
[path.join(repoRoot, 'SOUL.md'), path.join(tempWorkspace, 'SOUL.md')],
[path.join(repoRoot, 'plugins', 'continuity', 'src', 'index.mjs'), path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'index.mjs')],
[path.join(repoRoot, 'plugins', 'continuity', 'src', 'adapters', 'force-recall.mjs'), path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'adapters', 'force-recall.mjs')],
[path.join(repoRoot, 'plugins', 'continuity', 'src', 'adapters', 'generic-preflight.mjs'), path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'adapters', 'generic-preflight.mjs')],
[path.join(repoRoot, 'plugins', 'continuity', 'src', 'config', 'defaults.mjs'), path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'config', 'defaults.mjs')],
[path.join(repoRoot, 'plugins', 'continuity', 'src', 'config', 'schema.mjs'), path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'config', 'schema.mjs')],
[path.join(repoRoot, 'plugins', 'continuity', 'src', 'continuity', 'evaluator.mjs'), path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'continuity', 'evaluator.mjs')],
[path.join(repoRoot, 'plugins', 'continuity', 'src', 'continuity', 'receipt-validator.mjs'), path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'continuity', 'receipt-validator.mjs')],
[path.join(repoRoot, 'plugins', 'continuity', 'src', 'continuity', 'receipt-store.mjs'), path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'continuity', 'receipt-store.mjs')],
[path.join(repoRoot, 'plugins', 'continuity', 'src', 'continuity', 'engine.mjs'), path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'continuity', 'engine.mjs')],
];
for (const [src, dest] of copies) {
@@ -370,6 +372,8 @@ async function main() {
assert.match(passInjected, /Do not stop at this completed-task boundary/, 'hook pass-path should explicitly forbid stopping at the completed-task boundary');
assert.match(passInjected, /Auto-dispatch the next task in the same approved plan, unless waiting_user, blocked, pending_verification, or high-risk stop applies/, 'hook pass-path should explain the auto-next obligation exceptions');
assert.match(passInjected, /Do not stop at this completed-task boundary/, 'hook pass-path should hard-gate the completed-task boundary');
assert.match(passInjected, /Do not hand control back to the user with an ordinary progress update while auto-next is still obligatory/, 'hook pass-path should forbid ordinary progress handoff when auto-next obligation is active');
assert.match(passInjected, /If you cannot prove the next dispatch, convert this into an explicit continuity failure instead of a normal status report/, 'hook pass-path should require failure conversion instead of normal progress reporting');
assert.doesNotMatch(passInjected, /\[APPROVED_PLAN_CONTINUITY_GATE\][\s\S]*status=pass/, 'hook pass-path should not let approved-plan continuity pass on dry-run dispatch alone');
const failInjected = await withPatchedWrapper(buildWrapperScript({