feat: add execution-layer auto-chain dry-run planner
This commit is contained in:
@@ -7,6 +7,16 @@ import { promisify } from "node:util";
|
||||
const execFileAsync = promisify(execFile);
|
||||
const LONG_TASK_WRAPPER_TIMEOUT_MS = 8000;
|
||||
const LONG_TASK_GATE_LOCK_TIMEOUT_MS = 8000;
|
||||
const LONG_TASK_AUTO_CHAIN_PLANNER_TIMEOUT_MS = 8000;
|
||||
|
||||
type AutoChainPlanResult = {
|
||||
plannerStatus: string;
|
||||
derivedAction: string;
|
||||
dispatchMode: string;
|
||||
reason: string;
|
||||
requiredEvidence?: string[];
|
||||
autoChainAllowed: boolean;
|
||||
};
|
||||
|
||||
type GateLockResult = {
|
||||
gateRequired: boolean;
|
||||
@@ -210,6 +220,86 @@ async function runLongTaskGateLock(workspaceDir: string, wrapperResult: any): Pr
|
||||
return runJsonScript(gateLockPath, workspaceDir, input, LONG_TASK_GATE_LOCK_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
function buildAutoChainPlannerInput(gateLockResult: GateLockResult | null, wrapperResult: any): Record<string, unknown> {
|
||||
const requiredNextAction = typeof wrapperResult?.requiredNextAction === "string"
|
||||
? wrapperResult.requiredNextAction.trim()
|
||||
: "";
|
||||
const plannerInput: Record<string, unknown> = {
|
||||
gateStatus: gateLockResult?.gateStatus ?? "not_applicable",
|
||||
actorStage: "hook_preflight",
|
||||
requiredNextAction,
|
||||
};
|
||||
|
||||
if (!requiredNextAction) return plannerInput;
|
||||
|
||||
if (requiredNextAction === "dispatch_follow_up_subagent") {
|
||||
plannerInput.actorStage = "implementer_result";
|
||||
plannerInput.requiredNextAction = "request_spec_review";
|
||||
if (wrapperResult?.autoChainDispatchEvidence && typeof wrapperResult.autoChainDispatchEvidence === "object" && !Array.isArray(wrapperResult.autoChainDispatchEvidence)) {
|
||||
plannerInput.executionEvidence = wrapperResult.autoChainDispatchEvidence;
|
||||
}
|
||||
return plannerInput;
|
||||
}
|
||||
|
||||
if (requiredNextAction === "dispatch_code_quality_review") {
|
||||
plannerInput.actorStage = "spec_review";
|
||||
plannerInput.requiredNextAction = "request_code_quality_review";
|
||||
plannerInput.reviewOutcome = "pass";
|
||||
plannerInput.reviewEvidence = wrapperResult?.reviewEvidence && typeof wrapperResult.reviewEvidence === "object" && !Array.isArray(wrapperResult.reviewEvidence)
|
||||
? wrapperResult.reviewEvidence
|
||||
: { source: "hook_preflight", verdict: "pass" };
|
||||
return plannerInput;
|
||||
}
|
||||
|
||||
if (requiredNextAction === "dispatch_fix_slice") {
|
||||
plannerInput.actorStage = "review_result";
|
||||
plannerInput.requiredNextAction = "fix_review_findings";
|
||||
plannerInput.blocker = typeof wrapperResult?.silentLaunchReason === "string" && wrapperResult.silentLaunchReason.trim()
|
||||
? wrapperResult.silentLaunchReason.trim()
|
||||
: "hook_preflight_blocker";
|
||||
plannerInput.blockerEvidence = wrapperResult?.blockerEvidence && typeof wrapperResult.blockerEvidence === "object" && !Array.isArray(wrapperResult.blockerEvidence)
|
||||
? wrapperResult.blockerEvidence
|
||||
: { source: "hook_preflight", blocker: plannerInput.blocker };
|
||||
return plannerInput;
|
||||
}
|
||||
|
||||
return plannerInput;
|
||||
}
|
||||
|
||||
async function runAutoChainPlanner(workspaceDir: string, gateLockResult: GateLockResult | null, wrapperResult: any): Promise<AutoChainPlanResult | null> {
|
||||
if (!wrapperResult || wrapperResult.classification !== "long_task") return null;
|
||||
const plannerPath = path.join(workspaceDir, "scripts", "plan_long_task_auto_chain.mjs");
|
||||
const input = buildAutoChainPlannerInput(gateLockResult, wrapperResult);
|
||||
return runJsonScript(plannerPath, workspaceDir, input, LONG_TASK_AUTO_CHAIN_PLANNER_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
function buildAutoChainPlanBlock(planResult: AutoChainPlanResult | null): string {
|
||||
if (!planResult) {
|
||||
return [
|
||||
"[LONG_TASK_AUTO_CHAIN_PLAN]",
|
||||
"plannerStatus=degraded",
|
||||
"derivedAction=none",
|
||||
"dispatchMode=no_dispatch",
|
||||
"autoChainAllowed=false",
|
||||
"reason=auto-chain planner unavailable during hook preflight",
|
||||
"[/LONG_TASK_AUTO_CHAIN_PLAN]",
|
||||
"",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
return [
|
||||
"[LONG_TASK_AUTO_CHAIN_PLAN]",
|
||||
`plannerStatus=${planResult.plannerStatus}`,
|
||||
`derivedAction=${planResult.derivedAction}`,
|
||||
`dispatchMode=${planResult.dispatchMode}`,
|
||||
`autoChainAllowed=${planResult.autoChainAllowed}`,
|
||||
`reason=${planResult.reason}`,
|
||||
...((planResult.requiredEvidence ?? []).map((entry) => `requiredEvidence=${entry}`)),
|
||||
"[/LONG_TASK_AUTO_CHAIN_PLAN]",
|
||||
"",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function buildWrapperEnforcement(wrapperResult: any): string[] {
|
||||
const lines = [
|
||||
"- Treat this as ingress preflight guidance from the wrapper MVP.",
|
||||
@@ -327,8 +417,9 @@ const forceRecall = async (event: any) => {
|
||||
runLongTaskWrapper(workspaceDir, ctx),
|
||||
]);
|
||||
const gateLockResult = wrapperResult ? await runLongTaskGateLock(workspaceDir, wrapperResult) : null;
|
||||
const autoChainPlanResult = wrapperResult ? await runAutoChainPlanner(workspaceDir, gateLockResult, wrapperResult) : null;
|
||||
|
||||
if (!rulebook && !soul && !wrapperResult && !gateLockResult) return;
|
||||
if (!rulebook && !soul && !wrapperResult && !gateLockResult && !autoChainPlanResult) return;
|
||||
|
||||
const wrapperBlock = wrapperResult
|
||||
? [
|
||||
@@ -353,6 +444,7 @@ const forceRecall = async (event: any) => {
|
||||
: "";
|
||||
|
||||
const gateLockBlock = buildGateLockBlock(gateLockResult);
|
||||
const autoChainPlanBlock = buildAutoChainPlanBlock(autoChainPlanResult);
|
||||
|
||||
const recallBlock = [
|
||||
"[RECALL_GATE] Mandatory recall before ANY technical action/tool use.",
|
||||
@@ -361,6 +453,7 @@ const forceRecall = async (event: any) => {
|
||||
"",
|
||||
wrapperBlock || null,
|
||||
gateLockBlock,
|
||||
autoChainPlanBlock,
|
||||
rulebook ? `RULEBOOK (source: ${rulebookPath}):\n${clamp(rulebook, 1200)}` : null,
|
||||
soul ? `SOUL (source: ${soulPath}):\n${clamp(soul, 1200)}` : null,
|
||||
"[/RECALL_GATE]",
|
||||
|
||||
Reference in New Issue
Block a user