feat: add execution-layer auto-chain dry-run planner
This commit is contained in:
@@ -11,6 +11,7 @@ const repoRoot = path.resolve(__dirname, '..');
|
||||
const handlerPath = path.join(repoRoot, 'hooks', 'force-recall', 'handler.ts');
|
||||
const wrapperPath = path.join(repoRoot, 'scripts', 'long_task_governor_wrapper.mjs');
|
||||
const gateLockPath = path.join(repoRoot, 'scripts', 'long_task_gate_lock.mjs');
|
||||
const plannerPath = path.join(repoRoot, 'scripts', 'plan_long_task_auto_chain.mjs');
|
||||
|
||||
async function importTsModule(tsPath) {
|
||||
const source = await fs.readFile(tsPath, 'utf8');
|
||||
@@ -55,7 +56,7 @@ function buildWrapperScript(wrapperResult) {
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await Promise.all([fs.access(wrapperPath), fs.access(gateLockPath)]);
|
||||
await Promise.all([fs.access(wrapperPath), fs.access(gateLockPath), fs.access(plannerPath)]);
|
||||
const { default: forceRecall } = await importTsModule(handlerPath);
|
||||
assert.equal(typeof forceRecall, 'function', 'force-recall handler should export default function');
|
||||
|
||||
@@ -74,6 +75,13 @@ async function main() {
|
||||
'handoff.mode=button_path',
|
||||
'[LONG_TASK_GATE_LOCK]',
|
||||
'gateStatus=fail',
|
||||
'[LONG_TASK_AUTO_CHAIN_PLAN]',
|
||||
'plannerStatus=blocked_by_gate',
|
||||
'derivedAction=none',
|
||||
'dispatchMode=no_dispatch',
|
||||
'autoChainAllowed=false',
|
||||
'reason=gateStatus must pass before auto-chain planning can proceed',
|
||||
'requiredEvidence=gateStatus=pass',
|
||||
'requiredEvidence=externalizedCheckpoint',
|
||||
'requiredEvidence=concreteNextAction',
|
||||
'requiredEvidence=buttonPathMode',
|
||||
@@ -201,6 +209,11 @@ async function main() {
|
||||
handoff: { mode: 'direct_reply' },
|
||||
}), async () => runScenario(forceRecall, requestText));
|
||||
assert.match(passInjected, /gateStatus=pass/, 'hook pass-path should pass when wrapper provides concrete progressEvidence');
|
||||
assert.match(passInjected, /\[LONG_TASK_AUTO_CHAIN_PLAN\]/, 'hook pass-path should emit auto-chain plan block');
|
||||
assert.match(passInjected, /plannerStatus=pass/, 'hook pass-path should expose planner pass result');
|
||||
assert.match(passInjected, /derivedAction=dispatch_spec_review/, 'hook pass-path should derive dry-run spec review dispatch');
|
||||
assert.match(passInjected, /dispatchMode=dry_run_dispatch/, 'hook pass-path should stay in dry-run dispatch mode');
|
||||
assert.match(passInjected, /autoChainAllowed=true/, 'hook pass-path should allow auto-chain in dry-run planner output');
|
||||
|
||||
const failInjected = await withPatchedWrapper(buildWrapperScript({
|
||||
classification: 'long_task',
|
||||
@@ -213,6 +226,11 @@ async function main() {
|
||||
handoff: { mode: 'direct_reply' },
|
||||
}), async () => runScenario(forceRecall, requestText));
|
||||
assert.match(failInjected, /gateStatus=fail/, 'hook fail-path should fail when wrapper exposes explicit auto-chain action without dispatch evidence');
|
||||
assert.match(failInjected, /\[LONG_TASK_AUTO_CHAIN_PLAN\]/, 'hook fail-path should emit auto-chain plan block');
|
||||
assert.match(failInjected, /plannerStatus=blocked_by_gate/, 'hook fail-path should report planner blocked by gate');
|
||||
assert.match(failInjected, /derivedAction=none/, 'hook fail-path should not derive a dry-run action');
|
||||
assert.match(failInjected, /dispatchMode=no_dispatch/, 'hook fail-path should remain no-dispatch');
|
||||
assert.match(failInjected, /autoChainAllowed=false/, 'hook fail-path should not allow auto-chain');
|
||||
assert.match(failInjected, /reason=explicit auto-chain next action requires dispatched-action evidence/, 'hook fail-path should mention missing dispatched-action evidence');
|
||||
assert.match(failInjected, /requiredEvidence=autoChainDispatchEvidence/, 'hook fail-path should require autoChainDispatchEvidence');
|
||||
|
||||
@@ -227,6 +245,11 @@ async function main() {
|
||||
handoff: { mode: 'direct_reply' },
|
||||
}), async () => runScenario(forceRecall, requestText));
|
||||
assert.match(neutralInjected, /gateStatus=pass/, 'hook neutral-path should pass when wrapper does not expose an explicit auto-chain action');
|
||||
assert.match(neutralInjected, /\[LONG_TASK_AUTO_CHAIN_PLAN\]/, 'hook neutral-path should emit auto-chain plan block');
|
||||
assert.match(neutralInjected, /plannerStatus=none/, 'hook neutral-path should report no derived auto-chain action');
|
||||
assert.match(neutralInjected, /derivedAction=none/, 'hook neutral-path should keep derivedAction as none');
|
||||
assert.match(neutralInjected, /dispatchMode=no_dispatch/, 'hook neutral-path should remain no-dispatch');
|
||||
assert.match(neutralInjected, /autoChainAllowed=false/, 'hook neutral-path should keep auto-chain disabled');
|
||||
assert.doesNotMatch(neutralInjected, /reason=explicit auto-chain next action requires dispatched-action evidence/, 'hook neutral-path should not fail on auto-chain evidence when no explicit tool action exists');
|
||||
|
||||
const originalGateLock = await fs.readFile(gateLockPath, 'utf8');
|
||||
|
||||
Reference in New Issue
Block a user