From 8cf4a6ee96260d3b4823d7937a6165020b018dc7 Mon Sep 17 00:00:00 2001 From: BakerSean168 Date: Sat, 3 Oct 2026 02:44:18 +0000 Subject: [PATCH] fix: load ForgeFlow in native child sessions --- docs/architecture.md | 2 +- docs/model-policy.md | 2 +- extension/index.js | 13 +++++++++++++ tests/extension.test.js | 12 +++++++++++- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index 51230e7..3c947bc 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -29,7 +29,7 @@ or resume, it belongs in Pi/pi-subagents or the provider layer rather than Forge ## Model policy boundary -ForgeFlow registers stable logical roles as Pi virtual models. Their physical model mapping is read from operator policy rather than hard-coded into workflows or subagent definitions. New user/direct requests resolve the current mapping, while continuation/retry requests stay on the physical model already handling the turn to preserve prompt-cache and reasoning-signature continuity. +ForgeFlow registers stable logical roles as Pi virtual models. Their physical model mapping is read from operator policy rather than hard-coded into workflows or subagent definitions. For native pi-subagents children, ForgeFlow registers its own extension as a required child extension so those virtual roles are present even though local foreground children intentionally skip parent ambient-extension discovery. New user/direct requests resolve the current mapping, while continuation/retry requests stay on the physical model already handling the turn to preserve prompt-cache and reasoning-signature continuity. Project-local `.pi/forgeflow-models.json` is considered only when Pi reports the project trusted. User-level policy under `~/.pi/forgeflow-models.json` remains available for untrusted projects. `FORGEFLOW_MODEL_POLICY` is an explicit operator override. diff --git a/docs/model-policy.md b/docs/model-policy.md index dc8e0b1..0ac2a07 100644 --- a/docs/model-policy.md +++ b/docs/model-policy.md @@ -47,7 +47,7 @@ The pinned `pi-subagents@0.75.0` includes the child-runtime fixes required for P The parent Pi session can select `forgeflow/planner` when the parent is acting as the planning/orchestration role. ForgeFlow's trusted `forgeflow.review` and `forgeflow.accept` workflows explicitly request `forgeflow/reviewer`, so the workflow owns the stable reviewer role while operator policy remains free to change the physical reviewer model without editing workflow code. -`pi-subagents@0.75.0` contains the upstream child-runtime fixes for queued virtual-model registration and logical-selection verification (#2636 and #2638). ForgeFlow therefore does not carry a compatibility shim for virtual child models. +`pi-subagents@0.75.0` contains the upstream child-runtime fixes for queued virtual-model registration and logical-selection verification (#2636 and #2638). ForgeFlow also registers its own extension as a required native-child extension for each Pi session, because local foreground children intentionally do not load the parent's ambient extensions. This makes the `forgeflow/*` virtual models available in foreground, detached, nested, and recovery child sessions without hard-coding an installation path in operator profile settings. External CLI runners remain excluded by pi-subagents. ForgeFlow therefore does not carry a virtual-child compatibility shim. ## Policy file diff --git a/extension/index.js b/extension/index.js index 52529f7..fe0165a 100644 --- a/extension/index.js +++ b/extension/index.js @@ -1,9 +1,14 @@ +import { fileURLToPath } from "node:url"; + +import { registerRequiredChildExtensions } from "pi-subagents/required-child-extensions"; import { registerWorkflowResource } from "pi-subagents/workflow-resources"; import { renderPreflight } from "./invariants.js"; import { createAcceptanceWorkflowDefinition } from "./acceptance-workflow.js"; import { createReviewWorkflowDefinition } from "./review-workflow.js"; import { registerForgeFlowVirtualModels } from "./model-policy.js"; +const FORGEFLOW_EXTENSION_PATH = fileURLToPath(import.meta.url); + const CORE_POLICY = [ "ForgeFlow is a thin engineering-governance extension for Pi; Pi and pi-subagents own agent execution, sessions, delegation, worktrees, missions, schedules, and resume.", "ForgeFlow may define stable logical model roles, but Pi owns virtual-model dispatch and the provider layer owns channel, credential, quota, and transport routing.", @@ -20,6 +25,7 @@ export function buildForgeFlowPromptSection(prompt) { export default function registerForgeFlow(pi) { registerForgeFlowVirtualModels(pi); + let requiredChildRegistration; let reviewRegistration; let acceptanceRegistration; @@ -28,11 +34,16 @@ export default function registerForgeFlow(pi) { }); pi.on("session_start", (_event, ctx) => { + requiredChildRegistration?.dispose(); reviewRegistration?.dispose(); acceptanceRegistration?.dispose(); const sessionId = ctx.sessionManager.getSessionId(); const sessionCwd = ctx.cwd; + requiredChildRegistration = registerRequiredChildExtensions({ + sessionId, + extensions: [{ id: "forgeflow", path: FORGEFLOW_EXTENSION_PATH }] + }); reviewRegistration = registerWorkflowResource({ sessionId, definition: createReviewWorkflowDefinition(sessionCwd) @@ -44,8 +55,10 @@ export default function registerForgeFlow(pi) { }); pi.on("session_shutdown", () => { + requiredChildRegistration?.dispose(); reviewRegistration?.dispose(); acceptanceRegistration?.dispose(); + requiredChildRegistration = undefined; reviewRegistration = undefined; acceptanceRegistration = undefined; }); diff --git a/tests/extension.test.js b/tests/extension.test.js index a2736fe..aa18889 100644 --- a/tests/extension.test.js +++ b/tests/extension.test.js @@ -1,6 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; +import { registerRequiredChildExtensions } from "pi-subagents/required-child-extensions"; import registerForgeFlow from "../extension/index.js"; test("extension registers Pi lifecycle hooks and injects policy without a model call", () => { @@ -40,14 +41,23 @@ test("extension registers Pi lifecycle hooks and injects policy without a model assert.match(event.systemPromptOptions.sections.forgeflow_policy, /INV-CUTOVER-001/); assert.match(event.systemPromptOptions.sections.forgeflow_policy, /INV-REPLAY-001/); + const sessionId = "forgeflow-pi-native-test"; assert.doesNotThrow(() => { handlers.get("session_start")( {}, { cwd: process.cwd(), - sessionManager: { getSessionId: () => "forgeflow-pi-native-test" } + sessionManager: { getSessionId: () => sessionId } } ); }); + + assert.throws( + () => registerRequiredChildExtensions({ sessionId, extensions: [] }), + /already registered/ + ); + assert.doesNotThrow(() => handlers.get("session_shutdown")()); + const afterShutdown = registerRequiredChildExtensions({ sessionId, extensions: [] }); + afterShutdown.dispose(); });