feat: route force-recall continuity via plugin adapter
This commit is contained in:
@@ -51,6 +51,10 @@ async function prepareTempWorkspace() {
|
||||
await fs.mkdir(path.join(tempWorkspace, 'hooks', 'force-recall'), { recursive: true });
|
||||
await fs.mkdir(path.join(tempWorkspace, 'docs'), { recursive: true });
|
||||
|
||||
await fs.mkdir(path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'adapters'), { recursive: true });
|
||||
await fs.mkdir(path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'config'), { recursive: true });
|
||||
await fs.mkdir(path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'continuity'), { recursive: true });
|
||||
|
||||
const copies = [
|
||||
[wrapperPath, path.join(tempWorkspace, 'scripts', 'long_task_governor_wrapper.mjs')],
|
||||
[gateLockPath, path.join(tempWorkspace, 'scripts', 'long_task_gate_lock.mjs')],
|
||||
@@ -59,6 +63,13 @@ async function prepareTempWorkspace() {
|
||||
[handlerPath, path.join(tempWorkspace, 'hooks', 'force-recall', 'handler.ts')],
|
||||
[path.join(repoRoot, 'docs', 'RULEBOOK.md'), path.join(tempWorkspace, 'docs', 'RULEBOOK.md')],
|
||||
[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', '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')],
|
||||
];
|
||||
|
||||
for (const [src, dest] of copies) {
|
||||
@@ -285,6 +296,49 @@ async function main() {
|
||||
assert.equal(neutralSnakeCaseResult.gateStatus, 'pass', 'neutral snake_case non-dispatch action should not trigger dispatch-evidence requirement');
|
||||
assert.doesNotMatch(JSON.stringify(neutralSnakeCaseResult), /autoChainDispatchEvidence/, 'neutral snake_case non-dispatch action should not mention dispatch-evidence requirement');
|
||||
|
||||
const pluginPathInjected = await withPatchedWrapperWorkspace({
|
||||
classification: 'long_task',
|
||||
silentCandidate: true,
|
||||
needsCheckpoint: true,
|
||||
needsSubagent: false,
|
||||
needsOwnerDecision: false,
|
||||
silentLaunchOk: true,
|
||||
planId: 'plan-plugin-path',
|
||||
currentTask: 'task-plugin-path',
|
||||
taskState: 'complete',
|
||||
replyClosureState: 'completed',
|
||||
requiredNextAction: 'dispatch_follow_up_subagent',
|
||||
autoChainDispatchEvidence: {
|
||||
action: 'dispatch_follow_up_subagent',
|
||||
dispatched: true,
|
||||
event: 'dispatch',
|
||||
},
|
||||
progressEvidence: { sessionKey: 'task-plugin-path' },
|
||||
externalizedCheckpointPath: 'checkpoints/task-plugin-path.json',
|
||||
handoff: { mode: 'direct_reply' },
|
||||
dispatchReceipt: {
|
||||
planId: 'plan-plugin-path',
|
||||
currentTask: 'task-plugin-path',
|
||||
nextDerivedAction: {
|
||||
type: 'dry_run_dispatch',
|
||||
action: 'dispatch_spec_review',
|
||||
},
|
||||
dispatchedAt: '2026-04-24T17:00:00+08:00',
|
||||
},
|
||||
}, async (workspaceDir) => {
|
||||
const defaultsPath = path.join(workspaceDir, 'plugins', 'continuity', 'src', 'config', 'defaults.mjs');
|
||||
const defaultsSource = await fs.readFile(defaultsPath, 'utf8');
|
||||
await fs.writeFile(
|
||||
defaultsPath,
|
||||
defaultsSource.replace('APPROVED_PLAN_CONTINUITY_GATE', 'PLUGIN_CONTINUITY_GATE'),
|
||||
'utf8',
|
||||
);
|
||||
return runScenario(forceRecall, requestText, workspaceDir);
|
||||
});
|
||||
assert.match(pluginPathInjected, /\[PLUGIN_CONTINUITY_GATE\]/, 'hook should inject continuity block from plugin adapter path, not only local fallback builder');
|
||||
assert.match(pluginPathInjected, /status=pass/, 'plugin adapter path should still pass when a bound dispatch receipt exists');
|
||||
assert.doesNotMatch(pluginPathInjected, /\[APPROVED_PLAN_CONTINUITY_GATE\]/, 'plugin adapter label override should replace the legacy fallback block label when plugin path is active');
|
||||
|
||||
const passInjected = await withPatchedWrapperWorkspace({
|
||||
classification: 'long_task',
|
||||
silentCandidate: true,
|
||||
|
||||
Reference in New Issue
Block a user