@hassangameryt/freeflow 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -1
  3. package/.codex-plugin/plugin.json +2 -1
  4. package/README.md +150 -155
  5. package/capabilities/cognitive-routing/SKILL.md +229 -0
  6. package/capabilities/context-virtualization/SKILL.md +103 -0
  7. package/capabilities/conversation-history/SKILL.md +53 -0
  8. package/command-surface.json +17 -13
  9. package/hooks/freeflow-runtime-context.mjs +393 -159
  10. package/hooks/hooks.json +26 -1
  11. package/hooks/tests/check-runtime-context-hook.sh +223 -54
  12. package/package.json +25 -16
  13. package/pi-extension/dist/cognitive-routing/commands.js +96 -0
  14. package/pi-extension/dist/cognitive-routing/config.js +236 -0
  15. package/pi-extension/dist/cognitive-routing/controller.js +862 -0
  16. package/pi-extension/dist/cognitive-routing/history.js +439 -0
  17. package/pi-extension/dist/cognitive-routing/host.js +126 -0
  18. package/pi-extension/dist/cognitive-routing/runtime.js +43 -0
  19. package/pi-extension/dist/cognitive-routing/tool.js +211 -0
  20. package/pi-extension/dist/cognitive-routing/types.js +6 -0
  21. package/pi-extension/dist/context-virtualization/commands.js +89 -0
  22. package/pi-extension/dist/context-virtualization/projector.js +42 -0
  23. package/pi-extension/dist/context-virtualization/runtime.js +356 -0
  24. package/pi-extension/dist/context-virtualization/state.js +57 -0
  25. package/pi-extension/dist/context-virtualization/types.js +10 -0
  26. package/pi-extension/dist/conversation-history/passages.js +66 -0
  27. package/pi-extension/dist/conversation-history/ranking.js +105 -0
  28. package/pi-extension/dist/conversation-history/runtime.js +226 -0
  29. package/pi-extension/dist/conversation-history/search.js +56 -0
  30. package/pi-extension/dist/conversation-history/types.js +1 -0
  31. package/pi-extension/dist/freeflow-context/resolver.js +120 -0
  32. package/pi-extension/dist/freeflow-context/runtime.js +80 -0
  33. package/pi-extension/dist/freeflow-context/source-projector.js +95 -0
  34. package/pi-extension/dist/freeflow-context/tool.js +308 -0
  35. package/pi-extension/dist/freeflow-context/types.js +12 -0
  36. package/pi-extension/dist/freeflow-context/visibility.js +28 -0
  37. package/pi-extension/dist/index.js +516 -98
  38. package/pi-extension/dist/runtime/runtime-context.js +318 -271
  39. package/pi-extension/dist/runtime/runtime-identity.js +5 -0
  40. package/pi-extension/dist/settings/settings-tui.js +78 -0
  41. package/pi-extension/dist/settings/settings-ui.js +593 -563
  42. package/pi-extension/dist/ui/text-component.js +57 -0
  43. package/pi-extension/src/cognitive-routing/commands.ts +119 -0
  44. package/pi-extension/src/cognitive-routing/config.ts +307 -0
  45. package/pi-extension/src/cognitive-routing/controller.ts +1118 -0
  46. package/pi-extension/src/cognitive-routing/history.ts +638 -0
  47. package/pi-extension/src/cognitive-routing/host.ts +172 -0
  48. package/pi-extension/src/cognitive-routing/runtime.ts +57 -0
  49. package/pi-extension/src/cognitive-routing/tool.ts +272 -0
  50. package/pi-extension/src/cognitive-routing/types.ts +89 -0
  51. package/pi-extension/src/context-virtualization/commands.ts +111 -0
  52. package/pi-extension/src/context-virtualization/projector.ts +62 -0
  53. package/pi-extension/src/context-virtualization/runtime.ts +406 -0
  54. package/pi-extension/src/context-virtualization/state.ts +79 -0
  55. package/pi-extension/src/context-virtualization/types.ts +92 -0
  56. package/pi-extension/src/conversation-history/passages.ts +81 -0
  57. package/pi-extension/src/conversation-history/ranking.ts +155 -0
  58. package/pi-extension/src/conversation-history/runtime.ts +250 -0
  59. package/pi-extension/src/conversation-history/search.ts +79 -0
  60. package/pi-extension/src/conversation-history/types.ts +31 -0
  61. package/pi-extension/src/freeflow-context/resolver.ts +139 -0
  62. package/pi-extension/src/freeflow-context/runtime.ts +100 -0
  63. package/pi-extension/src/freeflow-context/source-projector.ts +106 -0
  64. package/pi-extension/src/freeflow-context/tool.ts +360 -0
  65. package/pi-extension/src/freeflow-context/types.ts +70 -0
  66. package/pi-extension/src/freeflow-context/visibility.ts +53 -0
  67. package/pi-extension/src/index.ts +553 -101
  68. package/pi-extension/src/runtime/runtime-context.ts +339 -281
  69. package/pi-extension/src/runtime/runtime-identity.ts +8 -0
  70. package/pi-extension/src/settings/settings-tui.ts +93 -0
  71. package/pi-extension/src/settings/settings-ui.ts +700 -613
  72. package/pi-extension/src/ui/text-component.ts +66 -0
  73. package/pi-extension/tests/cognitive-routing/commands.test.js +134 -0
  74. package/pi-extension/tests/cognitive-routing/controller.test.js +1099 -0
  75. package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +1071 -0
  76. package/pi-extension/tests/cognitive-routing/history.test.js +274 -0
  77. package/pi-extension/tests/cognitive-routing/host-fixture.js +4 -0
  78. package/pi-extension/tests/cognitive-routing/host-identity.test.js +63 -0
  79. package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +236 -0
  80. package/pi-extension/tests/cognitive-routing/tool.test.js +230 -0
  81. package/pi-extension/tests/context-virtualization/extension.test.js +749 -0
  82. package/pi-extension/tests/context-virtualization/projector.test.js +110 -0
  83. package/pi-extension/tests/context-virtualization/runtime.test.js +216 -0
  84. package/pi-extension/tests/conversation-history/runtime.test.js +98 -0
  85. package/pi-extension/tests/conversation-history/search.test.js +97 -0
  86. package/pi-extension/tests/integration/pi-extension.test.js +928 -2047
  87. package/pi-extension/tests/integration/prompt-architecture.test.js +305 -0
  88. package/runtime/prompts/cognitive-routing.md +9 -0
  89. package/runtime/prompts/context-virtualization.md +3 -0
  90. package/runtime/prompts/conversation-history.md +3 -0
  91. package/runtime/prompts/core.md +13 -0
  92. package/runtime/prompts/interaction-contract.md +7 -0
  93. package/runtime/prompts/skills.md +65 -0
  94. package/skills/action-selection/SKILL.md +91 -0
  95. package/skills/bypass/SKILL.md +3 -2
  96. package/skills/commit-work/SKILL.md +1 -1
  97. package/skills/commit-work/references/staging-decisions.md +1 -1
  98. package/skills/decision-gate/SKILL.md +1 -1
  99. package/skills/design-for-depth/SKILL.md +14 -33
  100. package/skills/design-for-depth/references/design-pressure-signals.md +1 -1
  101. package/skills/design-for-depth/references/interface-design-loop.md +1 -1
  102. package/skills/design-for-depth/references/software-design-philosophy.md +1 -1
  103. package/skills/diagnose-failure/SKILL.md +20 -14
  104. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +1 -3
  105. package/skills/diagnose-failure/references/{flaky-and-performance.md → flaky-and-performance-diagnosis.md} +1 -1
  106. package/skills/discuss/SKILL.md +34 -22
  107. package/skills/discuss/references/discussion-continuity.md +71 -0
  108. package/skills/evaluate-skill/SKILL.md +5 -5
  109. package/skills/evaluate-skill/references/{definition-schema.md → evaluation-definition-schema.md} +42 -2
  110. package/skills/evaluate-skill/references/execution-and-evidence.md +8 -3
  111. package/skills/evaluate-skill/scripts/lib/definitions.mjs +114 -3
  112. package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +156 -1
  113. package/skills/evaluate-skill/scripts/lib/grade.mjs +9 -1
  114. package/skills/evaluate-skill/scripts/lib/pi.mjs +147 -32
  115. package/skills/evaluate-skill/scripts/lib/rpc.mjs +4 -4
  116. package/skills/evaluate-skill/scripts/lib/runner.mjs +36 -14
  117. package/skills/evaluate-skill/scripts/lib/sandbox.mjs +92 -0
  118. package/skills/evaluate-skill/scripts/pi-guard.mjs +1 -1
  119. package/skills/evaluate-skill/scripts/pi-observer.mjs +66 -0
  120. package/skills/evaluate-skill/scripts/skill-eval.mjs +6 -5
  121. package/skills/execute-work/SKILL.md +18 -15
  122. package/skills/execute-work/references/code-practices.md +1 -1
  123. package/skills/execute-work/references/execute-work-edges.md +42 -0
  124. package/skills/finish-branch/SKILL.md +2 -2
  125. package/skills/finish-branch/references/integration-options.md +1 -1
  126. package/skills/handoff/SKILL.md +3 -3
  127. package/skills/launch-work/SKILL.md +1 -1
  128. package/skills/launch-work/references/launch-readiness.md +2 -2
  129. package/skills/migration-work/SKILL.md +2 -2
  130. package/skills/migration-work/references/migration-lifecycle.md +1 -1
  131. package/skills/mode-contract/SKILL.md +32 -10
  132. package/skills/release-work/SKILL.md +1 -1
  133. package/skills/release-work/references/release-evidence.md +1 -1
  134. package/skills/review-artifact/SKILL.md +28 -26
  135. package/skills/review-work/SKILL.md +28 -26
  136. package/skills/review-work/references/{reviewer-prompt.md → independent-work-reviewer-contract.md} +1 -1
  137. package/skills/review-work/references/security-risk-lens.md +1 -1
  138. package/skills/setup-freeflow/SKILL.md +6 -10
  139. package/skills/setup-freeflow/references/activation-contract.md +5 -10
  140. package/skills/setup-freeflow/references/host-setup.md +3 -7
  141. package/skills/simplify-code/SKILL.md +1 -1
  142. package/skills/simplify-code/references/simplification-patterns.md +1 -1
  143. package/skills/tdd/SKILL.md +18 -38
  144. package/skills/tdd/references/test-design.md +2 -2
  145. package/skills/track-work/SKILL.md +362 -58
  146. package/skills/track-work/scripts/lib/codec.mjs +570 -0
  147. package/skills/track-work/scripts/lib/commands.mjs +568 -0
  148. package/skills/track-work/scripts/lib/contract.mjs +303 -0
  149. package/skills/track-work/scripts/lib/model.mjs +506 -0
  150. package/skills/track-work/scripts/lib/operations.mjs +965 -0
  151. package/skills/track-work/scripts/lib/record-store.mjs +264 -0
  152. package/skills/track-work/scripts/lib/result.mjs +37 -0
  153. package/skills/track-work/scripts/lib/rewrites.mjs +740 -0
  154. package/skills/track-work/scripts/lib/schema.mjs +277 -0
  155. package/skills/track-work/scripts/lib/source-inventory.mjs +162 -0
  156. package/skills/track-work/scripts/lib/views.mjs +443 -0
  157. package/skills/track-work/scripts/lib/workspace.mjs +134 -0
  158. package/skills/track-work/scripts/working-record.mjs +122 -0
  159. package/skills/verify-work/SKILL.md +14 -2
  160. package/skills/verify-work/references/browser-runtime-evidence.md +1 -1
  161. package/skills/verify-work/references/integration-evidence.md +1 -1
  162. package/skills/verify-work/references/performance-evidence.md +1 -1
  163. package/skills/workflow/SKILL.md +105 -53
  164. package/skills/workflow/references/domain-skill-composition.md +22 -42
  165. package/skills/write-plan/SKILL.md +1 -1
  166. package/skills/write-skill/SKILL.md +3 -2
  167. package/skills/write-skill/references/progressive-disclosure.md +17 -3
  168. package/pi-extension/dist/output-router/host-producer-identification.js +0 -232
  169. package/pi-extension/dist/output-router/native-safety-net.js +0 -216
  170. package/pi-extension/dist/output-router/observed-tool-routing.js +0 -122
  171. package/pi-extension/dist/output-router/renderers.js +0 -1023
  172. package/pi-extension/dist/output-router/router-tools.js +0 -554
  173. package/pi-extension/dist/output-router/schemas.js +0 -383
  174. package/pi-extension/dist/output-router/status.js +0 -744
  175. package/pi-extension/dist/output-router/utils.js +0 -502
  176. package/pi-extension/src/output-router/host-producer-identification.ts +0 -256
  177. package/pi-extension/src/output-router/native-safety-net.ts +0 -248
  178. package/pi-extension/src/output-router/observed-tool-routing.ts +0 -134
  179. package/pi-extension/src/output-router/renderers.ts +0 -1108
  180. package/pi-extension/src/output-router/router-tools.ts +0 -579
  181. package/pi-extension/src/output-router/schemas.ts +0 -387
  182. package/pi-extension/src/output-router/status.ts +0 -791
  183. package/pi-extension/src/output-router/utils.ts +0 -554
  184. package/pi-extension/tests/output-router/observed-producers.test.js +0 -132
  185. package/pi-extension/tests/output-router/search.test.js +0 -722
  186. package/router/dist/benchmarks/benchmark-harness.d.ts +0 -35
  187. package/router/dist/benchmarks/benchmark-harness.js +0 -92
  188. package/router/dist/benchmarks/benchmarks.d.ts +0 -109
  189. package/router/dist/benchmarks/benchmarks.js +0 -979
  190. package/router/dist/benchmarks/codex-qa-benchmarks.d.ts +0 -95
  191. package/router/dist/benchmarks/codex-qa-benchmarks.js +0 -500
  192. package/router/dist/benchmarks/command-benchmarks.d.ts +0 -143
  193. package/router/dist/benchmarks/command-benchmarks.js +0 -698
  194. package/router/dist/benchmarks/context-mode-normalized-benchmarks.d.ts +0 -82
  195. package/router/dist/benchmarks/context-mode-normalized-benchmarks.js +0 -773
  196. package/router/dist/benchmarks/context-mode-real-deep-benchmark.d.ts +0 -132
  197. package/router/dist/benchmarks/context-mode-real-deep-benchmark.js +0 -1380
  198. package/router/dist/benchmarks/context-saving-baseline.d.ts +0 -60
  199. package/router/dist/benchmarks/context-saving-baseline.js +0 -470
  200. package/router/dist/benchmarks/index-benchmarks.d.ts +0 -103
  201. package/router/dist/benchmarks/index-benchmarks.js +0 -862
  202. package/router/dist/benchmarks/storage-policy-benchmarks.d.ts +0 -101
  203. package/router/dist/benchmarks/storage-policy-benchmarks.js +0 -618
  204. package/router/dist/benchmarks/vault-index-benchmarks.d.ts +0 -56
  205. package/router/dist/benchmarks/vault-index-benchmarks.js +0 -322
  206. package/router/dist/config/config.d.ts +0 -103
  207. package/router/dist/config/config.js +0 -632
  208. package/router/dist/config/router-contract.d.ts +0 -10
  209. package/router/dist/config/router-contract.js +0 -54
  210. package/router/dist/config/schema.d.ts +0 -27
  211. package/router/dist/config/schema.js +0 -648
  212. package/router/dist/config/types.d.ts +0 -573
  213. package/router/dist/config/types.js +0 -41
  214. package/router/dist/evidence/bounded-evidence.d.ts +0 -40
  215. package/router/dist/evidence/bounded-evidence.js +0 -323
  216. package/router/dist/evidence/evidence-range-selector.d.ts +0 -24
  217. package/router/dist/evidence/evidence-range-selector.js +0 -232
  218. package/router/dist/evidence/evidence-search.d.ts +0 -27
  219. package/router/dist/evidence/evidence-search.js +0 -490
  220. package/router/dist/evidence/evidence.d.ts +0 -38
  221. package/router/dist/evidence/evidence.js +0 -200
  222. package/router/dist/evidence/failure-contracts.d.ts +0 -37
  223. package/router/dist/evidence/failure-contracts.js +0 -166
  224. package/router/dist/evidence/line-ranges.d.ts +0 -24
  225. package/router/dist/evidence/line-ranges.js +0 -22
  226. package/router/dist/experiments/local-index.d.ts +0 -64
  227. package/router/dist/experiments/local-index.js +0 -581
  228. package/router/dist/index.d.ts +0 -30
  229. package/router/dist/index.js +0 -32
  230. package/router/dist/local/local-traversal.d.ts +0 -18
  231. package/router/dist/local/local-traversal.js +0 -334
  232. package/router/dist/processing/engine.d.ts +0 -132
  233. package/router/dist/processing/engine.js +0 -533
  234. package/router/dist/processing/reducers.d.ts +0 -280
  235. package/router/dist/processing/reducers.js +0 -1673
  236. package/router/dist/processing/renderers.d.ts +0 -31
  237. package/router/dist/processing/renderers.js +0 -195
  238. package/router/dist/processing/scripts.d.ts +0 -91
  239. package/router/dist/processing/scripts.js +0 -386
  240. package/router/dist/repo/repo-traversal.d.ts +0 -18
  241. package/router/dist/repo/repo-traversal.js +0 -245
  242. package/router/dist/routing/observed-reducers.d.ts +0 -41
  243. package/router/dist/routing/observed-reducers.js +0 -661
  244. package/router/dist/routing/observed-routing.d.ts +0 -26
  245. package/router/dist/routing/observed-routing.js +0 -378
  246. package/router/dist/routing/parsers.d.ts +0 -16
  247. package/router/dist/routing/parsers.js +0 -521
  248. package/router/dist/routing/run-filters.d.ts +0 -60
  249. package/router/dist/routing/run-filters.js +0 -238
  250. package/router/dist/routing/run-reducers.d.ts +0 -49
  251. package/router/dist/routing/run-reducers.js +0 -122
  252. package/router/dist/sandbox/adapter-roots.d.ts +0 -46
  253. package/router/dist/sandbox/adapter-roots.js +0 -66
  254. package/router/dist/sandbox/eryx-python-adapter.d.ts +0 -13
  255. package/router/dist/sandbox/eryx-python-adapter.js +0 -868
  256. package/router/dist/sandbox/jq-wasm-adapter.d.ts +0 -9
  257. package/router/dist/sandbox/jq-wasm-adapter.js +0 -429
  258. package/router/dist/sandbox/quickjs-wasi-adapter.d.ts +0 -13
  259. package/router/dist/sandbox/quickjs-wasi-adapter.js +0 -384
  260. package/router/dist/sandbox/script-sandbox.d.ts +0 -134
  261. package/router/dist/sandbox/script-sandbox.js +0 -337
  262. package/router/dist/setup/script-transform-adapters.d.ts +0 -32
  263. package/router/dist/setup/script-transform-adapters.js +0 -278
  264. package/router/dist/tools/batch.d.ts +0 -33
  265. package/router/dist/tools/batch.js +0 -586
  266. package/router/dist/tools/run.d.ts +0 -62
  267. package/router/dist/tools/run.js +0 -1304
  268. package/router/dist/tools/search.d.ts +0 -66
  269. package/router/dist/tools/search.js +0 -1364
  270. package/router/dist/transform/engine.d.ts +0 -168
  271. package/router/dist/transform/engine.js +0 -2072
  272. package/router/dist/vault/vault-index.d.ts +0 -125
  273. package/router/dist/vault/vault-index.js +0 -416
  274. package/router/dist/vault/vault.d.ts +0 -138
  275. package/router/dist/vault/vault.js +0 -630
  276. package/runtime/interaction-contract.md +0 -16
  277. package/skills/discuss/references/checkpoints.md +0 -66
  278. package/skills/execute-work/references/execution-loop.md +0 -77
  279. package/skills/output-router/SKILL.md +0 -206
  280. package/skills/output-router/references/safety-policy.md +0 -40
  281. package/skills/setup-freeflow/references/output-router-setup.md +0 -110
  282. package/skills/track-work/references/working-record-schema.md +0 -378
  283. package/skills/workflow/references/workflow-loop.md +0 -92
  284. /package/skills/handoff/references/{templates.md → handoff-templates.md} +0 -0
  285. /package/skills/review-artifact/references/{reviewer-prompt.md → independent-artifact-reviewer-contract.md} +0 -0
@@ -11,30 +11,37 @@ import {
11
11
  readCapabilityState,
12
12
  readFreeflowConfigLayers,
13
13
  readModeState,
14
- readOutputRouterConfig,
15
14
  resetSessionOverrides,
15
+ setModeStatus,
16
16
  restoreModeOverride,
17
17
  setSessionCoreOverride,
18
18
  setSessionMode,
19
19
  } from "../../dist/runtime/runtime-context.js";
20
- import { createVault, storeTextOutput } from "../../../router/dist/index.js";
20
+ import { PIFLOW_HOST } from "../cognitive-routing/host-fixture.js";
21
21
 
22
22
  const execFileAsync = promisify(execFile);
23
23
 
24
- function loadExtension() {
24
+ function loadExtension(host = PIFLOW_HOST) {
25
25
  const handlers = new Map();
26
26
  const tools = [];
27
27
  const commands = [];
28
+ const shortcuts = [];
28
29
  const entries = [];
29
30
  const sentMessages = [];
30
31
  let activeToolNames;
31
32
  const pi = {
33
+ host,
32
34
  registerTool(tool) {
33
- tools.push(tool);
35
+ const index = tools.findIndex((existing) => existing.name === tool.name);
36
+ if (index >= 0) tools[index] = tool;
37
+ else tools.push(tool);
34
38
  },
35
39
  registerCommand(name, definition) {
36
40
  commands.push({ name, definition });
37
41
  },
42
+ registerShortcut(shortcut, definition) {
43
+ shortcuts.push({ shortcut, definition });
44
+ },
38
45
  on(event, handler) {
39
46
  handlers.set(event, handler);
40
47
  },
@@ -64,6 +71,7 @@ function loadExtension() {
64
71
  handlers,
65
72
  tools,
66
73
  commands,
74
+ shortcuts,
67
75
  entries,
68
76
  sentMessages,
69
77
  activeToolNames: () => activeToolNames ?? tools.map((tool) => tool.name),
@@ -120,26 +128,239 @@ function renderText(component, width = 120) {
120
128
  return component.render(width).join("\n");
121
129
  }
122
130
 
123
- test("Pi registers capability commands and no public capture tool", () => {
124
- const { commands, tools } = loadExtension();
131
+ test("Pi registers Freeflow commands without retired router tools", () => {
132
+ const { commands, shortcuts, tools } = loadExtension();
125
133
  const commandNames = commands.map((command) => command.name);
126
134
  const toolNames = tools.map((tool) => tool.name);
127
135
 
128
136
  assert.ok(commandNames.includes("freeflow"));
129
- assert.ok(commandNames.includes("output-router"));
130
- for (const command of ["discuss", "discover", "track-work", "execute-work", "execute-plan"]) {
137
+ assert.ok(!commandNames.includes("output-router"));
138
+ for (const command of ["discuss", "track-work", "execute-work"]) {
131
139
  assert.ok(commandNames.includes(command));
132
140
  }
141
+ assert.ok(!commandNames.includes("discover"));
142
+ assert.ok(!commandNames.includes("execute-plan"));
133
143
  assert.ok(!commandNames.includes("workflow"));
134
- assert.ok(toolNames.includes("freeflow_status"));
135
- assert.ok(toolNames.includes("freeflow_search"));
136
- assert.ok(toolNames.includes("freeflow_run"));
137
- assert.ok(toolNames.includes("freeflow_batch"));
144
+ assert.deepEqual(
145
+ shortcuts.map(({ shortcut }) => shortcut),
146
+ ["ctrl+shift+r", "ctrl+shift+a"],
147
+ );
148
+ assert.ok(toolNames.includes("freeflow_context"));
149
+ assert.deepEqual(tools.find((tool) => tool.name === "freeflow_context").parameters.oneOf, []);
150
+ assert.ok(toolNames.includes("freeflow_switch_profile"));
151
+ assert.ok(
152
+ !toolNames.some((name) => ["freeflow_status", "freeflow_search", "freeflow_run", "freeflow_batch"].includes(name)),
153
+ );
138
154
  assert.ok(!toolNames.includes("freeflow_retrieve"));
139
- assert.ok(!toolNames.includes("freeflow_search action=transform"));
140
155
  assert.ok(!toolNames.includes("freeflow_capture"));
141
156
  });
142
157
 
158
+ test("normal Pi keeps Cognitive Routing disabled while showing its configuration", async () => {
159
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-normal-runtime-"));
160
+ try {
161
+ await mkdir(join(cwd, ".freeflow"));
162
+ await writeFile(join(cwd, ".freeflow/config.json"), JSON.stringify({ defaultMode: "workflow" }), "utf8");
163
+ await writeFile(
164
+ join(cwd, ".freeflow/local.json"),
165
+ JSON.stringify({
166
+ cognitiveRouting: {
167
+ enabled: true,
168
+ profiles: {
169
+ standard: { provider: "test", model: "standard", thinkingLevel: "high" },
170
+ reasoning: { provider: "test", model: "reasoning", thinkingLevel: "max" },
171
+ },
172
+ },
173
+ }),
174
+ "utf8",
175
+ );
176
+ const { commands, handlers, shortcuts, tools } = loadExtension(null);
177
+ const freeflowCommand = commands.find((command) => command.name === "freeflow");
178
+ assert.ok(freeflowCommand);
179
+ assert.deepEqual(shortcuts, []);
180
+ assert.ok(!tools.some((tool) => tool.name === "freeflow_switch_profile"));
181
+ assert.ok(!freeflowCommand.definition.getArgumentCompletions("").some((item) => item.value === "profile"));
182
+ const capabilityState = await readCapabilityState(
183
+ cwd,
184
+ { modelRegistry: cognitiveRoutingModelRegistry() },
185
+ undefined,
186
+ );
187
+ assert.equal(capabilityState.cognitiveRouting.enabled, true);
188
+ assert.equal(capabilityState.cognitiveRouting.effective, false);
189
+ assert.equal(capabilityState.cognitiveRouting.blockingReason.code, "runtime_disabled");
190
+
191
+ const ctx = context(cwd);
192
+ await freeflowCommand.definition.handler("status", ctx);
193
+ assert.match(ctx.notifications.at(-1).message, /cognitive routing: disabled \(PiFlow only\)/i);
194
+ await handlers.get("session_start")({ type: "session_start" }, ctx);
195
+ assert.match(ctx.statuses.at(-1).value, /cognitive disabled · PiFlow only/);
196
+ const beforeAgentStart = await handlers.get("before_agent_start")({ systemPrompt: "base" }, ctx);
197
+ assert.doesNotMatch(
198
+ `${beforeAgentStart?.systemPrompt ?? ""}\n${beforeAgentStart?.message ?? ""}`,
199
+ /Cognitive Routing/i,
200
+ );
201
+ ctx.ui.custom = async (factory) => {
202
+ let result;
203
+ const component = factory({ requestRender() {} }, testTheme, {}, (value) => {
204
+ result = value;
205
+ });
206
+ const rootText = renderText(component);
207
+ assert.match(rootText, /Cognitive Routing/);
208
+ assert.match(rootText, /PiFlow only/);
209
+ component.handleInput("\u001b[B");
210
+ component.handleInput("\r");
211
+ component.handleInput("\u001b[B");
212
+ component.handleInput("\u001b[B");
213
+ component.handleInput("\r");
214
+ await component.waitForWrites();
215
+ assert.doesNotMatch(renderText(component), /Cognitive Routing\s+enabled \(5\) configured · inactive/);
216
+ for (let index = 0; index < 5; index += 1) component.handleInput("\u001b[B");
217
+ component.handleInput("\r");
218
+ const cognitiveText = renderText(component);
219
+ assert.match(cognitiveText, /Freeflow Settings · Personal overrides › Cognitive Routing/);
220
+ assert.match(cognitiveText, /Enabled\s+enabled \(local · inactive\)/);
221
+ assert.match(cognitiveText, /Standard preset\s+test\/standard · high \(local · inactive\)/);
222
+ assert.match(cognitiveText, /Reasoning preset\s+test\/reasoning · max \(local · inactive\)/);
223
+ component.handleInput("\r");
224
+ assert.match(renderText(component), /Freeflow Settings · Personal overrides › Cognitive Routing/);
225
+ assert.doesNotMatch(renderText(component), /Inherit repository/);
226
+ component.handleInput("\u001b");
227
+ return result;
228
+ };
229
+ await freeflowCommand.definition.handler("settings", ctx);
230
+ } finally {
231
+ await rm(cwd, { recursive: true, force: true });
232
+ }
233
+ });
234
+
235
+ test("PiFlow statusline reports the current cognitive profile and control mode", () => {
236
+ const ctx = context();
237
+ setModeStatus(
238
+ ctx,
239
+ { effectiveMode: "workflow", currentMode: null },
240
+ {
241
+ configured: true,
242
+ enabled: true,
243
+ configSources: { interactionContract: "builtin", skillsEnabled: "builtin", enabled: "builtin" },
244
+ interactionContract: { effective: true },
245
+ skills: { effective: true },
246
+ cognitiveRouting: { enabled: true, effective: true, blockingReason: { code: "disabled" } },
247
+ },
248
+ { effective: true, activeProfile: "reasoning", controlMode: "automatic" },
249
+ );
250
+ assert.equal(ctx.statuses.at(-1).value, "freeflow: interaction · workflow · reasoning · automatic");
251
+ });
252
+
253
+ test("PiFlow statusline shows the configured standard profile pending first prompt activation", () => {
254
+ const ctx = context();
255
+ setModeStatus(
256
+ ctx,
257
+ { effectiveMode: "workflow", currentMode: null },
258
+ {
259
+ configured: true,
260
+ enabled: true,
261
+ configSources: { interactionContract: "builtin", skillsEnabled: "builtin", enabled: "builtin" },
262
+ interactionContract: { effective: true },
263
+ skills: { effective: true },
264
+ cognitiveRouting: { enabled: true, effective: true, blockingReason: null },
265
+ },
266
+ undefined,
267
+ { cognitiveRoutingStartupPending: true },
268
+ );
269
+ assert.equal(ctx.statuses.at(-1).value, "freeflow: interaction · workflow · standard · pending");
270
+ });
271
+
272
+ test("PiFlow statusline shows the configured startup profile while pending", () => {
273
+ const ctx = context();
274
+ setModeStatus(
275
+ ctx,
276
+ { effectiveMode: "workflow", currentMode: null },
277
+ {
278
+ configured: true,
279
+ enabled: true,
280
+ configSources: { interactionContract: "builtin", skillsEnabled: "builtin", enabled: "builtin" },
281
+ interactionContract: { effective: true },
282
+ skills: { effective: true },
283
+ cognitiveRouting: {
284
+ enabled: true,
285
+ effective: true,
286
+ sessionStart: { control: "manual", profile: "reasoning" },
287
+ blockingReason: null,
288
+ },
289
+ },
290
+ undefined,
291
+ { cognitiveRoutingStartupPending: true },
292
+ );
293
+ assert.equal(ctx.statuses.at(-1).value, "freeflow: interaction · workflow · reasoning · pending");
294
+ });
295
+
296
+ test("PiFlow statusline keeps an inactive runtime blocked instead of showing startup pending", () => {
297
+ const ctx = context();
298
+ setModeStatus(
299
+ ctx,
300
+ { effectiveMode: "workflow", currentMode: null },
301
+ {
302
+ configured: true,
303
+ enabled: true,
304
+ configSources: { interactionContract: "builtin", skillsEnabled: "builtin", enabled: "builtin" },
305
+ interactionContract: { effective: true },
306
+ skills: { effective: true },
307
+ cognitiveRouting: { enabled: true, effective: true, blockingReason: null },
308
+ },
309
+ { effective: false, activeProfile: "standard", controlMode: "automatic" },
310
+ { cognitiveRoutingStartupPending: true },
311
+ );
312
+ assert.equal(ctx.statuses.at(-1).value, "freeflow: interaction · workflow · cognitive blocked · runtime_inactive");
313
+ });
314
+
315
+ test("PiFlow statusline preserves startup suppression when the host selected a model", () => {
316
+ const ctx = context();
317
+ ctx.modelStateProvenance = { explicitModel: true };
318
+ setModeStatus(
319
+ ctx,
320
+ { effectiveMode: "workflow", currentMode: null },
321
+ {
322
+ configured: true,
323
+ enabled: true,
324
+ configSources: { interactionContract: "builtin", skillsEnabled: "builtin", enabled: "builtin" },
325
+ interactionContract: { effective: true },
326
+ skills: { effective: true },
327
+ cognitiveRouting: { enabled: true, effective: true, blockingReason: null },
328
+ },
329
+ undefined,
330
+ { cognitiveRoutingStartupPending: true },
331
+ );
332
+ assert.equal(ctx.statuses.at(-1).value, "freeflow: interaction · workflow · cognitive blocked · runtime_inactive");
333
+ });
334
+
335
+ test("PiFlow empty configured sessions show standard pending before first prompt", async () => {
336
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-pending-cognitive-status-"));
337
+ try {
338
+ await mkdir(join(cwd, ".freeflow"));
339
+ await writeFile(
340
+ join(cwd, ".freeflow/config.json"),
341
+ JSON.stringify({
342
+ defaultMode: "workflow",
343
+ cognitiveRouting: {
344
+ enabled: true,
345
+ profiles: {
346
+ standard: { provider: "test", model: "model-a", thinkingLevel: "low" },
347
+ reasoning: { provider: "test", model: "model-b", thinkingLevel: "high" },
348
+ },
349
+ },
350
+ }),
351
+ "utf8",
352
+ );
353
+ const { handlers } = loadExtension();
354
+ const ctx = context(cwd);
355
+ ctx.modelRegistry = cognitiveRoutingModelRegistry();
356
+
357
+ await handlers.get("session_start")({ type: "session_start", reason: "startup" }, ctx);
358
+ assert.equal(ctx.statuses.at(-1).value, "freeflow: interaction · workflow · standard · pending");
359
+ } finally {
360
+ await rm(cwd, { recursive: true, force: true });
361
+ }
362
+ });
363
+
143
364
  test("Pi exposes bypass scope argument completions", () => {
144
365
  const { commands } = loadExtension();
145
366
  const bypassCommand = commands.find((command) => command.name === "bypass");
@@ -155,20 +376,52 @@ test("Pi exposes bypass scope argument completions", () => {
155
376
  assert.deepEqual(bypassCommand.definition.getArgumentCompletions("unknown"), []);
156
377
  });
157
378
 
158
- test("Pi describes Freeflow and Output Router argument completions", () => {
379
+ test("Pi describes Freeflow argument completions", () => {
159
380
  const { commands } = loadExtension();
160
381
  const freeflowCommand = commands.find((command) => command.name === "freeflow");
161
- const outputRouterCommand = commands.find((command) => command.name === "output-router");
162
382
  assert.ok(freeflowCommand);
163
- assert.ok(outputRouterCommand);
164
383
 
165
384
  assert.deepEqual(freeflowCommand.definition.getArgumentCompletions(""), [
166
385
  { value: "settings", label: "settings", description: "Open personal override settings" },
167
386
  { value: "status", label: "status", description: "Show effective Freeflow state" },
387
+ { value: "context", label: "context", description: "Inspect Freeflow Context" },
168
388
  { value: "mode", label: "mode", description: "Select a temporary session mode" },
389
+ { value: "profile", label: "profile", description: "Hold or release Cognitive Routing profile control" },
169
390
  { value: "enable", label: "enable", description: "Enable Freeflow for this repository" },
170
391
  { value: "disable", label: "disable", description: "Disable Freeflow for this repository" },
171
392
  ]);
393
+ assert.deepEqual(freeflowCommand.definition.getArgumentCompletions("profile "), [
394
+ {
395
+ value: "profile standard",
396
+ label: "standard",
397
+ description: "Hold standard profile manually",
398
+ },
399
+ {
400
+ value: "profile reasoning",
401
+ label: "reasoning",
402
+ description: "Hold reasoning profile manually",
403
+ },
404
+ {
405
+ value: "profile auto",
406
+ label: "auto",
407
+ description: "Release the manual hold without changing the model",
408
+ },
409
+ {
410
+ value: "profile history",
411
+ label: "history",
412
+ description: "Show Cognitive Routing transition history",
413
+ },
414
+ {
415
+ value: "profile history active",
416
+ label: "history active",
417
+ description: "Show current-branch Cognitive Routing history",
418
+ },
419
+ {
420
+ value: "profile history anomalies",
421
+ label: "history anomalies",
422
+ description: "Show Cognitive Routing history anomalies",
423
+ },
424
+ ]);
172
425
  assert.deepEqual(freeflowCommand.definition.getArgumentCompletions("settings "), [
173
426
  { value: "settings session", label: "session", description: "Override Freeflow for this Pi session" },
174
427
  { value: "settings local", label: "local", description: "Edit personal overrides for this repository" },
@@ -188,15 +441,15 @@ test("Pi describes Freeflow and Output Router argument completions", () => {
188
441
  description: "Clear the session override and use the configured default",
189
442
  },
190
443
  ]);
191
- assert.deepEqual(outputRouterCommand.definition.getArgumentCompletions(""), [
192
- { value: "settings", label: "settings", description: "Open repository Output Router settings" },
193
- { value: "status", label: "status", description: "Show effective Output Router state" },
194
- { value: "enable", label: "enable", description: "Enable Output Router for this repository" },
195
- { value: "disable", label: "disable", description: "Disable Output Router for this repository" },
444
+ assert.deepEqual(freeflowCommand.definition.getArgumentCompletions("context "), [
445
+ { value: "context status", label: "status", description: "Show Freeflow Context state" },
446
+ { value: "context list", label: "list", description: "List archived context projections" },
447
+ { value: "context restore", label: "restore", description: "Restore one or more context references" },
448
+ { value: "context reset all", label: "reset all", description: "Reset projection decisions on the active branch" },
196
449
  ]);
197
450
  });
198
451
 
199
- test("Pi exposes canonical model skills and maps published command aliases", async () => {
452
+ test("Pi exposes canonical model skills without compatibility aliases", async () => {
200
453
  const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-canonical-skills-"));
201
454
  try {
202
455
  await mkdir(join(cwd, ".freeflow"));
@@ -209,25 +462,29 @@ test("Pi exposes canonical model skills and maps published command aliases", asy
209
462
  assert.ok(match, `unexpected skill path: ${path}`);
210
463
  return match[1];
211
464
  });
212
- for (const skill of ["discuss", "execute-work", "track-work"]) {
465
+ assert.equal(skillNames.length, 26);
466
+ for (const skill of ["action-selection", "discuss", "execute-work", "track-work"]) {
213
467
  assert.ok(skillNames.includes(skill));
214
468
  }
469
+ assert.ok(!skillNames.includes("output-router"));
215
470
  assert.ok(!skillNames.includes("discover"));
216
471
  assert.ok(!skillNames.includes("execute-plan"));
217
472
  await Promise.all(resources.skillPaths.map((path) => readFile(path, "utf8")));
218
473
 
219
474
  for (const [commandName, expectedSkill] of [
475
+ ["action-selection", "action-selection"],
220
476
  ["discuss", "discuss"],
221
- ["discover", "discuss"],
222
477
  ["track-work", "track-work"],
223
478
  ["execute-work", "execute-work"],
224
- ["execute-plan", "execute-work"],
225
479
  ]) {
226
480
  const command = commands.find((candidate) => candidate.name === commandName);
227
481
  assert.ok(command);
228
482
  await command.definition.handler(undefined, context(cwd));
229
483
  assert.equal(sentMessages.at(-1), `/skill:${expectedSkill}`);
230
484
  }
485
+ for (const alias of ["discover", "execute-plan"]) {
486
+ assert.ok(!commands.some((candidate) => candidate.name === alias));
487
+ }
231
488
  } finally {
232
489
  await rm(cwd, { recursive: true, force: true });
233
490
  }
@@ -236,7 +493,7 @@ test("Pi exposes canonical model skills and maps published command aliases", asy
236
493
  test("Pi keeps Freeflow inactive until setup config exists", async () => {
237
494
  const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-missing-setup-"));
238
495
  try {
239
- const { handlers, activeToolNames } = loadExtension();
496
+ const { handlers } = loadExtension();
240
497
  const resourcesDiscover = handlers.get("resources_discover");
241
498
  const beforeAgentStart = handlers.get("before_agent_start");
242
499
  assert.ok(resourcesDiscover);
@@ -248,10 +505,6 @@ test("Pi keeps Freeflow inactive until setup config exists", async () => {
248
505
 
249
506
  const result = await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
250
507
  assert.equal(result.systemPrompt, "base prompt");
251
- assert.ok(!activeToolNames().includes("freeflow_status"));
252
- assert.ok(!activeToolNames().includes("freeflow_search"));
253
- assert.ok(!activeToolNames().includes("freeflow_run"));
254
- assert.ok(!activeToolNames().includes("freeflow_batch"));
255
508
  } finally {
256
509
  await rm(cwd, { recursive: true, force: true });
257
510
  }
@@ -269,7 +522,6 @@ test("Pi treats invalid Freeflow setup config as inactive", async () => {
269
522
  enabled: "false",
270
523
  interactionContract: "on",
271
524
  skills: { enabled: "no" },
272
- outputRouter: { enabled: true },
273
525
  },
274
526
  null,
275
527
  2,
@@ -277,15 +529,13 @@ test("Pi treats invalid Freeflow setup config as inactive", async () => {
277
529
  "utf8",
278
530
  );
279
531
 
280
- const { handlers, commands, activeToolNames } = loadExtension();
532
+ const { handlers, commands } = loadExtension();
281
533
  const resources = await handlers.get("resources_discover")({ cwd }, context(cwd));
282
534
  assert.equal(resources.skillPaths.length, 1);
283
535
  assert.match(resources.skillPaths[0], /setup-freeflow\/SKILL\.md$/);
284
536
 
285
537
  const result = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, context(cwd));
286
538
  assert.equal(result.systemPrompt, "base prompt");
287
- assert.ok(!activeToolNames().includes("freeflow_status"));
288
- assert.ok(!activeToolNames().includes("freeflow_search"));
289
539
 
290
540
  const freeflowCommand = commands.find((command) => command.name === "freeflow");
291
541
  const statusCtx = context(cwd);
@@ -356,20 +606,22 @@ test("Pi layers local core overrides over repository defaults with source eviden
356
606
  assert.deepEqual(layers.coreConfig, {
357
607
  enabled: true,
358
608
  interactionContract: false,
609
+ contextVirtualization: false,
610
+ conversationHistory: false,
359
611
  skills: { enabled: false },
360
612
  defaultMode: "strict-workflow",
361
613
  });
362
614
  assert.deepEqual(layers.sources, {
363
615
  enabled: "local",
364
616
  interactionContract: "repository",
617
+ contextVirtualization: "builtin",
618
+ conversationHistory: "builtin",
365
619
  skillsEnabled: "local",
366
620
  defaultMode: "local",
367
621
  });
368
622
  assert.equal(layers.local.parsed.processing.unsafeUnsandboxed.enabled, true);
369
- const routerConfig = await readOutputRouterConfig(cwd);
370
- assert.equal(routerConfig.localConfig.processing.unsafeUnsandboxed.enabled, true);
371
623
 
372
- const capabilityState = await readCapabilityState(cwd);
624
+ const capabilityState = await readCapabilityState(cwd, undefined, PIFLOW_HOST);
373
625
  assert.equal(capabilityState.enabled, true);
374
626
  assert.equal(capabilityState.interactionContract.effective, false);
375
627
  assert.equal(capabilityState.skills.effective, false);
@@ -403,7 +655,7 @@ test("Pi layers branch-aware session core overrides above configured values", as
403
655
 
404
656
  let result = await setSessionCoreOverride("enabled", false, ctx, pi);
405
657
  assert.equal(result.changed, true);
406
- let capabilityState = await readCapabilityState(cwd);
658
+ let capabilityState = await readCapabilityState(cwd, undefined, PIFLOW_HOST);
407
659
  assert.equal(capabilityState.enabled, false);
408
660
  assert.equal(capabilityState.configSources.enabled, "session");
409
661
  assert.deepEqual(capabilityState.sessionOverrides, { enabled: false });
@@ -419,7 +671,7 @@ test("Pi layers branch-aware session core overrides above configured values", as
419
671
  await setSessionCoreOverride("enabled", null, ctx, pi);
420
672
  await setSessionCoreOverride("interactionContract", false, ctx, pi);
421
673
  await setSessionCoreOverride("skillsEnabled", false, ctx, pi);
422
- capabilityState = await readCapabilityState(cwd);
674
+ capabilityState = await readCapabilityState(cwd, undefined, PIFLOW_HOST);
423
675
  assert.equal(capabilityState.enabled, true);
424
676
  assert.equal(capabilityState.interactionContract.effective, false);
425
677
  assert.equal(capabilityState.skills.effective, false);
@@ -429,7 +681,7 @@ test("Pi layers branch-aware session core overrides above configured values", as
429
681
  const reset = await resetSessionOverrides(ctx, pi);
430
682
  assert.equal(reset.changed, true);
431
683
  assert.equal(reset.reloadRequired, true);
432
- capabilityState = await readCapabilityState(cwd);
684
+ capabilityState = await readCapabilityState(cwd, undefined, PIFLOW_HOST);
433
685
  assert.deepEqual(capabilityState.sessionOverrides, {});
434
686
  assert.equal(capabilityState.interactionContract.effective, true);
435
687
  assert.equal(capabilityState.skills.effective, true);
@@ -449,12 +701,12 @@ test("Pi session enablement can override configured off but cannot bypass activa
449
701
  const ctx = context(cwd);
450
702
  await setSessionCoreOverride("enabled", true, ctx, pi);
451
703
 
452
- let capabilityState = await readCapabilityState(cwd);
704
+ let capabilityState = await readCapabilityState(cwd, undefined, PIFLOW_HOST);
453
705
  assert.equal(capabilityState.configured, false);
454
706
  assert.equal(capabilityState.enabled, false);
455
707
 
456
708
  await writeFile(join(cwd, ".freeflow/config.json"), "{ invalid", "utf8");
457
- capabilityState = await readCapabilityState(cwd);
709
+ capabilityState = await readCapabilityState(cwd, undefined, PIFLOW_HOST);
458
710
  assert.equal(capabilityState.configured, false);
459
711
  assert.equal(capabilityState.enabled, false);
460
712
 
@@ -463,7 +715,7 @@ test("Pi session enablement can override configured off but cannot bypass activa
463
715
  JSON.stringify({ enabled: false, defaultMode: "workflow" }),
464
716
  "utf8",
465
717
  );
466
- capabilityState = await readCapabilityState(cwd);
718
+ capabilityState = await readCapabilityState(cwd, undefined, PIFLOW_HOST);
467
719
  assert.equal(capabilityState.configured, true);
468
720
  assert.equal(capabilityState.enabled, true);
469
721
  assert.equal(capabilityState.configSources.enabled, "session");
@@ -496,7 +748,7 @@ test("Pi restores session core overrides from the active branch", async () => {
496
748
  const { handlers } = loadExtension();
497
749
  await handlers.get("session_start")({ reason: "resume" }, context(cwd, allEntries, activeBranchEntries));
498
750
 
499
- let capabilityState = await readCapabilityState(cwd);
751
+ let capabilityState = await readCapabilityState(cwd, undefined, PIFLOW_HOST);
500
752
  assert.equal(capabilityState.enabled, true);
501
753
  assert.equal(capabilityState.interactionContract.effective, false);
502
754
  assert.deepEqual(capabilityState.sessionOverrides, { interactionContract: false });
@@ -509,7 +761,7 @@ test("Pi restores session core overrides from the active branch", async () => {
509
761
  },
510
762
  ];
511
763
  await handlers.get("session_tree")({}, context(cwd, allEntries, switchedBranchEntries));
512
- capabilityState = await readCapabilityState(cwd);
764
+ capabilityState = await readCapabilityState(cwd, undefined, PIFLOW_HOST);
513
765
  assert.equal(capabilityState.enabled, false);
514
766
  assert.deepEqual(capabilityState.sessionOverrides, { enabled: false });
515
767
  } finally {
@@ -536,25 +788,29 @@ test("Pi compact mode context distinguishes layered defaults, session state, and
536
788
  assert.ok(freeflowCommand);
537
789
 
538
790
  let result = await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
539
- assert.match(result.systemPrompt, /Repository default mode: `workflow` \(repository\)/);
540
- assert.match(result.systemPrompt, /Personal default override: `strict-workflow`/);
541
- assert.match(result.systemPrompt, /Configured default mode: `strict-workflow` \(personal override\)/);
542
- assert.match(result.systemPrompt, /Session mode override: `none`/);
543
- assert.match(result.systemPrompt, /Resolved mode: `strict-workflow`/);
544
- assert.match(result.systemPrompt, /Effective Freeflow mode: `strict-workflow`/);
545
- assert.match(result.systemPrompt, /direct skill calls do not change mode/);
546
- assert.match(result.systemPrompt, /## Strict Workflow Overlay/);
547
- assert.match(result.systemPrompt, /security, privacy, billing, data loss, migrations, public interfaces/);
791
+ assert.match(result.systemPrompt, /Use the latest extension-generated Freeflow Runtime State/);
792
+ assert.doesNotMatch(result.systemPrompt, /Repository default mode:/);
793
+ assert.doesNotMatch(result.systemPrompt, /Configured default mode:/);
794
+ assert.doesNotMatch(result.systemPrompt, /Effective Freeflow mode:/);
795
+ assert.match(
796
+ result.systemPrompt,
797
+ /Task type, skill selection, direct skill calls, usefulness, or new evidence does not change it/,
798
+ );
799
+ assert.doesNotMatch(result.systemPrompt, /## Strict Workflow Overlay/);
800
+ assert.doesNotMatch(result.systemPrompt, /security, privacy, billing, data loss, migrations, public interfaces/);
548
801
  assert.doesNotMatch(result.systemPrompt, /## Conversation Mode Boundary/);
802
+ const providerContext = await handlers.get("context")({ messages: [] }, context(cwd));
803
+ assert.match(providerContext.messages.at(-1).content, /Default mode: `strict-workflow`/);
804
+ assert.match(providerContext.messages.at(-1).content, /Active mode: `strict-workflow`/);
549
805
 
550
806
  await freeflowCommand.definition.handler("mode conversation", context(cwd));
551
807
  result = await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
552
- assert.match(result.systemPrompt, /Session mode override: `conversation`/);
553
- assert.match(result.systemPrompt, /Resolved mode: `conversation`/);
554
- assert.match(result.systemPrompt, /Effective Freeflow mode: `conversation`/);
555
- assert.match(result.systemPrompt, /## Conversation Mode Boundary/);
556
- assert.match(result.systemPrompt, /Do not call write, edit, or mutating tools/);
557
- assert.match(result.systemPrompt, /an execution skill does not override this boundary/);
808
+ assert.doesNotMatch(result.systemPrompt, /Session mode override:/);
809
+ assert.doesNotMatch(result.systemPrompt, /Resolved mode:/);
810
+ assert.doesNotMatch(result.systemPrompt, /Effective Freeflow mode:/);
811
+ assert.doesNotMatch(result.systemPrompt, /## Conversation Mode Boundary/);
812
+ assert.doesNotMatch(result.systemPrompt, /Do not call write, edit, or mutating tools/);
813
+ assert.doesNotMatch(result.systemPrompt, /an execution skill does not override this boundary/);
558
814
  assert.doesNotMatch(result.systemPrompt, /## Strict Workflow Overlay/);
559
815
 
560
816
  await freeflowCommand.definition.handler("mode reset", context(cwd));
@@ -597,9 +853,13 @@ test("Pi restores layered session mode on resume and preserves it through compac
597
853
 
598
854
  await sessionCompact({ reason: "manual" }, ctx);
599
855
  let result = await beforeAgentStart({ prompt: "continue", systemPrompt: "base prompt" }, ctx);
600
- assert.match(result.systemPrompt, /Session mode override: `conversation`/);
601
- assert.match(result.systemPrompt, /Resolved mode: `conversation`/);
602
- assert.match(result.systemPrompt, /Effective Freeflow mode: `conversation`/);
856
+ assert.doesNotMatch(result.systemPrompt, /## Conversation Mode Boundary/);
857
+ assert.doesNotMatch(result.systemPrompt, /Session mode override:/);
858
+ assert.doesNotMatch(result.systemPrompt, /Resolved mode:/);
859
+ assert.doesNotMatch(result.systemPrompt, /Effective Freeflow mode:/);
860
+ let providerContext = await handlers.get("context")({ messages: [] }, ctx);
861
+ assert.match(providerContext.messages.at(-1).content, /Default mode: `strict-workflow`/);
862
+ assert.match(providerContext.messages.at(-1).content, /Active mode: `conversation`/);
603
863
 
604
864
  await writeFile(
605
865
  localPath,
@@ -620,9 +880,13 @@ test("Pi restores layered session mode on resume and preserves it through compac
620
880
  assert.equal(modeState.active, false);
621
881
  assert.equal(modeState.effectiveMode, null);
622
882
  result = await beforeAgentStart({ prompt: "continue", systemPrompt: "base prompt" }, ctx);
623
- assert.match(result.systemPrompt, /Session mode override: `conversation`/);
624
- assert.match(result.systemPrompt, /Resolved mode: `conversation` \(inactive because Skills are disabled\)/);
625
- assert.match(result.systemPrompt, /Effective Freeflow mode: `none`/);
883
+ assert.doesNotMatch(result.systemPrompt, /Session mode override:/);
884
+ assert.doesNotMatch(result.systemPrompt, /Resolved mode:/);
885
+ assert.doesNotMatch(result.systemPrompt, /Effective Freeflow mode:/);
886
+ providerContext = await handlers.get("context")({ messages: [] }, ctx);
887
+ assert.match(providerContext.messages.at(-1).content, /Default mode: `strict-workflow`/);
888
+ assert.match(providerContext.messages.at(-1).content, /Active mode: `inactive`/);
889
+ assert.match(providerContext.messages.at(-1).content, /Skills: inactive/);
626
890
 
627
891
  sessionEntries.push({
628
892
  type: "custom",
@@ -719,7 +983,10 @@ test("Pi ordinary prompts and direct skill calls do not change mode state", asyn
719
983
  assert.equal(modeState.sessionMode, null);
720
984
  assert.equal(modeState.resolvedMode, "strict-workflow");
721
985
  assert.equal(modeState.effectiveMode, "strict-workflow");
722
- assert.match(result.systemPrompt, /Task type, risk classification, and direct skill calls do not change mode/);
986
+ assert.match(
987
+ result.systemPrompt,
988
+ /Task type, skill selection, direct skill calls, usefulness, or new evidence does not change it/,
989
+ );
723
990
  assert.deepEqual(entries, []);
724
991
 
725
992
  await freeflowCommand.definition.handler("mode conversation", context(cwd));
@@ -733,8 +1000,11 @@ test("Pi ordinary prompts and direct skill calls do not change mode state", asyn
733
1000
  assert.equal(modeState.resolvedMode, "conversation");
734
1001
  assert.equal(modeState.effectiveMode, "conversation");
735
1002
  result = await beforeAgentStart({ prompt: "/commit-work", systemPrompt: "base prompt" }, context(cwd));
736
- assert.match(result.systemPrompt, /Session mode override: `conversation`/);
737
- assert.match(result.systemPrompt, /Effective Freeflow mode: `conversation`/);
1003
+ assert.doesNotMatch(result.systemPrompt, /Session mode override:/);
1004
+ assert.doesNotMatch(result.systemPrompt, /Effective Freeflow mode:/);
1005
+ const providerContext = await handlers.get("context")({ messages: [] }, context(cwd));
1006
+ assert.match(providerContext.messages.at(-1).content, /Default mode: `strict-workflow`/);
1007
+ assert.match(providerContext.messages.at(-1).content, /Active mode: `conversation`/);
738
1008
  } finally {
739
1009
  restoreModeOverride(context(cwd));
740
1010
  await rm(cwd, { recursive: true, force: true });
@@ -752,12 +1022,16 @@ test("Pi layered core config inherits omitted values and built-in defaults", asy
752
1022
  assert.deepEqual(layers.coreConfig, {
753
1023
  enabled: true,
754
1024
  interactionContract: false,
1025
+ contextVirtualization: false,
1026
+ conversationHistory: false,
755
1027
  skills: { enabled: true },
756
1028
  defaultMode: "workflow",
757
1029
  });
758
1030
  assert.deepEqual(layers.sources, {
759
1031
  enabled: "builtin",
760
1032
  interactionContract: "local",
1033
+ contextVirtualization: "builtin",
1034
+ conversationHistory: "builtin",
761
1035
  skillsEnabled: "builtin",
762
1036
  defaultMode: "builtin",
763
1037
  });
@@ -779,7 +1053,7 @@ test("Pi requires repository activation even when local overrides exist", async
779
1053
  const layers = await readFreeflowConfigLayers(cwd);
780
1054
  assert.equal(layers.repositoryConfigured, false);
781
1055
  assert.equal(layers.configured, false);
782
- const capabilityState = await readCapabilityState(cwd);
1056
+ const capabilityState = await readCapabilityState(cwd, undefined, PIFLOW_HOST);
783
1057
  assert.equal(capabilityState.enabled, false);
784
1058
  assert.equal(capabilityState.skills.effective, false);
785
1059
 
@@ -807,7 +1081,7 @@ test("Pi fails closed when an existing local override is invalid", async () => {
807
1081
  assert.equal(layers.blockingConfigPath, layers.local.path);
808
1082
  assert.match(layers.parseError, /JSON/);
809
1083
 
810
- const capabilityState = await readCapabilityState(cwd);
1084
+ const capabilityState = await readCapabilityState(cwd, undefined, PIFLOW_HOST);
811
1085
  assert.equal(capabilityState.configured, false);
812
1086
  assert.equal(capabilityState.enabled, false);
813
1087
  assert.equal(capabilityState.interactionContract.effective, false);
@@ -815,13 +1089,12 @@ test("Pi fails closed when an existing local override is invalid", async () => {
815
1089
  assert.equal(capabilityState.localConfigValid, false);
816
1090
  assert.match(capabilityState.localConfigParseError, /JSON/);
817
1091
 
818
- const { handlers, activeToolNames } = loadExtension();
1092
+ const { handlers } = loadExtension();
819
1093
  const resources = await handlers.get("resources_discover")({ cwd }, context(cwd));
820
1094
  assert.equal(resources.skillPaths.length, 1);
821
1095
  assert.match(resources.skillPaths[0], /setup-freeflow\/SKILL\.md$/);
822
1096
  const result = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, context(cwd));
823
1097
  assert.equal(result.systemPrompt, "base prompt");
824
- assert.ok(!activeToolNames().includes("freeflow_status"));
825
1098
  } finally {
826
1099
  await rm(cwd, { recursive: true, force: true });
827
1100
  }
@@ -838,7 +1111,7 @@ test("Pi fails closed when a local core override has an invalid type", async ()
838
1111
  assert.equal(layers.configured, false);
839
1112
  assert.equal(layers.local.valid, false);
840
1113
  assert.equal(layers.parseError, "skills.enabled must be a boolean");
841
- const state = await readCapabilityState(cwd);
1114
+ const state = await readCapabilityState(cwd, undefined, PIFLOW_HOST);
842
1115
  assert.equal(state.enabled, false);
843
1116
  assert.equal(state.skills.effective, false);
844
1117
  } finally {
@@ -856,7 +1129,6 @@ test("Pi local enabled false overrides an enabled repository master switch", asy
856
1129
  {
857
1130
  enabled: true,
858
1131
  defaultMode: "workflow",
859
- outputRouter: { enabled: true },
860
1132
  },
861
1133
  null,
862
1134
  2,
@@ -865,17 +1137,16 @@ test("Pi local enabled false overrides an enabled repository master switch", asy
865
1137
  );
866
1138
  await writeFile(join(cwd, ".freeflow/local.json"), JSON.stringify({ enabled: false }, null, 2), "utf8");
867
1139
 
868
- const state = await readCapabilityState(cwd);
1140
+ const state = await readCapabilityState(cwd, undefined, PIFLOW_HOST);
869
1141
  assert.equal(state.configured, true);
870
1142
  assert.equal(state.enabled, false);
871
1143
  assert.equal(state.configSources.enabled, "local");
872
- assert.equal(state.outputRouter.enabled, false);
873
1144
  } finally {
874
1145
  await rm(cwd, { recursive: true, force: true });
875
1146
  }
876
1147
  });
877
1148
 
878
- test("Pi master Freeflow toggle disables skills, capabilities, and routing", async () => {
1149
+ test("Pi master Freeflow toggle disables skills and capabilities", async () => {
879
1150
  const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-master-disabled-"));
880
1151
  try {
881
1152
  await mkdir(join(cwd, ".freeflow"));
@@ -886,7 +1157,6 @@ test("Pi master Freeflow toggle disables skills, capabilities, and routing", asy
886
1157
  enabled: false,
887
1158
  defaultMode: "workflow",
888
1159
  skills: { enabled: true },
889
- outputRouter: { enabled: true, postToolRouting: "safety-net" },
890
1160
  },
891
1161
  null,
892
1162
  2,
@@ -894,18 +1164,13 @@ test("Pi master Freeflow toggle disables skills, capabilities, and routing", asy
894
1164
  "utf8",
895
1165
  );
896
1166
 
897
- const { handlers, activeToolNames } = loadExtension();
1167
+ const { handlers } = loadExtension();
898
1168
  const resources = await handlers.get("resources_discover")({ cwd }, context(cwd));
899
1169
  assert.deepEqual(resources.skillPaths, []);
900
1170
 
901
1171
  const result = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, context(cwd));
902
- assert.match(result.systemPrompt, /# Freeflow Disabled/);
903
- assert.doesNotMatch(result.systemPrompt, /# Freeflow Interaction Contract/);
904
- assert.doesNotMatch(result.systemPrompt, /# Freeflow Runtime Kernel/);
905
- assert.doesNotMatch(result.systemPrompt, /## Loaded Workflow Skill/);
906
- assert.doesNotMatch(result.systemPrompt, /## Loaded Output Router Skill/);
907
- assert.ok(!activeToolNames().includes("freeflow_status"));
908
- assert.ok(!activeToolNames().includes("freeflow_search"));
1172
+ assert.equal(result.systemPrompt, "base prompt");
1173
+ assert.equal(result.message, undefined);
909
1174
 
910
1175
  const routed = await handlers.get("tool_result")(
911
1176
  {
@@ -922,7 +1187,7 @@ test("Pi master Freeflow toggle disables skills, capabilities, and routing", asy
922
1187
  }
923
1188
  });
924
1189
 
925
- test("Pi skills toggle suppresses workflow skills while allowing enabled router tools", async () => {
1190
+ test("Pi skills toggle suppresses workflow skills without enabling retired router tools", async () => {
926
1191
  const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-skills-disabled-router-on-"));
927
1192
  try {
928
1193
  await mkdir(join(cwd, ".freeflow"));
@@ -932,7 +1197,6 @@ test("Pi skills toggle suppresses workflow skills while allowing enabled router
932
1197
  {
933
1198
  defaultMode: "workflow",
934
1199
  skills: { enabled: false },
935
- outputRouter: { enabled: true },
936
1200
  },
937
1201
  null,
938
1202
  2,
@@ -952,18 +1216,18 @@ test("Pi skills toggle suppresses workflow skills while allowing enabled router
952
1216
  assert.doesNotMatch(result.systemPrompt, /## Loaded Workflow Skill/);
953
1217
  assert.doesNotMatch(result.systemPrompt, /## Loaded Decision Gate Skill/);
954
1218
  assert.doesNotMatch(result.systemPrompt, /## Discovery-light/);
955
- assert.match(result.systemPrompt, /Skills: disabled/);
956
- assert.match(result.systemPrompt, /Repository default mode: `workflow` \(repository\)/);
957
- assert.match(result.systemPrompt, /Personal default override: `none`/);
958
- assert.match(result.systemPrompt, /Configured default mode: `workflow` \(repository default\)/);
959
- assert.match(result.systemPrompt, /Resolved mode: `workflow` \(inactive because Skills are disabled\)/);
960
- assert.match(result.systemPrompt, /Effective Freeflow mode: `none`/);
961
- assert.match(result.systemPrompt, /## Loaded Output Router Skill/);
962
- assert.equal(ctx.statuses.at(-1).value, "freeflow: interaction · router");
963
- assert.ok(activeToolNames().includes("freeflow_status"));
964
- assert.ok(activeToolNames().includes("freeflow_search"));
965
- assert.ok(activeToolNames().includes("freeflow_run"));
966
- assert.ok(activeToolNames().includes("freeflow_batch"));
1219
+ assert.doesNotMatch(result.systemPrompt, /Repository default mode:/);
1220
+ assert.doesNotMatch(result.systemPrompt, /Personal default override:/);
1221
+ assert.doesNotMatch(result.systemPrompt, /Configured default mode:/);
1222
+ assert.doesNotMatch(result.systemPrompt, /Resolved mode:/);
1223
+ assert.doesNotMatch(result.systemPrompt, /Effective Freeflow mode:/);
1224
+ const providerContext = await handlers.get("context")({ messages: [] }, ctx);
1225
+ assert.match(providerContext.messages.at(-1).content, /Default mode: `workflow`/);
1226
+ assert.match(providerContext.messages.at(-1).content, /Active mode: `inactive`/);
1227
+ assert.match(providerContext.messages.at(-1).content, /Skills: inactive/);
1228
+ assert.doesNotMatch(result.systemPrompt, /Output Router|freeflow_(search|run|batch)/i);
1229
+ assert.equal(ctx.statuses.at(-1).value, "freeflow: interaction");
1230
+ assert.ok(!activeToolNames().includes("freeflow_context"));
967
1231
  } finally {
968
1232
  await rm(cwd, { recursive: true, force: true });
969
1233
  }
@@ -984,16 +1248,17 @@ test("Pi interaction contract can be disabled independently while Workflow stays
984
1248
  context(cwd),
985
1249
  );
986
1250
  assert.doesNotMatch(result.systemPrompt, /# Freeflow Interaction Contract/);
987
- assert.doesNotMatch(result.systemPrompt, /# Freeflow Runtime Kernel/);
988
- assert.equal(result.message.customType, "freeflow-workflow-bootstrap");
989
- assert.match(result.message.content, /# Workflow/);
1251
+ assert.equal(result.message, undefined);
1252
+ assert.match(result.systemPrompt, /# Freeflow Stable Guidance/);
1253
+ assert.match(result.systemPrompt, /## Shared Terms/);
1254
+ assert.doesNotMatch(result.systemPrompt, /# Freeflow Interaction Contract/);
990
1255
  } finally {
991
1256
  await rm(cwd, { recursive: true, force: true });
992
1257
  }
993
1258
  });
994
1259
 
995
1260
  // Regression for the "everything disabled except Freeflow control plane" state.
996
- // The model should know Freeflow exists and can be reconfigured, but should not receive workflow/router/delegation behavior.
1261
+ // The model should know Freeflow exists and can be reconfigured, but should not receive workflow behavior.
997
1262
  // `defaultMode` is dormant while Skills are off.
998
1263
  test("Pi all-disabled capability state injects only Freeflow control-plane status", async () => {
999
1264
  const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-all-capabilities-disabled-"));
@@ -1017,24 +1282,22 @@ test("Pi all-disabled capability state injects only Freeflow control-plane statu
1017
1282
  const ctx = context(cwd);
1018
1283
  const result = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
1019
1284
 
1020
- assert.match(result.systemPrompt, /# Freeflow Control Plane/);
1021
- assert.match(
1022
- result.systemPrompt,
1023
- /Freeflow is enabled for this repo, but no model-facing capabilities are enabled/,
1024
- );
1025
- assert.match(result.systemPrompt, /Repository default mode: `workflow` \(repository\)/);
1026
- assert.match(result.systemPrompt, /Resolved mode: `workflow` \(inactive because Skills are disabled\)/);
1027
- assert.match(result.systemPrompt, /Effective Freeflow mode: `none`/);
1028
- assert.doesNotMatch(result.systemPrompt, /# Freeflow Runtime Context/);
1285
+ assert.match(result.systemPrompt, /# Freeflow Stable Guidance/);
1286
+ assert.match(result.systemPrompt, /## Mode/);
1287
+ assert.doesNotMatch(result.systemPrompt, /## Shared Terms/);
1029
1288
  assert.doesNotMatch(result.systemPrompt, /# Freeflow Interaction Contract/);
1289
+ const providerContext = await handlers.get("context")({ messages: [] }, ctx);
1290
+ assert.match(providerContext.messages.at(-1).content, /Default mode: `workflow`/);
1291
+ assert.match(providerContext.messages.at(-1).content, /Active mode: `inactive`/);
1292
+ assert.match(providerContext.messages.at(-1).content, /Interaction Contract: inactive/);
1293
+ assert.match(providerContext.messages.at(-1).content, /Skills: inactive/);
1030
1294
  assert.doesNotMatch(result.systemPrompt, /# Freeflow Runtime Kernel/);
1031
1295
  assert.doesNotMatch(result.systemPrompt, /## Loaded Mode Contract Skill/);
1032
1296
  assert.doesNotMatch(result.systemPrompt, /## Loaded Workflow Skill/);
1033
1297
  assert.doesNotMatch(result.systemPrompt, /## Loaded Decision Gate Skill/);
1034
- assert.doesNotMatch(result.systemPrompt, /## Loaded Output Router Skill/);
1298
+ assert.doesNotMatch(result.systemPrompt, /Output Router|freeflow_(search|run|batch)/i);
1035
1299
  assert.equal(ctx.statuses.at(-1).value, "freeflow: idle");
1036
- assert.ok(activeToolNames().includes("freeflow_status"));
1037
- assert.ok(!activeToolNames().includes("freeflow_search"));
1300
+ assert.ok(!activeToolNames().includes("freeflow_context"));
1038
1301
 
1039
1302
  const freeflowCommand = commands.find((command) => command.name === "freeflow");
1040
1303
  assert.ok(freeflowCommand);
@@ -1310,16 +1573,14 @@ test("Pi statusline reports only effective Freeflow runtime state", async () =>
1310
1573
  await statusFor({
1311
1574
  defaultMode: "workflow",
1312
1575
  skills: { enabled: false },
1313
- outputRouter: { enabled: true },
1314
1576
  }),
1315
- "freeflow: interaction · router",
1577
+ "freeflow: interaction",
1316
1578
  );
1317
1579
  assert.equal(
1318
1580
  await statusFor({
1319
1581
  defaultMode: "workflow",
1320
- outputRouter: { enabled: true },
1321
1582
  }),
1322
- "freeflow: interaction · workflow · router",
1583
+ "freeflow: interaction · workflow",
1323
1584
  );
1324
1585
 
1325
1586
  const masterOverrideCtx = context(cwd);
@@ -1331,22 +1592,21 @@ test("Pi statusline reports only effective Freeflow runtime state", async () =>
1331
1592
  const interactionOverrideCtx = context(cwd);
1332
1593
  await setSessionCoreOverride("interactionContract", false, interactionOverrideCtx, pi);
1333
1594
  const interactionRuntime = await beforeAgentStart({ systemPrompt: "base prompt" }, interactionOverrideCtx);
1334
- assert.match(interactionRuntime.systemPrompt, /Interaction contract: disabled \(session override\)/);
1335
- assert.equal(
1336
- interactionOverrideCtx.statuses.at(-1).value,
1337
- "freeflow: interaction off (session) · workflow · router",
1338
- );
1595
+ assert.doesNotMatch(interactionRuntime.systemPrompt, /Interaction contract:/);
1596
+ const interactionProviderContext = await handlers.get("context")({ messages: [] }, interactionOverrideCtx);
1597
+ assert.match(interactionProviderContext.messages.at(-1).content, /Interaction Contract: inactive/);
1598
+ assert.equal(interactionOverrideCtx.statuses.at(-1).value, "freeflow: interaction off (session) · workflow");
1339
1599
  await resetSessionOverrides(interactionOverrideCtx, pi);
1340
1600
 
1341
1601
  const skillsOverrideCtx = context(cwd);
1342
1602
  await setSessionCoreOverride("skillsEnabled", false, skillsOverrideCtx, pi);
1343
1603
  await beforeAgentStart({ systemPrompt: "base prompt" }, skillsOverrideCtx);
1344
- assert.equal(skillsOverrideCtx.statuses.at(-1).value, "freeflow: interaction · skills off (session) · router");
1604
+ assert.equal(skillsOverrideCtx.statuses.at(-1).value, "freeflow: interaction · skills off (session)");
1345
1605
  await resetSessionOverrides(skillsOverrideCtx, pi);
1346
1606
 
1347
1607
  const modeCtx = context(cwd);
1348
1608
  await freeflowCommand.definition.handler("mode conversation", modeCtx);
1349
- assert.equal(modeCtx.statuses.at(-1).value, "freeflow: interaction · conversation (session) · router");
1609
+ assert.equal(modeCtx.statuses.at(-1).value, "freeflow: interaction · conversation (session)");
1350
1610
  await freeflowCommand.definition.handler("mode reset", context(cwd));
1351
1611
  } finally {
1352
1612
  await rm(cwd, { recursive: true, force: true });
@@ -1363,7 +1623,6 @@ test("Pi /freeflow command toggles master switch and blocks inactive settings ro
1363
1623
  {
1364
1624
  enabled: false,
1365
1625
  defaultMode: "workflow",
1366
- outputRouter: { enabled: true },
1367
1626
  },
1368
1627
  null,
1369
1628
  2,
@@ -1391,12 +1650,10 @@ test("Pi /freeflow command toggles master switch and blocks inactive settings ro
1391
1650
  const rootText = renderText(component);
1392
1651
  assert.match(rootText, /^─+/);
1393
1652
  assert.match(rootText, /Freeflow Settings/);
1394
- assert.match(rootText, /\[dim\]Output Router\[\/dim\]/);
1395
- assert.match(rootText, /enabled \(22\) \(repository\) · inactive/);
1653
+ assert.doesNotMatch(rootText, /Output Router|Native safety net/);
1396
1654
  assert.match(rootText, /\[dim\]Interaction Contract/);
1397
1655
  assert.match(rootText, /\[dim\]Skills/);
1398
- assert.match(rootText, /\[dim\].*Output Router/);
1399
- assert.doesNotMatch(rootText, /Native safety net/);
1656
+ assert.match(rootText, /Freeflow Context/);
1400
1657
  component.handleInput("\u001b[B"); // Interaction Contract row is inactive while Freeflow is off.
1401
1658
  component.handleInput("\r");
1402
1659
  component.handleInput("\u001b");
@@ -1408,7 +1665,6 @@ test("Pi /freeflow command toggles master switch and blocks inactive settings ro
1408
1665
  assert.deepEqual(afterInactiveEdit, {
1409
1666
  enabled: false,
1410
1667
  defaultMode: "workflow",
1411
- outputRouter: { enabled: true },
1412
1668
  });
1413
1669
  assert.equal(settingsCtx.reloads.length, 0);
1414
1670
 
@@ -1417,13 +1673,33 @@ test("Pi /freeflow command toggles master switch and blocks inactive settings ro
1417
1673
  const afterEnable = JSON.parse(await readFile(join(cwd, ".freeflow/config.json"), "utf8"));
1418
1674
  assert.equal(afterEnable.enabled, undefined);
1419
1675
  assert.equal(afterEnable.defaultMode, "workflow");
1420
- assert.equal(afterEnable.outputRouter.enabled, true);
1676
+ assert.equal(afterEnable.outputRouter, undefined);
1421
1677
  assert.equal(enableCtx.reloads.length, 1);
1422
1678
  } finally {
1423
1679
  await rm(cwd, { recursive: true, force: true });
1424
1680
  }
1425
1681
  });
1426
1682
 
1683
+ test("Pi statusline uses one umbrella context label for either enabled context feature", async () => {
1684
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-context-status-"));
1685
+ try {
1686
+ await mkdir(join(cwd, ".freeflow"));
1687
+ await writeFile(
1688
+ join(cwd, ".freeflow/config.json"),
1689
+ JSON.stringify({ contextVirtualization: true, conversationHistory: true }, null, 2),
1690
+ "utf8",
1691
+ );
1692
+ const { handlers } = loadExtension();
1693
+ const ctx = context(cwd);
1694
+ await handlers.get("session_start")({ reason: "startup" }, ctx);
1695
+ const status = ctx.statuses.at(-1).value;
1696
+ assert.match(status, /context/);
1697
+ assert.doesNotMatch(status, /context virtualization|conversation history/);
1698
+ } finally {
1699
+ await rm(cwd, { recursive: true, force: true });
1700
+ }
1701
+ });
1702
+
1427
1703
  test("Pi /freeflow disable applies live gates before reload completes", async () => {
1428
1704
  const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-disable-live-gates-"));
1429
1705
  try {
@@ -1433,7 +1709,6 @@ test("Pi /freeflow disable applies live gates before reload completes", async ()
1433
1709
  JSON.stringify(
1434
1710
  {
1435
1711
  defaultMode: "workflow",
1436
- outputRouter: { enabled: true },
1437
1712
  },
1438
1713
  null,
1439
1714
  2,
@@ -1441,19 +1716,14 @@ test("Pi /freeflow disable applies live gates before reload completes", async ()
1441
1716
  "utf8",
1442
1717
  );
1443
1718
 
1444
- const { commands, activeToolNames } = loadExtension();
1719
+ const { commands } = loadExtension();
1445
1720
  const freeflowCommand = commands.find((command) => command.name === "freeflow");
1446
1721
  assert.ok(freeflowCommand);
1447
- assert.ok(activeToolNames().includes("freeflow_search"));
1448
-
1449
1722
  const ctx = context(cwd);
1450
1723
  await freeflowCommand.definition.handler("disable", ctx);
1451
1724
 
1452
1725
  assert.equal(ctx.reloads.length, 1);
1453
- assert.ok(!activeToolNames().includes("freeflow_status"));
1454
- assert.ok(!activeToolNames().includes("freeflow_search"));
1455
- assert.ok(!activeToolNames().includes("freeflow_run"));
1456
- assert.ok(!activeToolNames().includes("freeflow_batch"));
1726
+ assert.equal(ctx.statuses.at(-1).value, "freeflow: off");
1457
1727
  } finally {
1458
1728
  await rm(cwd, { recursive: true, force: true });
1459
1729
  }
@@ -1481,6 +1751,7 @@ test("Pi /freeflow settings marks default mode dormant when Skills are disabled"
1481
1751
  });
1482
1752
  const rootText = renderText(component);
1483
1753
  assert.match(rootText, /Skills\s+disabled \(repository\)/);
1754
+ assert.match(rootText, /Cognitive Routing\s+disabled \(5\) disabled · inactive/);
1484
1755
  assert.match(rootText, /Session mode\s+default \(workflow · repository · inactive\)/);
1485
1756
  assert.match(rootText, /Default mode\s+workflow \(repository · inactive\)/);
1486
1757
 
@@ -1552,7 +1823,7 @@ test("Pi /freeflow settings toggles the interaction contract independently", asy
1552
1823
  context(cwd),
1553
1824
  );
1554
1825
  assert.doesNotMatch(runtime.systemPrompt, /# Freeflow Interaction Contract/);
1555
- assert.equal(runtime.message.customType, "freeflow-workflow-bootstrap");
1826
+ assert.equal(runtime.message, undefined);
1556
1827
  const enableCtx = context(cwd);
1557
1828
  enableCtx.ui.custom = async (factory) => {
1558
1829
  let result;
@@ -1600,6 +1871,7 @@ test("Pi session settings override Freeflow without changing config", async () =
1600
1871
  assert.match(rootText, /Interaction Contract\s+enabled \(builtin\)/);
1601
1872
  assert.match(rootText, /Skills\s+enabled \(builtin\)/);
1602
1873
  assert.match(rootText, /Mode\s+default \(workflow · repository\)/);
1874
+ assert.match(rootText, /Cognitive Routing\s+auto unavailable · inactive/);
1603
1875
  assert.match(rootText, /Reset session overrides\s+available/);
1604
1876
 
1605
1877
  component.handleInput("\r");
@@ -1615,7 +1887,7 @@ test("Pi session settings override Freeflow without changing config", async () =
1615
1887
  };
1616
1888
 
1617
1889
  await freeflowCommand.definition.handler("settings session", settingsCtx);
1618
- const capabilityState = await readCapabilityState(cwd);
1890
+ const capabilityState = await readCapabilityState(cwd, undefined, PIFLOW_HOST);
1619
1891
  assert.equal(capabilityState.enabled, false);
1620
1892
  assert.equal(capabilityState.configSources.enabled, "session");
1621
1893
  assert.equal(settingsCtx.reloads.length, 1);
@@ -1635,7 +1907,7 @@ test("Pi session settings override Freeflow without changing config", async () =
1635
1907
  const rootText = renderText(component);
1636
1908
  assert.match(rootText, /Freeflow\s+disabled \(session\)/);
1637
1909
  assert.match(rootText, /Interaction Contract\s+enabled \(builtin\) · inactive/);
1638
- for (let index = 0; index < 4; index += 1) component.handleInput("\u001b[B");
1910
+ for (let index = 0; index < 5; index += 1) component.handleInput("\u001b[B");
1639
1911
  component.handleInput("\r");
1640
1912
  assert.match(renderText(component), /Reset all session overrides/);
1641
1913
  component.handleInput("\r");
@@ -1643,7 +1915,7 @@ test("Pi session settings override Freeflow without changing config", async () =
1643
1915
  return result;
1644
1916
  };
1645
1917
  await freeflowCommand.definition.handler("settings session", resetCtx);
1646
- assert.equal((await readCapabilityState(cwd)).enabled, true);
1918
+ assert.equal((await readCapabilityState(cwd, undefined, PIFLOW_HOST)).enabled, true);
1647
1919
  assert.equal(resetCtx.reloads.length, 1);
1648
1920
  assert.deepEqual(entries.at(-1), {
1649
1921
  customType: "freeflow-session-overrides",
@@ -1742,8 +2014,7 @@ test("Pi settings do not report save or reload success after a mixed write failu
1742
2014
  component.handleInput("\r"); // Session mode succeeds.
1743
2015
  await component.waitForWrites();
1744
2016
 
1745
- component.handleInput("\u001b[A");
1746
- component.handleInput("\u001b[A");
2017
+ component.handleInput("\u001b[B");
1747
2018
  component.handleInput("\r");
1748
2019
  component.handleInput("\u001b[B");
1749
2020
  component.handleInput("\u001b[B");
@@ -1822,347 +2093,177 @@ test("Pi /freeflow settings repo edits shared defaults and shows local effective
1822
2093
  }
1823
2094
  });
1824
2095
 
1825
- test("Pi /freeflow settings groups capability settings", async () => {
1826
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-freeflow-grouped-settings-"));
2096
+ test("PiFlow shows unset Cognitive Routing presets as not configured and edits them while disabled", async () => {
2097
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-piflow-cognitive-not-configured-"));
1827
2098
  try {
1828
2099
  await mkdir(join(cwd, ".freeflow"));
1829
- await writeFile(
1830
- join(cwd, ".freeflow/config.json"),
1831
- JSON.stringify({ defaultMode: "workflow", outputRouter: { enabled: true } }, null, 2),
1832
- "utf8",
1833
- );
1834
-
2100
+ await writeFile(join(cwd, ".freeflow/config.json"), JSON.stringify({ defaultMode: "workflow" }), "utf8");
1835
2101
  const { commands } = loadExtension();
1836
2102
  const freeflowCommand = commands.find((command) => command.name === "freeflow");
1837
2103
  assert.ok(freeflowCommand);
1838
-
1839
2104
  const settingsCtx = context(cwd);
2105
+ settingsCtx.isIdle = () => true;
2106
+ settingsCtx.modelRegistry = cognitiveRoutingModelRegistry();
1840
2107
  settingsCtx.ui.custom = async (factory) => {
1841
2108
  let result;
1842
2109
  const component = factory({ requestRender() {} }, testTheme, {}, (value) => {
1843
2110
  result = value;
1844
2111
  });
1845
2112
  const rootText = renderText(component);
1846
- assert.match(rootText, /^─+/);
1847
- assert.match(rootText, /Output Router\s+enabled \(22\) \(repository\)/);
1848
- assert.doesNotMatch(rootText, /Native safety net/);
2113
+ assert.match(rootText, /Cognitive Routing\s+disabled \(5\) disabled/);
2114
+ assert.doesNotMatch(rootText, /Output Router/);
2115
+ for (let index = 0; index < 5; index += 1) component.handleInput("\u001b[B");
2116
+ component.handleInput("\r");
2117
+ assert.match(renderText(component), /Enabled\s+disabled/);
2118
+ assert.match(renderText(component), /Standard preset\s+not configured/);
2119
+ assert.match(renderText(component), /Reasoning preset\s+not configured/);
1849
2120
 
1850
- for (let index = 0; index < 5; index++) {
1851
- component.handleInput("\u001b[B");
1852
- }
2121
+ component.handleInput("\u001b[B");
1853
2122
  component.handleInput("\r");
1854
- const routerText = renderText(component);
1855
- assert.match(routerText, /Freeflow Settings · Personal overrides › Output Router/);
1856
- assert.match(routerText, /Native safety net/);
1857
- component.handleInput("\u001b");
1858
- component.handleInput("\u001b");
2123
+ component.handleInput("\u001b[B");
2124
+ component.handleInput("\r");
2125
+ component.handleInput("\r");
2126
+ component.handleInput("\r");
2127
+ await component.waitForWrites();
1859
2128
  return result;
1860
2129
  };
1861
2130
 
1862
2131
  await freeflowCommand.definition.handler("settings", settingsCtx);
1863
- assert.equal(settingsCtx.reloads.length, 0);
2132
+ const saved = JSON.parse(await readFile(join(cwd, ".freeflow/local.json"), "utf8"));
2133
+ assert.deepEqual(saved.cognitiveRouting.profiles.standard, {
2134
+ provider: "test",
2135
+ model: "model-a",
2136
+ thinkingLevel: "off",
2137
+ });
2138
+ assert.equal(saved.cognitiveRouting.enabled, undefined);
1864
2139
  } finally {
1865
2140
  await rm(cwd, { recursive: true, force: true });
1866
2141
  }
1867
2142
  });
1868
2143
 
1869
- test("Pi before_agent_start keeps output-router disabled by default", async () => {
1870
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-router-default-disabled-"));
2144
+ test("PiFlow settings refresh the Cognitive Routing group after enabling it", async () => {
2145
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-piflow-cognitive-hot-reload-"));
1871
2146
  try {
1872
2147
  await mkdir(join(cwd, ".freeflow"));
1873
- await writeFile(join(cwd, ".freeflow/config.json"), JSON.stringify({ defaultMode: "workflow" }, null, 2), "utf8");
1874
-
1875
- const { handlers, activeToolNames } = loadExtension();
1876
- const beforeAgentStart = handlers.get("before_agent_start");
1877
- assert.ok(beforeAgentStart);
1878
-
1879
- const result = await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
1880
-
1881
- assert.match(result.systemPrompt, /# Freeflow Interaction Contract/);
1882
- assert.doesNotMatch(result.systemPrompt, /# Freeflow Runtime Kernel/);
1883
- assert.doesNotMatch(result.systemPrompt, /## Loaded Mode Contract Skill/);
1884
- assert.doesNotMatch(result.systemPrompt, /## Loaded Workflow Skill/);
1885
- assert.doesNotMatch(result.systemPrompt, /## Loaded Decision Gate Skill/);
1886
- assert.doesNotMatch(result.systemPrompt, /## Discovery-light/);
1887
- assert.match(result.systemPrompt, /Output router: disabled/);
1888
- assert.doesNotMatch(result.systemPrompt, /## Loaded Output Router Skill/);
1889
- assert.doesNotMatch(result.systemPrompt, /freeflow_search/);
2148
+ await writeFile(
2149
+ join(cwd, ".freeflow/config.json"),
2150
+ JSON.stringify({
2151
+ defaultMode: "workflow",
2152
+ cognitiveRouting: {
2153
+ enabled: false,
2154
+ profiles: {
2155
+ standard: { provider: "test", model: "model-a", thinkingLevel: "low" },
2156
+ reasoning: { provider: "test", model: "model-b", thinkingLevel: "high" },
2157
+ },
2158
+ },
2159
+ }),
2160
+ "utf8",
2161
+ );
2162
+ const { commands } = loadExtension();
2163
+ const freeflowCommand = commands.find((command) => command.name === "freeflow");
2164
+ assert.ok(freeflowCommand);
2165
+ const settingsCtx = context(cwd);
2166
+ settingsCtx.isIdle = () => true;
2167
+ settingsCtx.modelRegistry = cognitiveRoutingModelRegistry();
2168
+ settingsCtx.ui.custom = async (factory) => {
2169
+ let result;
2170
+ const component = factory({ requestRender() {} }, testTheme, {}, (value) => {
2171
+ result = value;
2172
+ });
2173
+ assert.match(renderText(component), /Cognitive Routing\s+disabled \(5\) disabled/);
2174
+ for (let index = 0; index < 5; index += 1) component.handleInput("\u001b[B");
2175
+ component.handleInput("\r");
2176
+ component.handleInput("\r");
2177
+ component.handleInput("\u001b[B");
2178
+ component.handleInput("\r");
2179
+ await component.waitForWrites();
2180
+ component.handleInput("\u001b");
2181
+ component.handleInput("\u001b");
2182
+ assert.match(renderText(component), /Cognitive Routing\s+enabled \(5\) configured/);
2183
+ component.handleInput("\u001b");
2184
+ return result;
2185
+ };
1890
2186
 
1891
- assert.doesNotMatch(result.systemPrompt, /Legacy `FFRESULT`/);
1892
- assert.doesNotMatch(result.systemPrompt, /# Context Locality/);
1893
- assert.doesNotMatch(result.systemPrompt, /freeflow_run/);
1894
- assert.doesNotMatch(result.systemPrompt, /freeflow_capture/);
1895
- assert.doesNotMatch(result.systemPrompt, /freeflow_search action=transform/);
1896
- assert.doesNotMatch(result.systemPrompt, /Native tools stay direct/);
1897
- assert.doesNotMatch(result.systemPrompt, /## Loaded Workflow Map/);
1898
- assert.doesNotMatch(result.systemPrompt, /## Loaded Output Router Safety Policy/);
1899
- assert.doesNotMatch(result.systemPrompt, /Do not silently summarize or compress exactness-sensitive output/);
1900
- assert.doesNotMatch(result.systemPrompt, /large native read\/bash outputs may be vaulted/);
1901
- assert.doesNotMatch(result.systemPrompt, /Output-router config note/);
1902
- assert.ok(!activeToolNames().includes("freeflow_search"));
1903
- assert.ok(!activeToolNames().includes("freeflow_run"));
1904
- assert.ok(!activeToolNames().includes("freeflow_batch"));
2187
+ await freeflowCommand.definition.handler("settings", settingsCtx);
2188
+ const saved = JSON.parse(await readFile(join(cwd, ".freeflow/local.json"), "utf8"));
2189
+ assert.equal(saved.cognitiveRouting.enabled, true);
2190
+ assert.equal(settingsCtx.reloads.length, 1);
1905
2191
  } finally {
1906
2192
  await rm(cwd, { recursive: true, force: true });
1907
2193
  }
1908
2194
  });
1909
2195
 
1910
- test("Pi before_agent_start keeps the per-turn system context to the compact interaction contract", async () => {
1911
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-interaction-contract-"));
2196
+ test("Pi Freeflow Context parent summary refreshes after a child feature changes", async () => {
2197
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-context-settings-hot-reload-"));
1912
2198
  try {
1913
2199
  await mkdir(join(cwd, ".freeflow"));
1914
- await writeFile(join(cwd, ".freeflow/config.json"), JSON.stringify({ defaultMode: "workflow" }, null, 2), "utf8");
1915
-
1916
- const { handlers } = loadExtension();
1917
- const beforeAgentStart = handlers.get("before_agent_start");
1918
- assert.ok(beforeAgentStart);
1919
-
1920
- const result = await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
2200
+ await writeFile(
2201
+ join(cwd, ".freeflow/config.json"),
2202
+ JSON.stringify({ contextVirtualization: true }, null, 2),
2203
+ "utf8",
2204
+ );
1921
2205
 
1922
- assert.match(result.systemPrompt, /# Freeflow Interaction Contract/);
1923
- assert.doesNotMatch(result.systemPrompt, /# Freeflow Runtime Kernel/);
1924
- assert.match(result.systemPrompt, /Runtime delivery: confirmed for this Pi `before_agent_start` invocation/);
1925
- assert.doesNotMatch(result.systemPrompt, /## Conversation Mode Boundary/);
1926
- assert.doesNotMatch(result.systemPrompt, /## Strict Workflow Overlay/);
1927
- assert.match(result.systemPrompt, /Answer questions without inferring action/);
1928
- assert.match(result.systemPrompt, /enough shared\s+understanding for\s+the next sound action/);
1929
- assert.doesNotMatch(result.systemPrompt, /self-review|final assurance|standing authorization/i);
1930
- assert.doesNotMatch(result.systemPrompt, /## Loaded Mode Contract Skill/);
1931
- assert.doesNotMatch(result.systemPrompt, /## Loaded Workflow Skill/);
1932
- assert.doesNotMatch(result.systemPrompt, /## Loaded Decision Gate Skill/);
1933
- assert.doesNotMatch(result.systemPrompt, /## Discovery-light/);
1934
- } finally {
1935
- await rm(cwd, { recursive: true, force: true });
1936
- }
1937
- });
1938
-
1939
- test("Pi loads the full Workflow skill as one persistent first-turn message", async () => {
1940
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-workflow-bootstrap-"));
1941
- try {
1942
- const workflowSource = await readFile(new URL("../../../skills/workflow/SKILL.md", import.meta.url), "utf8");
1943
- const workflowLines = workflowSource.split(/\r?\n/);
1944
- const workflowOwnerLine = workflowLines.find((line) => line.startsWith("The active agent owns "));
1945
- const workflowSelfReviewLine = workflowLines.find((line) => line.startsWith("Self-review is silent "));
1946
- assert.ok(workflowOwnerLine, "canonical Workflow owner sentinel missing");
1947
- assert.ok(workflowSelfReviewLine, "canonical Workflow self-review sentinel missing");
1948
-
1949
- await mkdir(join(cwd, ".freeflow"));
1950
- await writeFile(join(cwd, ".freeflow/config.json"), JSON.stringify({ defaultMode: "workflow" }, null, 2), "utf8");
1951
-
1952
- const { handlers } = loadExtension();
1953
- const beforeAgentStart = handlers.get("before_agent_start");
1954
- assert.ok(beforeAgentStart);
1955
-
1956
- const sessionEntries = [];
1957
- const ctx = context(cwd, sessionEntries);
1958
- const first = await beforeAgentStart({ prompt: "hi", systemPrompt: "base prompt" }, ctx);
1959
-
1960
- assert.equal(first.message.customType, "freeflow-workflow-bootstrap");
1961
- assert.equal(first.message.display, false);
1962
- assert.match(first.message.content, /^# Freeflow Workflow Bootstrap/);
1963
- assert.match(first.message.content, /# Workflow/);
1964
- assert.ok(first.message.content.includes(workflowOwnerLine));
1965
- assert.ok(first.message.content.includes(workflowSelfReviewLine));
1966
- assert.match(first.message.content, /evidence/i);
1967
- assert.doesNotMatch(first.systemPrompt, /# Freeflow Workflow Bootstrap/);
1968
-
1969
- sessionEntries.push({
1970
- type: "custom_message",
1971
- id: "workflow-bootstrap-entry",
1972
- parentId: null,
1973
- timestamp: new Date().toISOString(),
1974
- customType: first.message.customType,
1975
- content: first.message.content,
1976
- display: first.message.display,
1977
- details: first.message.details,
1978
- });
1979
- const later = await beforeAgentStart({ prompt: "implement the feature", systemPrompt: "base prompt" }, ctx);
1980
-
1981
- assert.equal(later.message, undefined);
1982
- assert.match(later.systemPrompt, /# Freeflow Interaction Contract/);
1983
- assert.doesNotMatch(later.systemPrompt, /# Freeflow Runtime Kernel/);
1984
-
1985
- const compactedContext = context(cwd, sessionEntries, []);
1986
- const afterCompaction = await beforeAgentStart(
1987
- { prompt: "continue", systemPrompt: "base prompt" },
1988
- compactedContext,
1989
- );
1990
- assert.equal(afterCompaction.message.customType, "freeflow-workflow-bootstrap");
1991
- } finally {
1992
- await rm(cwd, { recursive: true, force: true });
1993
- }
1994
- });
1995
-
1996
- test("Pi suppresses persisted Workflow context while Skills are disabled", async () => {
1997
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-workflow-bootstrap-disabled-"));
1998
- try {
1999
- await mkdir(join(cwd, ".freeflow"));
2000
- const configPath = join(cwd, ".freeflow/config.json");
2001
- await writeFile(
2002
- configPath,
2003
- JSON.stringify({ defaultMode: "workflow", skills: { enabled: false } }, null, 2),
2004
- "utf8",
2005
- );
2206
+ const { commands } = loadExtension();
2207
+ const freeflowCommand = commands.find((command) => command.name === "freeflow");
2208
+ assert.ok(freeflowCommand);
2209
+ const settingsCtx = context(cwd);
2210
+ settingsCtx.ui.custom = async (factory) => {
2211
+ let result;
2212
+ const component = factory({ requestRender() {} }, testTheme, {}, (value) => {
2213
+ result = value;
2214
+ });
2215
+ assert.match(renderText(component), /Freeflow Context\s+enabled \(2\) 1\/2 enabled/);
2006
2216
 
2007
- const { handlers } = loadExtension();
2008
- const contextHandler = handlers.get("context");
2009
- assert.ok(contextHandler);
2217
+ for (let index = 0; index < 6; index += 1) component.handleInput("\u001b[B");
2218
+ component.handleInput("\r");
2219
+ component.handleInput("\r");
2220
+ component.handleInput("\u001b[B");
2221
+ component.handleInput("\u001b[B");
2222
+ component.handleInput("\r");
2223
+ await component.waitForWrites();
2224
+ component.handleInput("\u001b");
2010
2225
 
2011
- const workflowMessage = {
2012
- role: "custom",
2013
- customType: "freeflow-workflow-bootstrap",
2014
- content: "# Freeflow Workflow Bootstrap",
2015
- display: false,
2016
- timestamp: Date.now(),
2017
- };
2018
- const userMessage = {
2019
- role: "user",
2020
- content: "hello",
2021
- timestamp: Date.now(),
2226
+ assert.match(renderText(component), /Freeflow Context\s+disabled \(2\) 0\/2 enabled/);
2227
+ component.handleInput("\u001b");
2228
+ return result;
2022
2229
  };
2023
- const disabled = await contextHandler({ messages: [workflowMessage, userMessage] }, context(cwd));
2024
- assert.deepEqual(disabled.messages, [userMessage]);
2025
-
2026
- await writeFile(configPath, JSON.stringify({ defaultMode: "workflow" }, null, 2), "utf8");
2027
- const enabled = await contextHandler({ messages: [workflowMessage, userMessage] }, context(cwd));
2028
- assert.equal(enabled, undefined);
2029
- } finally {
2030
- await rm(cwd, { recursive: true, force: true });
2031
- }
2032
- });
2033
-
2034
- test("Pi before_agent_start injects the Freeflow interaction contract on every turn", async () => {
2035
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-core-context-"));
2036
- try {
2037
- await mkdir(join(cwd, ".freeflow"));
2038
- await writeFile(join(cwd, ".freeflow/config.json"), JSON.stringify({ defaultMode: "workflow" }, null, 2), "utf8");
2039
-
2040
- const { handlers } = loadExtension();
2041
- const beforeAgentStart = handlers.get("before_agent_start");
2042
- assert.ok(beforeAgentStart);
2043
-
2044
- const first = await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
2045
- const second = await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
2046
-
2047
- for (const result of [first, second]) {
2048
- assert.match(result.systemPrompt, /# Freeflow Runtime Context/);
2049
- assert.match(result.systemPrompt, /# Freeflow Interaction Contract/);
2050
- assert.doesNotMatch(result.systemPrompt, /# Freeflow Runtime Kernel/);
2051
- assert.match(result.systemPrompt, /Answer questions without inferring action/);
2052
- assert.doesNotMatch(result.systemPrompt, /## Freeflow Runtime Priority/);
2053
- assert.doesNotMatch(result.systemPrompt, /## Loaded Mode Contract Skill/);
2054
- assert.doesNotMatch(result.systemPrompt, /## Loaded Workflow Skill/);
2055
- assert.doesNotMatch(result.systemPrompt, /## Loaded Decision Gate Skill/);
2056
- assert.doesNotMatch(result.systemPrompt, /## Discovery-light/);
2057
- assert.match(result.systemPrompt, /Output router: disabled/);
2058
- assert.doesNotMatch(result.systemPrompt, /## Loaded Output Router Skill/);
2059
- assert.doesNotMatch(result.systemPrompt, /freeflow_search action=transform/);
2060
- assert.doesNotMatch(result.systemPrompt, /## Freeflow Output Router Reminder/);
2061
- assert.doesNotMatch(result.systemPrompt, /## Loaded Workflow Map/);
2062
- assert.doesNotMatch(result.systemPrompt, /## Loaded Output Router Safety Policy/);
2063
- }
2064
- } finally {
2065
- await rm(cwd, { recursive: true, force: true });
2066
- }
2067
- });
2068
-
2069
- test("Pi session_start and session_compact keep the interaction contract on later turns", async () => {
2070
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-session-cache-"));
2071
- try {
2072
- await mkdir(join(cwd, ".freeflow"));
2073
- await writeFile(join(cwd, ".freeflow/config.json"), JSON.stringify({ defaultMode: "workflow" }, null, 2), "utf8");
2074
-
2075
- const { handlers } = loadExtension();
2076
- const beforeAgentStart = handlers.get("before_agent_start");
2077
- const sessionStart = handlers.get("session_start");
2078
- const sessionCompact = handlers.get("session_compact");
2079
- assert.ok(beforeAgentStart);
2080
- assert.ok(sessionStart);
2081
- assert.ok(sessionCompact);
2082
-
2083
- await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
2084
- const afterFirst = await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
2085
- assert.match(afterFirst.systemPrompt, /# Freeflow Interaction Contract/);
2086
-
2087
- await sessionCompact({ reason: "manual" }, context(cwd));
2088
- const afterCompact = await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
2089
- assert.match(afterCompact.systemPrompt, /# Freeflow Interaction Contract/);
2090
- assert.doesNotMatch(afterCompact.systemPrompt, /## Loaded Workflow Skill/);
2091
- assert.doesNotMatch(afterCompact.systemPrompt, /## Loaded Output Router Skill/);
2092
-
2093
- await sessionStart({ reason: "resume" }, context(cwd));
2094
- const afterResume = await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
2095
- assert.match(afterResume.systemPrompt, /# Freeflow Interaction Contract/);
2096
- assert.doesNotMatch(afterResume.systemPrompt, /## Loaded Workflow Skill/);
2097
- assert.doesNotMatch(afterResume.systemPrompt, /## Loaded Output Router Skill/);
2098
- } finally {
2099
- await rm(cwd, { recursive: true, force: true });
2100
- }
2101
- });
2102
-
2103
- test("Pi capability config disables output-router context, active tools, and execution", async () => {
2104
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-output-router-disabled-"));
2105
- try {
2106
- await mkdir(join(cwd, ".freeflow"));
2107
- await writeFile(
2108
- join(cwd, ".freeflow/config.json"),
2109
- JSON.stringify({ defaultMode: "workflow", outputRouter: { enabled: false } }, null, 2),
2110
- "utf8",
2111
- );
2112
-
2113
- const { handlers, tools, activeToolNames } = loadExtension();
2114
- const beforeAgentStart = handlers.get("before_agent_start");
2115
- assert.ok(beforeAgentStart);
2116
-
2117
- const result = await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
2118
- assert.match(result.systemPrompt, /Output router: disabled/);
2119
- assert.doesNotMatch(result.systemPrompt, /## Loaded Output Router Skill/);
2120
- assert.ok(!activeToolNames().includes("freeflow_search"));
2121
- assert.ok(!activeToolNames().includes("freeflow_run"));
2122
- assert.ok(!activeToolNames().includes("freeflow_batch"));
2123
-
2124
- const searchTool = tools.find((tool) => tool.name === "freeflow_search");
2125
- assert.ok(searchTool);
2126
- const disabled = await searchTool.execute(
2127
- "search-disabled",
2128
- { action: "locate", query: "x" },
2129
- undefined,
2130
- undefined,
2131
- context(cwd),
2132
- );
2133
- assert.match(disabled.content[0].text, /freeflow_search\|disabled_by_config/);
2134
2230
 
2135
- const guard = handlers.get("tool_call");
2136
- const blocked = await guard({ toolName: "freeflow_search" }, context(cwd));
2137
- assert.equal(blocked.block, true);
2138
- assert.match(blocked.reason, /disabled by Freeflow config/);
2231
+ await freeflowCommand.definition.handler("settings", settingsCtx);
2232
+ const local = JSON.parse(await readFile(join(cwd, ".freeflow/local.json"), "utf8"));
2233
+ assert.equal(local.contextVirtualization, false);
2234
+ assert.equal(settingsCtx.reloads.length, 1);
2139
2235
  } finally {
2140
2236
  await rm(cwd, { recursive: true, force: true });
2141
2237
  }
2142
2238
  });
2143
2239
 
2144
- test("Pi output-router command updates config and reloads", async () => {
2145
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-capability-command-"));
2146
- try {
2147
- await mkdir(join(cwd, ".freeflow"));
2148
- await writeFile(join(cwd, ".freeflow/config.json"), JSON.stringify({}, null, 2), "utf8");
2149
-
2150
- const { commands } = loadExtension();
2151
- const outputRouterCommand = commands.find((command) => command.name === "output-router");
2152
- assert.ok(outputRouterCommand);
2153
-
2154
- const outputCtx = context(cwd);
2155
- await outputRouterCommand.definition.handler("enable", outputCtx);
2156
- const afterOutput = JSON.parse(await readFile(join(cwd, ".freeflow/config.json"), "utf8"));
2157
- assert.equal(afterOutput.outputRouter.enabled, true);
2158
- assert.equal(outputCtx.reloads.length, 1);
2159
- } finally {
2160
- await rm(cwd, { recursive: true, force: true });
2161
- }
2162
- });
2240
+ function cognitiveRoutingModelRegistry() {
2241
+ const models = [
2242
+ { provider: "test", id: "model-a", name: "Model A", reasoning: true },
2243
+ { provider: "test", id: "model-b", name: "Model B", reasoning: true },
2244
+ ];
2245
+ const supported = new Map([
2246
+ ["model-a", new Set(["off", "low", "medium", "high"])],
2247
+ ["model-b", new Set(["off", "low", "medium", "high", "max"])],
2248
+ ]);
2249
+ return {
2250
+ getAvailable() {
2251
+ return models;
2252
+ },
2253
+ find(provider, id) {
2254
+ return models.find((model) => model.provider === provider && model.id === id);
2255
+ },
2256
+ async getApiKeyAndHeaders() {
2257
+ return { ok: true };
2258
+ },
2259
+ clampThinkingLevel(model, level) {
2260
+ return supported.get(model.id)?.has(level) ? level : "off";
2261
+ },
2262
+ };
2263
+ }
2163
2264
 
2164
- test("Pi output-router settings UI toggles multiple config values and reloads once", async () => {
2165
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-output-router-settings-"));
2265
+ test("PiFlow settings persist independent new-session control and profile overrides", async () => {
2266
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-cognitive-session-start-settings-"));
2166
2267
  try {
2167
2268
  await mkdir(join(cwd, ".freeflow"));
2168
2269
  await writeFile(
@@ -2170,7 +2271,13 @@ test("Pi output-router settings UI toggles multiple config values and reloads on
2170
2271
  JSON.stringify(
2171
2272
  {
2172
2273
  defaultMode: "workflow",
2173
- outputRouter: { generatedPaths: ["graphify-out/**"] },
2274
+ cognitiveRouting: {
2275
+ enabled: true,
2276
+ profiles: {
2277
+ standard: { provider: "test", model: "model-a", thinkingLevel: "low" },
2278
+ reasoning: { provider: "test", model: "model-b", thinkingLevel: "high" },
2279
+ },
2280
+ },
2174
2281
  },
2175
2282
  null,
2176
2283
  2,
@@ -2179,46 +2286,59 @@ test("Pi output-router settings UI toggles multiple config values and reloads on
2179
2286
  );
2180
2287
 
2181
2288
  const { commands } = loadExtension();
2182
- const outputRouterCommand = commands.find((command) => command.name === "output-router");
2183
- assert.ok(outputRouterCommand);
2184
-
2185
- const ctx = context(cwd);
2186
- ctx.ui.custom = async (factory) => {
2289
+ const freeflowCommand = commands.find((command) => command.name === "freeflow");
2290
+ assert.ok(freeflowCommand);
2291
+ const settingsCtx = context(cwd);
2292
+ settingsCtx.isIdle = () => true;
2293
+ settingsCtx.modelRegistry = cognitiveRoutingModelRegistry();
2294
+ settingsCtx.ui.custom = async (factory) => {
2187
2295
  let result;
2188
2296
  const component = factory({ requestRender() {} }, testTheme, {}, (value) => {
2189
2297
  result = value;
2190
2298
  });
2191
- assert.match(renderText(component), /Output Router Settings/);
2299
+ for (let index = 0; index < 5; index += 1) component.handleInput("\u001b[B");
2300
+ component.handleInput("\r");
2301
+ assert.match(renderText(component), /Session start control\s+automatic \(builtin\)/);
2302
+ assert.match(renderText(component), /Session start profile\s+standard \(builtin\)/);
2303
+
2304
+ for (let index = 0; index < 3; index += 1) component.handleInput("\u001b[B");
2305
+ component.handleInput("\r");
2306
+ component.handleInput("\u001b[B");
2307
+ component.handleInput("\u001b[B");
2192
2308
  component.handleInput("\r");
2193
- component.handleInput("\u001b[A");
2194
- component.handleInput("\r"); // Output Router enabled
2195
2309
  await component.waitForWrites();
2196
- for (let index = 0; index < 9; index++) {
2197
- component.handleInput("\u001b[B");
2198
- }
2310
+
2311
+ component.handleInput("\u001b");
2312
+ for (let index = 0; index < 5; index += 1) component.handleInput("\u001b[B");
2313
+ component.handleInput("\r");
2314
+ for (let index = 0; index < 4; index += 1) component.handleInput("\u001b[B");
2315
+ component.handleInput("\r");
2316
+ component.handleInput("\u001b[B");
2317
+ component.handleInput("\u001b[B");
2199
2318
  component.handleInput("\r");
2200
- component.handleInput("\u001b[A");
2201
- component.handleInput("\r"); // Script transform enabled
2202
2319
  await component.waitForWrites();
2320
+
2321
+ component.handleInput("\u001b");
2322
+ for (let index = 0; index < 5; index += 1) component.handleInput("\u001b[B");
2323
+ component.handleInput("\r");
2324
+ assert.match(renderText(component), /Session start control\s+manual \(local\)/);
2325
+ assert.match(renderText(component), /Session start profile\s+reasoning \(local\)/);
2326
+ component.handleInput("\u001b");
2203
2327
  component.handleInput("\u001b");
2204
2328
  return result;
2205
2329
  };
2206
2330
 
2207
- await outputRouterCommand.definition.handler("", ctx);
2208
-
2209
- const after = JSON.parse(await readFile(join(cwd, ".freeflow/config.json"), "utf8"));
2210
- assert.equal(after.outputRouter.enabled, true);
2211
- assert.deepEqual(after.outputRouter.hints.generatedPathGlobs, ["graphify-out/**"]);
2212
- assert.equal(after.outputRouter.generatedPaths, undefined);
2213
- assert.equal(after.outputRouter.scriptTransform.enabled, true);
2214
- assert.equal(ctx.reloads.length, 1);
2331
+ await freeflowCommand.definition.handler("settings", settingsCtx);
2332
+ const saved = JSON.parse(await readFile(join(cwd, ".freeflow/local.json"), "utf8"));
2333
+ assert.deepEqual(saved.cognitiveRouting.sessionStart, { control: "manual", profile: "reasoning" });
2334
+ assert.equal(settingsCtx.reloads.length, 1);
2215
2335
  } finally {
2216
2336
  await rm(cwd, { recursive: true, force: true });
2217
2337
  }
2218
2338
  });
2219
2339
 
2220
- test("Pi output-router status summarizes master and subfeature state", async () => {
2221
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-output-router-status-"));
2340
+ test("Pi Cognitive Routing settings save a complete authenticated model and effort preset atomically", async () => {
2341
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-cognitive-settings-"));
2222
2342
  try {
2223
2343
  await mkdir(join(cwd, ".freeflow"));
2224
2344
  await writeFile(
@@ -2226,1727 +2346,498 @@ test("Pi output-router status summarizes master and subfeature state", async ()
2226
2346
  JSON.stringify(
2227
2347
  {
2228
2348
  defaultMode: "workflow",
2229
- outputRouter: {
2349
+ cognitiveRouting: {
2230
2350
  enabled: true,
2231
- postToolRouting: "safety-net",
2232
- scriptTransform: { enabled: true },
2233
- observedRouting: { enabled: true },
2351
+ profiles: {
2352
+ standard: { provider: "test", model: "model-a", thinkingLevel: "low" },
2353
+ reasoning: { provider: "test", model: "model-b", thinkingLevel: "high" },
2354
+ },
2234
2355
  },
2235
2356
  },
2236
2357
  null,
2237
2358
  2,
2238
2359
  ),
2239
- "utf8",
2240
2360
  );
2241
2361
 
2242
2362
  const { commands } = loadExtension();
2243
- const outputRouterCommand = commands.find((command) => command.name === "output-router");
2244
- assert.ok(outputRouterCommand);
2245
-
2246
- const ctx = context(cwd);
2247
- await outputRouterCommand.definition.handler("status", ctx);
2248
-
2249
- assert.equal(ctx.notifications.length, 1);
2250
- assert.match(ctx.notifications[0].message, /Output Router: enabled/);
2251
- assert.match(ctx.notifications[0].message, /script transform: enabled/);
2252
- assert.match(ctx.notifications[0].message, /observed routing: enabled/);
2253
- assert.match(ctx.notifications[0].message, /native safety net: safety-net/);
2254
- } finally {
2255
- await rm(cwd, { recursive: true, force: true });
2256
- }
2257
- });
2258
-
2259
- test("Pi freeflow_status reports effective defaults without writing config", async () => {
2260
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-status-minimal-"));
2261
- try {
2262
- await mkdir(join(cwd, ".freeflow"));
2263
- const configPath = join(cwd, ".freeflow/config.json");
2264
- const configText = JSON.stringify({ defaultMode: "workflow" }, null, 2);
2265
- await writeFile(configPath, configText, "utf8");
2266
-
2267
- const { tools } = loadExtension();
2268
- const statusTool = tools.find((tool) => tool.name === "freeflow_status");
2269
- assert.ok(statusTool);
2270
-
2271
- const result = await statusTool.execute("status-minimal", { action: "doctor" }, undefined, undefined, context(cwd));
2272
- const report = JSON.parse(result.content[0].text);
2363
+ const freeflowCommand = commands.find((command) => command.name === "freeflow");
2364
+ assert.ok(freeflowCommand);
2365
+ const settingsCtx = context(cwd);
2366
+ settingsCtx.isIdle = () => true;
2367
+ settingsCtx.modelRegistry = cognitiveRoutingModelRegistry();
2368
+ settingsCtx.ui.custom = async (factory) => {
2369
+ let result;
2370
+ const component = factory({ requestRender() {} }, testTheme, {}, (value) => {
2371
+ result = value;
2372
+ });
2373
+ for (let index = 0; index < 5; index++) component.handleInput("\u001b[B");
2374
+ component.handleInput("\r"); // Cognitive Routing group.
2375
+ component.handleInput("\u001b[B");
2376
+ component.handleInput("\r"); // Standard preset wizard.
2377
+ component.handleInput("\u001b[B");
2378
+ component.handleInput("\r"); // model-b.
2379
+ for (let index = 0; index < 4; index++) component.handleInput("\u001b[B");
2380
+ component.handleInput("\r"); // max.
2381
+ component.handleInput("\r"); // confirm save.
2382
+ await component.waitForWrites();
2383
+ return result;
2384
+ };
2273
2385
 
2274
- assert.equal(report.toolStatus, "ok");
2275
- assert.equal(report.action, "doctor");
2276
- assert.equal(report.mode.defaultMode, "workflow");
2277
- assert.deepEqual(report.effectiveConfig.interactionContract, {
2278
- enabled: true,
2279
- effective: true,
2386
+ await freeflowCommand.definition.handler("settings repo", settingsCtx);
2387
+ const saved = JSON.parse(await readFile(join(cwd, ".freeflow/config.json"), "utf8"));
2388
+ assert.deepEqual(saved.cognitiveRouting.profiles.standard, {
2389
+ provider: "test",
2390
+ model: "model-b",
2391
+ thinkingLevel: "max",
2280
2392
  });
2281
- assert.equal(report.effectiveDefaults.interactionContract, true);
2282
- assert.equal(report.effectiveConfig.outputRouter.enabled, false);
2283
- assert.equal(report.effectiveConfig.outputRouter.profile, "standard");
2284
- assert.equal(report.effectiveConfig.outputRouter.postToolRouting, "off");
2285
- assert.equal("capture" in report.effectiveConfig, false);
2286
- assert.equal("providers" in report.effectiveConfig, false);
2287
- assert.equal(report.effectiveConfig.outputRouter.observedRouting.enabled, false);
2288
- assert.deepEqual(report.effectiveConfig.outputRouter.observedRouting.mcp.servers, {});
2289
- assert.equal(report.effectiveConfig.outputRouter.scriptTransform.enabled, false);
2290
- assert.equal(report.effectiveLocalConfig.processing.unsafeUnsandboxed.enabled, false);
2291
- assert.equal(report.processing.unsafeUnsandboxed.enabled, false);
2292
- assert.equal(report.processing.unsafeUnsandboxed.status, "disabled");
2293
- assert.equal(report.localConfigExists, false);
2294
- assert.deepEqual(report.localConfigWarnings, []);
2295
- assert.equal(report.scriptTransform.enabled, false);
2296
- assert.equal(report.scriptTransform.executionStatus, "disabled");
2297
- assert.ok(["available", "unavailable"].includes(report.scriptTransform.adapterStatus));
2298
- assert.equal(report.scriptTransform.adapterContractVersion, 1);
2299
- assert.deepEqual(report.scriptTransform.configuredLanguages, ["javascript", "python", "jq"]);
2300
- const reportedLanguages = [
2301
- ...report.scriptTransform.availableLanguages,
2302
- ...report.scriptTransform.unavailableLanguages.map((entry) => entry.language),
2303
- ].sort();
2304
- assert.deepEqual(reportedLanguages, ["javascript", "jq", "python"]);
2305
- assert.ok(report.scriptTransform.requiredProofs.includes("network_access_denied"));
2306
- assert.ok(
2307
- report.scriptTransform.candidateMechanisms.some(
2308
- (candidate) => candidate.id === "node-vm" && candidate.status === "rejected",
2309
- ),
2310
- );
2311
- assert.ok(
2312
- report.scriptTransform.candidateMechanisms.some(
2313
- (candidate) => candidate.id === "os-sandbox-adapter" && candidate.status === "candidate_unproven",
2314
- ),
2315
- );
2316
- assert.equal(report.scriptTransform.network, "off");
2317
- assert.equal(report.scriptTransform.rawScriptPersistence, "disabled");
2318
- assert.equal(report.observedRouting.host.name, "pi");
2319
- assert.equal(report.observedRouting.host.outputReplacement, "available");
2320
- assert.equal(report.vaultIndex.engine, "local-json-sidecar");
2321
- assert.equal(typeof report.vaultIndex.available, "boolean");
2322
- assert.equal(report.vaultIndex.degraded, false);
2323
- assert.equal(report.vaultIndex.stale, false);
2324
- assert.equal(report.vaultIndex.rebuildRecommended, false);
2325
- assert.equal(typeof report.vaultIndex.entryCount, "number");
2326
- assert.ok(report.vaultIndex.entryCount >= 0);
2327
- assert.equal(report.observedRouting.unsupportedPersistenceModes.includes("redacted"), true);
2328
- assert.deepEqual(report.configWarnings, []);
2329
- assert.match(report.vault.root, /freeflow-router\/vault$/);
2330
- assert.ok(
2331
- [
2332
- "writable",
2333
- "missing_ancestor_writable",
2334
- "missing_ancestor_unavailable",
2335
- "not_directory",
2336
- "not_writable",
2337
- "unknown",
2338
- ].includes(report.vault.writability.status),
2339
- );
2340
- assert.equal(await readFile(configPath, "utf8"), configText);
2393
+ assert.equal(saved.cognitiveRouting.profiles.reasoning.thinkingLevel, "high");
2394
+ assert.equal(settingsCtx.reloads.length, 1);
2341
2395
  } finally {
2342
2396
  await rm(cwd, { recursive: true, force: true });
2343
2397
  }
2344
2398
  });
2345
2399
 
2346
- test("Pi freeflow_status reports layered core sources and dormant mode", async () => {
2347
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-status-layered-core-"));
2400
+ test("Pi Cognitive Routing preset cancel leaves the previous preset unchanged", async () => {
2401
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-cognitive-cancel-"));
2348
2402
  try {
2349
2403
  await mkdir(join(cwd, ".freeflow"));
2350
- await writeFile(
2351
- join(cwd, ".freeflow/config.json"),
2352
- JSON.stringify({
2404
+ const original = {
2405
+ defaultMode: "workflow",
2406
+ cognitiveRouting: {
2353
2407
  enabled: true,
2354
- interactionContract: true,
2355
- defaultMode: "workflow",
2356
- skills: { enabled: true },
2357
- }),
2358
- "utf8",
2359
- );
2360
- await writeFile(
2361
- join(cwd, ".freeflow/local.json"),
2362
- JSON.stringify({
2363
- interactionContract: false,
2364
- defaultMode: "strict-workflow",
2365
- skills: { enabled: false },
2366
- }),
2367
- "utf8",
2368
- );
2369
-
2370
- const { tools } = loadExtension();
2371
- const statusTool = tools.find((tool) => tool.name === "freeflow_status");
2372
- assert.ok(statusTool);
2373
- const result = await statusTool.execute(
2374
- "status-layered-core",
2375
- { action: "status" },
2376
- undefined,
2377
- undefined,
2378
- context(cwd),
2379
- );
2380
- const report = JSON.parse(result.content[0].text);
2381
-
2382
- assert.equal(report.configValid, true);
2383
- assert.equal(report.localConfigValid, true);
2384
- assert.deepEqual(report.configuration.sources, {
2385
- enabled: "repository",
2386
- interactionContract: "local",
2387
- skillsEnabled: "local",
2388
- defaultMode: "local",
2389
- });
2390
- assert.equal(report.effectiveConfig.enabled, true);
2391
- assert.equal(report.effectiveConfig.defaultMode, "strict-workflow");
2392
- assert.deepEqual(report.effectiveConfig.interactionContract, {
2393
- enabled: false,
2394
- effective: false,
2395
- });
2396
- assert.deepEqual(report.effectiveConfig.skills, {
2397
- enabled: false,
2398
- effective: false,
2399
- });
2400
- assert.equal(report.mode.repositoryDefaultMode, "workflow");
2401
- assert.equal(report.mode.repositoryDefaultModeSource, "repository");
2402
- assert.equal(report.mode.personalDefaultMode, "strict-workflow");
2403
- assert.equal(report.mode.defaultMode, "strict-workflow");
2404
- assert.equal(report.mode.defaultModeSource, "local");
2405
- assert.equal(report.mode.sessionMode, null);
2406
- assert.equal(report.mode.active, false);
2407
- assert.equal(report.mode.resolvedMode, "strict-workflow");
2408
- assert.equal(report.mode.effectiveMode, null);
2409
-
2410
- const expanded = renderText(statusTool.renderResult(result, { expanded: true }, testTheme));
2411
- assert.match(expanded, /repository:\s+valid/);
2412
- assert.match(expanded, /personal:\s+valid/);
2413
- assert.match(expanded, /interactionContract:\s+false\s+\(local\)/);
2414
- assert.match(expanded, /skills:\s+false\s+\(local\)/);
2415
- assert.match(expanded, /active:\s+false/);
2416
- assert.match(expanded, /effective:\s+none/);
2417
- assert.match(expanded, /resolved:\s+strict-workflow/);
2418
- assert.match(expanded, /repository default:\s+workflow\s+\(repository\)/);
2419
- assert.match(expanded, /personal default override:\s+strict-workflow/);
2420
- assert.match(expanded, /configured default:\s+strict-workflow\s+\(local\)/);
2421
- } finally {
2422
- await rm(cwd, { recursive: true, force: true });
2423
- }
2424
- });
2425
-
2426
- test("Pi freeflow_status fails closed and explains invalid local core config", async () => {
2427
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-status-invalid-local-"));
2428
- try {
2429
- await mkdir(join(cwd, ".freeflow"));
2430
- await writeFile(join(cwd, ".freeflow/config.json"), JSON.stringify({ defaultMode: "workflow" }), "utf8");
2431
- await writeFile(join(cwd, ".freeflow/local.json"), JSON.stringify({ skills: { enabled: "no" } }), "utf8");
2432
-
2433
- const { tools } = loadExtension();
2434
- const statusTool = tools.find((tool) => tool.name === "freeflow_status");
2435
- assert.ok(statusTool);
2436
- const result = await statusTool.execute(
2437
- "status-invalid-local",
2438
- { action: "doctor" },
2439
- undefined,
2440
- undefined,
2441
- context(cwd),
2442
- );
2443
- const report = JSON.parse(result.content[0].text);
2444
-
2445
- assert.equal(report.configValid, false);
2446
- assert.equal(report.repositoryConfigValid, true);
2447
- assert.equal(report.localConfigValid, false);
2448
- assert.equal(report.effectiveConfig.configured, false);
2449
- assert.equal(report.effectiveConfig.enabled, false);
2450
- assert.equal(report.mode.active, false);
2451
- assert.match(report.configuration.personal.error, /skills\.enabled/);
2452
- assert.ok(
2453
- report.localConfigWarnings.some(
2454
- (warning) => warning.includes(".freeflow/local.json is invalid") && warning.includes("runtime is inactive"),
2455
- ),
2456
- );
2457
-
2458
- const collapsed = renderText(statusTool.renderResult(result, { expanded: false }, testTheme));
2459
- assert.match(collapsed, /freeflow off/);
2460
- assert.match(collapsed, /mode dormant \(workflow\)/);
2461
- assert.match(collapsed, /warnings=1/);
2462
- } finally {
2463
- await rm(cwd, { recursive: true, force: true });
2464
- }
2465
- });
2466
-
2467
- test("Pi freeflow_status reports vault writability without creating directories", async () => {
2468
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-status-vault-"));
2469
- try {
2470
- await mkdir(join(cwd, ".freeflow"));
2471
- const nestedVault = join(cwd, "missing", "nested", "vault");
2472
- await writeFile(
2473
- join(cwd, ".freeflow/config.json"),
2474
- JSON.stringify({
2475
- defaultMode: "workflow",
2476
- outputRouter: { vaultRoot: nestedVault },
2477
- }),
2478
- "utf8",
2479
- );
2480
-
2481
- const { tools } = loadExtension();
2482
- const statusTool = tools.find((tool) => tool.name === "freeflow_status");
2483
- const missingResult = await statusTool.execute(
2484
- "status-vault-missing",
2485
- { action: "doctor" },
2486
- undefined,
2487
- undefined,
2488
- context(cwd),
2489
- );
2490
- const missingReport = JSON.parse(missingResult.content[0].text);
2491
-
2492
- assert.equal(missingReport.vault.writability.status, "missing_ancestor_writable");
2493
- await assert.rejects(readFile(nestedVault, "utf8"));
2494
-
2495
- const fileVault = join(cwd, "vault-file");
2496
- await writeFile(fileVault, "not a directory", "utf8");
2497
- await writeFile(
2498
- join(cwd, ".freeflow/config.json"),
2499
- JSON.stringify({
2500
- defaultMode: "workflow",
2501
- outputRouter: { enabled: true, vaultRoot: fileVault },
2502
- }),
2503
- "utf8",
2504
- );
2505
- const fileResult = await statusTool.execute(
2506
- "status-vault-file",
2507
- { action: "doctor" },
2508
- undefined,
2509
- undefined,
2510
- context(cwd),
2511
- );
2512
- const fileReport = JSON.parse(fileResult.content[0].text);
2513
- assert.equal(fileReport.vault.writability.status, "not_directory");
2514
- } finally {
2515
- await rm(cwd, { recursive: true, force: true });
2516
- }
2517
- });
2518
-
2519
- test("Pi freeflow_status reports local unsafe processing opt-in without shared config", async () => {
2520
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-status-local-processing-"));
2521
- try {
2522
- await mkdir(join(cwd, ".freeflow"));
2523
- await writeFile(join(cwd, ".freeflow/config.json"), JSON.stringify({ defaultMode: "workflow" }), "utf8");
2524
- await writeFile(
2525
- join(cwd, ".freeflow/local.json"),
2526
- JSON.stringify({ processing: { unsafeUnsandboxed: { enabled: true } } }),
2527
- "utf8",
2528
- );
2529
-
2530
- const { tools } = loadExtension();
2531
- const statusTool = tools.find((tool) => tool.name === "freeflow_status");
2532
- const result = await statusTool.execute(
2533
- "status-local-processing",
2534
- { action: "doctor" },
2535
- undefined,
2536
- undefined,
2537
- context(cwd),
2538
- );
2539
- const report = JSON.parse(result.content[0].text);
2540
-
2541
- assert.equal(report.localConfigExists, true);
2542
- assert.equal(report.effectiveLocalConfig.processing.unsafeUnsandboxed.enabled, true);
2543
- assert.equal(report.processing.unsafeUnsandboxed.enabled, true);
2544
- assert.equal(report.processing.unsafeUnsandboxed.status, "enabled_unsafe");
2545
- assert.deepEqual(report.configWarnings, []);
2546
- assert.deepEqual(report.localConfigWarnings, []);
2547
- } finally {
2548
- await rm(cwd, { recursive: true, force: true });
2549
- }
2550
- });
2551
-
2552
- test("Pi freeflow_status warns that shared config cannot enable unsafe processing", async () => {
2553
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-status-shared-processing-"));
2554
- try {
2555
- await mkdir(join(cwd, ".freeflow"));
2556
- await writeFile(
2557
- join(cwd, ".freeflow/config.json"),
2558
- JSON.stringify({
2559
- defaultMode: "workflow",
2560
- processing: { unsafeUnsandboxed: { enabled: true } },
2561
- }),
2562
- "utf8",
2563
- );
2564
-
2565
- const { tools } = loadExtension();
2566
- const statusTool = tools.find((tool) => tool.name === "freeflow_status");
2567
- const result = await statusTool.execute(
2568
- "status-shared-processing",
2569
- { action: "doctor" },
2570
- undefined,
2571
- undefined,
2572
- context(cwd),
2573
- );
2574
- const report = JSON.parse(result.content[0].text);
2575
-
2576
- assert.equal(report.effectiveLocalConfig.processing.unsafeUnsandboxed.enabled, false);
2577
- assert.equal(report.processing.unsafeUnsandboxed.enabled, false);
2578
- assert.ok(report.configWarnings.some((warning) => warning.includes("processing config is ignored")));
2579
- } finally {
2580
- await rm(cwd, { recursive: true, force: true });
2581
- }
2582
- });
2583
-
2584
- test("Pi freeflow_status reports configured observed routing", async () => {
2585
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-status-observed-"));
2586
- try {
2587
- await mkdir(join(cwd, ".freeflow"));
2588
- await writeFile(
2589
- join(cwd, ".freeflow/config.json"),
2590
- JSON.stringify({
2591
- defaultMode: "workflow",
2592
- outputRouter: {
2593
- enabled: true,
2594
- observedRouting: {
2595
- enabled: true,
2596
- onRoutingFailure: "fail-open",
2597
- mcp: {
2598
- servers: {
2599
- github: { enabled: true, persistence: "exact" },
2600
- gmail: { enabled: true, persistence: "metadata-only" },
2601
- },
2602
- },
2603
- web: { enabled: true, persistence: "exact" },
2604
- fetch: { enabled: false },
2605
- codeSearch: { enabled: true, persistence: "none" },
2606
- },
2607
- scriptTransform: {
2608
- enabled: true,
2609
- languages: ["python"],
2610
- limits: {
2611
- timeoutMs: 1000,
2612
- maxInputBytes: 2048,
2613
- maxOutputBytes: 4096,
2614
- },
2615
- },
2616
- },
2617
- }),
2618
- "utf8",
2619
- );
2620
-
2621
- const { tools } = loadExtension();
2622
- const statusTool = tools.find((tool) => tool.name === "freeflow_status");
2623
- const result = await statusTool.execute(
2624
- "status-observed",
2625
- { action: "doctor" },
2626
- undefined,
2627
- undefined,
2628
- context(cwd),
2629
- );
2630
- const report = JSON.parse(result.content[0].text);
2631
-
2632
- assert.equal(report.effectiveConfig.outputRouter.observedRouting.enabled, true);
2633
- assert.equal(report.effectiveConfig.outputRouter.observedRouting.onRoutingFailure, "fail-open");
2634
- assert.deepEqual(report.effectiveConfig.outputRouter.observedRouting.mcp.servers.github, {
2635
- enabled: true,
2636
- persistence: "exact",
2637
- });
2638
- assert.deepEqual(report.effectiveConfig.outputRouter.observedRouting.mcp.servers.gmail, {
2639
- enabled: true,
2640
- persistence: "metadata-only",
2641
- });
2642
- assert.deepEqual(report.effectiveConfig.outputRouter.observedRouting.web, {
2643
- enabled: true,
2644
- persistence: "exact",
2645
- });
2646
- assert.deepEqual(report.effectiveConfig.outputRouter.observedRouting.fetch, {
2647
- enabled: false,
2648
- persistence: "none",
2649
- });
2650
- assert.deepEqual(report.effectiveConfig.outputRouter.observedRouting.codeSearch, {
2651
- enabled: true,
2652
- persistence: "none",
2653
- });
2654
- assert.equal(report.observedRouting.enabled, true);
2655
- assert.equal(report.observedRouting.mcp.configuredServerCount, 2);
2656
- assert.equal(report.effectiveConfig.outputRouter.scriptTransform.enabled, true);
2657
- assert.deepEqual(report.effectiveConfig.outputRouter.scriptTransform.languages, ["python"]);
2658
- assert.equal(report.scriptTransform.enabled, true);
2659
- assert.ok(["available", "adapter_unavailable"].includes(report.scriptTransform.executionStatus));
2660
- assert.equal(typeof report.scriptTransform.adapterAvailable, "boolean");
2661
- if (report.scriptTransform.adapterAvailable) {
2662
- assert.ok(report.scriptTransform.availableLanguages.includes("python"));
2663
- }
2664
- assert.deepEqual(report.observedRouting.persistenceModes, ["exact", "metadata-only", "none"]);
2665
- assert.deepEqual(report.configWarnings, []);
2666
- } finally {
2667
- await rm(cwd, { recursive: true, force: true });
2668
- }
2669
- });
2670
-
2671
- test("Pi freeflow_status reports invalid config warnings and safe fallbacks", async () => {
2672
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-status-invalid-"));
2673
- try {
2674
- await mkdir(join(cwd, ".freeflow"));
2675
- await writeFile(
2676
- join(cwd, ".freeflow/config.json"),
2677
- JSON.stringify({
2678
- defaultMode: "workflow",
2679
- outputRouter: {
2680
- enabled: "yes",
2681
- profile: "future",
2682
- scriptTransform: {
2683
- enabled: "yes",
2684
- sandbox: "none",
2685
- languages: ["ruby"],
2686
- network: "on",
2687
- },
2688
- observedRouting: {
2689
- enabled: "yes",
2690
- mcp: {
2691
- servers: { github: { enabled: true, persistence: "redacted" } },
2692
- },
2693
- web: { enabled: true },
2694
- },
2695
- },
2696
- capture: { freeflowMediated: "metadata-only", directHostTools: "raw" },
2697
- providers: { enabled: [{ id: "serena", mode: "write" }] },
2698
- }),
2699
- "utf8",
2700
- );
2701
-
2702
- const { tools } = loadExtension();
2703
- const statusTool = tools.find((tool) => tool.name === "freeflow_status");
2704
- const result = await statusTool.execute("status-invalid", { action: "doctor" }, undefined, undefined, context(cwd));
2705
- const report = JSON.parse(result.content[0].text);
2706
-
2707
- assert.equal(report.effectiveConfig.outputRouter.enabled, false);
2708
- assert.equal(report.effectiveConfig.outputRouter.profile, "standard");
2709
- assert.equal("capture" in report.effectiveConfig, false);
2710
- assert.equal("providers" in report.effectiveConfig, false);
2711
- assert.equal(report.effectiveConfig.outputRouter.observedRouting.enabled, false);
2712
- assert.equal(report.effectiveConfig.outputRouter.scriptTransform.enabled, false);
2713
- assert.deepEqual(report.effectiveConfig.outputRouter.scriptTransform.languages, ["javascript", "python", "jq"]);
2714
- assert.equal(report.effectiveConfig.outputRouter.observedRouting.mcp.servers.github.persistence, "metadata-only");
2715
- assert.equal(report.effectiveConfig.outputRouter.observedRouting.web.persistence, "metadata-only");
2716
- assert.ok(report.configWarnings.some((warning) => warning.includes("outputRouter.enabled")));
2717
- assert.ok(report.configWarnings.some((warning) => warning.includes("outputRouter.profile")));
2718
- assert.ok(report.configWarnings.some((warning) => warning.includes("outputRouter.scriptTransform.enabled")));
2719
- assert.ok(report.configWarnings.some((warning) => warning.includes("outputRouter.scriptTransform.sandbox")));
2720
- assert.ok(report.configWarnings.some((warning) => warning.includes("outputRouter.scriptTransform.languages")));
2721
- assert.ok(report.configWarnings.some((warning) => warning.includes("outputRouter.scriptTransform.network")));
2722
- assert.ok(report.configWarnings.some((warning) => warning.includes("outputRouter.observedRouting.enabled")));
2723
- assert.ok(
2724
- report.configWarnings.some(
2725
- (warning) =>
2726
- warning.includes("outputRouter.observedRouting.mcp.servers.github.persistence") &&
2727
- warning.includes("redacted"),
2728
- ),
2729
- );
2730
- assert.ok(
2731
- report.configWarnings.some((warning) => warning.includes("outputRouter.observedRouting.web.persistence")),
2732
- );
2733
- } finally {
2734
- await rm(cwd, { recursive: true, force: true });
2735
- }
2736
- });
2737
-
2738
- test("Pi freeflow_status migration recommendations are non-destructive", async () => {
2739
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-status-migration-"));
2740
- try {
2741
- await mkdir(join(cwd, ".freeflow"));
2742
- const configPath = join(cwd, ".freeflow/config.json");
2743
- const configText = JSON.stringify(
2744
- {
2745
- defaultMode: "workflow",
2746
- outputRouter: {
2747
- enabled: true,
2748
- profile: "standard",
2749
- postToolRouting: "off",
2750
- largeOutputBytes: 64000,
2751
- largeOutputLines: 1000,
2752
- vaultRoot: "~/.cache/freeflow-router/vault",
2753
- vaultRetentionDays: 7,
2754
- },
2755
- capture: { freeflowMediated: "raw", directHostTools: "off" },
2756
- providers: { enabled: [] },
2757
- },
2758
- null,
2759
- 2,
2760
- );
2761
- await writeFile(configPath, configText, "utf8");
2762
-
2763
- const { tools } = loadExtension();
2764
- const statusTool = tools.find((tool) => tool.name === "freeflow_status");
2765
- const result = await statusTool.execute(
2766
- "status-migration",
2767
- { action: "migration" },
2768
- undefined,
2769
- undefined,
2770
- context(cwd),
2771
- );
2772
- const report = JSON.parse(result.content[0].text);
2773
-
2774
- assert.equal(report.action, "migration");
2775
- assert.equal(report.migration.applied, false);
2776
- assert.equal(report.migration.requiresConfirmation, true);
2777
- assert.ok(
2778
- report.migration.recommendations.some((recommendation) => recommendation.path === "outputRouter.postToolRouting"),
2779
- );
2780
- assert.ok(report.migration.recommendations.some((recommendation) => recommendation.path === "capture"));
2781
- assert.ok(report.migration.recommendations.some((recommendation) => recommendation.path === "providers"));
2782
- assert.equal(await readFile(configPath, "utf8"), configText);
2783
- } finally {
2784
- await rm(cwd, { recursive: true, force: true });
2785
- }
2786
- });
2787
-
2788
- test("Pi outputRouter.enabled=false suppresses router context and native safety net", async () => {
2789
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-router-disabled-"));
2790
- try {
2791
- await mkdir(join(cwd, ".freeflow"));
2792
- await writeFile(
2793
- join(cwd, ".freeflow/config.json"),
2794
- JSON.stringify({
2795
- defaultMode: "workflow",
2796
- outputRouter: {
2797
- enabled: false,
2798
- postToolRouting: "safety-net",
2799
- largeOutputLines: 1,
2800
- largeOutputBytes: 1,
2408
+ profiles: {
2409
+ standard: { provider: "test", model: "model-a", thinkingLevel: "low" },
2410
+ reasoning: { provider: "test", model: "model-b", thinkingLevel: "high" },
2801
2411
  },
2802
- }),
2803
- "utf8",
2804
- );
2805
-
2806
- const { handlers } = loadExtension();
2807
- const beforeAgentStart = handlers.get("before_agent_start");
2808
- const result = await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
2809
- assert.doesNotMatch(result.systemPrompt, /## Loaded Output Router Skill/);
2810
-
2811
- const toolResult = await handlers.get("tool_result")(
2812
- {
2813
- type: "tool_result",
2814
- toolName: "read",
2815
- toolCallId: "read-router-disabled",
2816
- input: { path: "large.txt" },
2817
- content: [{ type: "text", text: "line 1\nline 2" }],
2818
- details: undefined,
2819
- isError: false,
2820
- },
2821
- context(cwd),
2822
- );
2823
- assert.equal(toolResult, undefined);
2824
- } finally {
2825
- await rm(cwd, { recursive: true, force: true });
2826
- }
2827
- });
2828
-
2829
- test("Pi output-router context mentions native safety net only when config enables it", async () => {
2830
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-config-"));
2831
- try {
2832
- await mkdir(join(cwd, ".freeflow"));
2833
- await writeFile(
2834
- join(cwd, ".freeflow/config.json"),
2835
- JSON.stringify({
2836
- defaultMode: "workflow",
2837
- outputRouter: { enabled: true, postToolRouting: "safety-net" },
2838
- }),
2839
- "utf8",
2840
- );
2841
-
2842
- const { handlers } = loadExtension();
2843
- const beforeAgentStart = handlers.get("before_agent_start");
2844
- const result = await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
2845
-
2846
- assert.match(result.systemPrompt, /large native read\/bash outputs may be vaulted/);
2847
- assert.match(result.systemPrompt, /## Loaded Output Router Skill/);
2848
- assert.doesNotMatch(result.systemPrompt, /## Loaded Output Router Safety Policy/);
2849
- assert.match(result.systemPrompt, /freeflow_search/);
2850
- assert.match(result.systemPrompt, /freeflow_search action=transform/);
2851
- } finally {
2852
- await rm(cwd, { recursive: true, force: true });
2853
- }
2854
- });
2855
-
2856
- test("Pi freeflow_search renders compact and expanded routed evidence UI", () => {
2857
- const { tools } = loadExtension();
2858
- const searchTool = tools.find((tool) => tool.name === "freeflow_search");
2859
- assert.ok(searchTool);
2860
- assert.ok(searchTool.parameters.properties.action.enum.includes("get"));
2861
-
2862
- const call = renderText(
2863
- searchTool.renderCall(
2864
- {
2865
- action: "query",
2866
- source: {
2867
- kind: "repo",
2868
- path: "docs/codex-cli-agent-harness/passes/2026-06-12-pass-3-sandboxing-and-permissions.md",
2869
- },
2870
- query: "SandboxPermissions Plain-language meaning",
2871
- },
2872
- testTheme,
2873
- ),
2874
- 200,
2875
- );
2876
- assert.match(call, /freeflow_search query repo/);
2877
- assert.match(call, /SandboxPermissions/);
2878
-
2879
- const toolResult = {
2880
- content: [{ type: "text", text: "raw json should not be the visible UI" }],
2881
- details: {
2882
- result: {
2883
- toolStatus: "ok",
2884
- decisionId: "ffdec_test",
2885
- preserve: "important",
2886
- source: { kind: "repo", path: "docs/example.md" },
2887
- routing: {
2888
- status: "routed",
2889
- route: "search",
2890
- reason: "Deterministic test route.",
2891
- },
2892
- evidence: [
2893
- {
2894
- id: "ev_test",
2895
- source: { kind: "repo", path: "docs/example.md" },
2896
- path: "docs/example.md",
2897
- lines: "523-527",
2898
- excerpt: "### Sandbox Permissions\n\n`SandboxPermissions` is a per-command request shape.",
2899
- why: "Matched exact heading and identifier.",
2900
- window: "small",
2901
- expandable: true,
2902
- },
2903
- ],
2904
- recovery: {
2905
- how: "Use freeflow_search action=expand with evidenceId=ev_test.",
2906
- evidenceId: "ev_test",
2907
- },
2908
- },
2909
- },
2910
- };
2911
-
2912
- const collapsed = renderText(searchTool.renderResult(toolResult, { expanded: false }, testTheme));
2913
- assert.match(collapsed, /1 evidence packet/);
2914
- assert.match(collapsed, /docs\/example\.md:523-527/);
2915
- assert.match(collapsed, /ctrl\+o to expand/);
2916
- assert.doesNotMatch(collapsed, /raw json/);
2917
-
2918
- const expanded = renderText(searchTool.renderResult(toolResult, { expanded: true }, testTheme));
2919
- assert.match(expanded, /Source/);
2920
- assert.match(expanded, /preserve: important/);
2921
- assert.match(expanded, /Storage/);
2922
- assert.match(expanded, /decisionId: ffdec_test/);
2923
- assert.match(expanded, /Evidence/);
2924
- assert.match(expanded, /evidenceId: ev_test/);
2925
- assert.match(expanded, /source: repo docs\/example\.md/);
2926
- assert.match(expanded, /expandable: true/);
2927
- assert.match(expanded, /exact search: action=retrieve source.kind=repo lineRange=523-527 path=docs\/example\.md/);
2928
- assert.match(expanded, /### Sandbox Permissions/);
2929
- assert.match(expanded, /Recovery/);
2930
- assert.match(expanded, /expand hint: freeflow_search action=expand evidenceId=ev_test/);
2931
- });
2932
-
2933
- test("Pi freeflow_run exposes declarative filter schema", () => {
2934
- const { tools } = loadExtension();
2935
- const runTool = tools.find((tool) => tool.name === "freeflow_run");
2936
- assert.ok(runTool);
2937
-
2938
- assert.deepEqual(runTool.parameters.oneOf, [{ required: ["command"] }, { required: ["script"] }]);
2939
-
2940
- const script = runTool.parameters.properties.script;
2941
- assert.equal(script.type, "object");
2942
- assert.equal(script.additionalProperties, false);
2943
- assert.deepEqual(script.properties.language.enum, ["javascript", "python", "jq"]);
2944
- assert.equal(script.properties.code.minLength, 1);
2945
- assert.equal(script.properties.limits.properties.timeoutMs.maximum, 30000);
2946
- assert.deepEqual(script.required, ["language", "code"]);
2947
-
2948
- const filters = runTool.parameters.properties.filters;
2949
- assert.equal(filters.type, "object");
2950
- assert.equal(filters.additionalProperties, false);
2951
- assert.deepEqual(filters.properties.stream.enum, ["stdout", "stderr", "combined"]);
2952
- assert.equal(filters.properties.include.items.minLength, 1);
2953
- assert.equal(filters.properties.exclude.items.minLength, 1);
2954
- assert.match(filters.properties.flags.pattern, /gimsu/);
2955
- assert.equal(filters.properties.head.minimum, 1);
2956
- assert.equal(filters.properties.tail.minimum, 1);
2957
- assert.equal(filters.properties.maxLines.minimum, 1);
2958
- assert.equal(filters.properties.maxBytes.minimum, 1);
2959
-
2960
- const scriptFilter = runTool.parameters.properties.scriptFilter;
2961
- assert.equal(scriptFilter.type, "object");
2962
- assert.equal(scriptFilter.additionalProperties, false);
2963
- assert.deepEqual(scriptFilter.properties.language.enum, ["javascript", "python", "jq"]);
2964
- assert.equal(scriptFilter.properties.code.minLength, 1);
2965
- assert.equal(scriptFilter.properties.limits.properties.timeoutMs.maximum, 30000);
2966
- assert.deepEqual(scriptFilter.required, ["language", "code"]);
2967
- });
2968
-
2969
- test("Pi freeflow_run renders compact and expanded status, evidence, and vault UI", () => {
2970
- const { tools } = loadExtension();
2971
- const runTool = tools.find((tool) => tool.name === "freeflow_run");
2972
- assert.ok(runTool);
2973
-
2974
- const call = renderText(runTool.renderCall({ command: "npm test -- --runInBand", preserve: "important" }, testTheme));
2975
- assert.match(call, /freeflow_run \$ npm test/);
2976
- assert.match(call, /preserve=important/);
2977
-
2978
- const toolResult = {
2979
- content: [{ type: "text", text: "raw json should not be the visible UI" }],
2980
- details: {
2981
- result: {
2982
- toolStatus: "ok",
2983
- decisionId: "ffdec_run_test",
2984
- preserve: "important",
2985
- outputId: "ffout_test123",
2986
- recordId: "ffrec_test123",
2987
- execution: { status: "failed", exitCode: 1, durationMs: 842 },
2988
- routing: {
2989
- status: "routed",
2990
- route: "run",
2991
- reason: "Command failed; exact failure evidence was returned and raw output was vaulted before routing.",
2992
- },
2993
- summary: "Command failed with exitCode=1.",
2994
- parser: {
2995
- name: "test-runner",
2996
- confidence: 0.92,
2997
- fidelity: "exact",
2998
- compressed: true,
2999
- counts: { testsFailed: 1 },
3000
- },
3001
- persistence: {
3002
- status: "vaulted",
3003
- recoverability: "exact",
3004
- recoveryOutputId: "ffout_test123",
3005
- },
3006
- filters: {
3007
- stream: "stderr",
3008
- include: ["AssertionError"],
3009
- sourceLines: 2,
3010
- selectedLines: 1,
3011
- },
3012
- scriptFilter: {
3013
- status: "success",
3014
- language: "javascript",
3015
- label: "failures-only",
3016
- rawOutputId: "ffout_test123",
3017
- sourceAliases: ["stdout", "stderr", "combined"],
3018
- outputId: "ffout_script123",
3019
- operation: {
3020
- kind: "script",
3021
- language: "javascript",
3022
- codeSha256: "sha256_abc",
3023
- },
3024
- },
3025
- importantLines: [
3026
- {
3027
- stream: "stderr",
3028
- lines: "14-16",
3029
- excerpt: "AssertionError: expected false to equal true\nSTACK_BENCH_MARKER exact failure line",
3030
- },
3031
- ],
3032
- recovery: {
3033
- how: "Use freeflow_search with source.kind=vault and outputId=ffout_test123 to recover exact command output.",
3034
- outputId: "ffout_test123",
3035
- },
3036
- },
3037
- },
3038
- };
3039
-
3040
- const collapsed = renderText(runTool.renderResult(toolResult, { expanded: false }, testTheme));
3041
- assert.match(collapsed, /execution: failed/);
3042
- assert.match(collapsed, /routing: routed/);
3043
- assert.match(collapsed, /ffout_test123/);
3044
- assert.match(collapsed, /ffout_script123/);
3045
- assert.match(collapsed, /parser test-runner 0\.92/);
3046
- assert.match(collapsed, /raw and script output recoverable from vault/);
3047
- assert.match(collapsed, /ctrl\+o to expand/);
3048
- assert.doesNotMatch(collapsed, /raw json/);
3049
-
3050
- const expanded = renderText(
3051
- runTool.renderResult(toolResult, { expanded: true }, testTheme, {
3052
- args: { command: "npm test -- --runInBand" },
3053
- }),
3054
- );
3055
- assert.match(expanded, /Status/);
3056
- assert.match(expanded, /execution.status: failed/);
3057
- assert.match(expanded, /Storage/);
3058
- assert.match(expanded, /decisionId: ffdec_run_test/);
3059
- assert.match(expanded, /recordId: ffrec_test123/);
3060
- assert.match(expanded, /persistence: vaulted \/ exact/);
3061
- assert.match(expanded, /Filters/);
3062
- assert.match(expanded, /stream=stderr/);
3063
- assert.match(expanded, /include=AssertionError/);
3064
- assert.match(expanded, /selected=1\/2/);
3065
- assert.match(expanded, /Script filter/);
3066
- assert.match(expanded, /javascript:success/);
3067
- assert.match(expanded, /rawOutputId: ffout_test123/);
3068
- assert.match(expanded, /sources: stdout, stderr, combined/);
3069
- assert.match(expanded, /sha256_abc/);
3070
- assert.match(expanded, /Parser/);
3071
- assert.match(expanded, /confidence: 0\.92/);
3072
- assert.match(expanded, /counts:.*testsFailed/);
3073
- assert.match(expanded, /Evidence/);
3074
- assert.match(expanded, /AssertionError/);
3075
- assert.match(expanded, /Vault recovery/);
3076
- assert.match(expanded, /source.kind=vault/);
3077
- assert.match(
3078
- expanded,
3079
- /exact search: action=retrieve source.kind=vault lineRange=14-16 stream=raw outputId=ffout_script123/,
3080
- );
3081
- assert.match(expanded, /raw command starting point: freeflow_search source.kind=vault outputId=ffout_test123/);
3082
- assert.match(expanded, /details\.result/);
3083
- });
3084
-
3085
- test("Pi freeflow_run returns compact model-visible text with full structured details", async () => {
3086
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-run-compact-text-"));
3087
- try {
3088
- await mkdir(join(cwd, ".freeflow"));
3089
- await writeFile(
3090
- join(cwd, ".freeflow/config.json"),
3091
- JSON.stringify({
3092
- defaultMode: "workflow",
3093
- outputRouter: { enabled: true },
3094
- }),
3095
- "utf8",
3096
- );
3097
- const tools = [];
3098
- const pi = {
3099
- registerTool(tool) {
3100
- tools.push(tool);
3101
- },
3102
- registerCommand() {},
3103
- on() {},
3104
- appendEntry() {},
3105
- sendUserMessage() {},
3106
- async exec() {
3107
- return {
3108
- stdout: "Tests: 1 failed, 24 passed, 25 total\n",
3109
- stderr: "AssertionError: expected false to equal true\nSTACK_BENCH_MARKER exact failure line\n",
3110
- code: 1,
3111
- killed: false,
3112
- };
3113
2412
  },
3114
2413
  };
3115
- freeflowExtension(pi);
3116
- const runTool = tools.find((tool) => tool.name === "freeflow_run");
3117
- assert.ok(runTool);
3118
-
3119
- const result = await runTool.execute(
3120
- "tool-call",
3121
- { command: "npm test", goal: "verification" },
3122
- undefined,
3123
- undefined,
3124
- context(cwd),
3125
- );
2414
+ await writeFile(join(cwd, ".freeflow/config.json"), JSON.stringify(original, null, 2));
3126
2415
 
3127
- const visibleText = result.content[0].text;
3128
- const detailsText = JSON.stringify(result.details.result, null, 2);
3129
-
3130
- assert.match(visibleText, /freeflow_run\|failed/);
3131
- assert.match(visibleText, /exit=1/);
3132
- assert.match(visibleText, /raw=ffout_/);
3133
- assert.match(visibleText, /STACK_BENCH_MARKER exact failure line/);
3134
- assert.match(visibleText, /rec\|vault\|ffout_[^|]+\|stderr\|1-3/);
3135
- assert.match(visibleText, /details\.result/);
3136
- assert.doesNotMatch(visibleText, /^\s*\{/);
3137
- assert.ok(Buffer.byteLength(visibleText, "utf8") < Buffer.byteLength(detailsText, "utf8"));
3138
- assert.ok(Buffer.byteLength(visibleText, "utf8") < 900);
3139
-
3140
- assert.equal(result.details.result.toolStatus, "ok");
3141
- assert.equal(result.details.result.execution.status, "failed");
3142
- assert.equal(result.details.result.execution.exitCode, 1);
3143
- assert.ok(result.details.result.recovery.outputId.startsWith("ffout_"));
3144
- assert.ok(Array.isArray(result.details.result.importantLines));
3145
- } finally {
3146
- await rm(cwd, { recursive: true, force: true });
3147
- }
3148
- });
3149
-
3150
- test("Pi freeflow_run forwards script producer and does not call host exec when sandbox is disabled", async () => {
3151
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-run-script-producer-"));
3152
- try {
3153
- await mkdir(join(cwd, ".freeflow"));
3154
- await writeFile(
3155
- join(cwd, ".freeflow/config.json"),
3156
- JSON.stringify({
3157
- defaultMode: "workflow",
3158
- outputRouter: { enabled: true },
3159
- }),
3160
- "utf8",
3161
- );
3162
- let execCalls = 0;
3163
- const tools = [];
3164
- const pi = {
3165
- registerTool(tool) {
3166
- tools.push(tool);
3167
- },
3168
- registerCommand() {},
3169
- on() {},
3170
- appendEntry() {},
3171
- sendUserMessage() {},
3172
- async exec() {
3173
- execCalls += 1;
3174
- throw new Error("host exec should not be called for sandboxed script producers");
3175
- },
2416
+ const { commands } = loadExtension();
2417
+ const freeflowCommand = commands.find((command) => command.name === "freeflow");
2418
+ assert.ok(freeflowCommand);
2419
+ const settingsCtx = context(cwd);
2420
+ settingsCtx.isIdle = () => true;
2421
+ settingsCtx.modelRegistry = cognitiveRoutingModelRegistry();
2422
+ settingsCtx.ui.custom = async (factory) => {
2423
+ let result;
2424
+ const component = factory({ requestRender() {} }, testTheme, {}, (value) => {
2425
+ result = value;
2426
+ });
2427
+ for (let index = 0; index < 5; index++) component.handleInput("\u001b[B");
2428
+ component.handleInput("\r");
2429
+ component.handleInput("\u001b[B");
2430
+ component.handleInput("\r");
2431
+ component.handleInput("\u001b[B");
2432
+ component.handleInput("\r");
2433
+ component.handleInput("\u001b"); // Cancel at the effort step.
2434
+ component.handleInput("\u001b");
2435
+ component.handleInput("\u001b");
2436
+ return result;
3176
2437
  };
3177
- freeflowExtension(pi);
3178
- const runTool = tools.find((tool) => tool.name === "freeflow_run");
3179
- assert.ok(runTool);
3180
-
3181
- const result = await runTool.execute(
3182
- "tool-call-script-producer",
3183
- {
3184
- script: {
3185
- language: "javascript",
3186
- code: "RAW_PI_SCRIPT",
3187
- label: "pi-script",
3188
- },
3189
- },
3190
- undefined,
3191
- undefined,
3192
- context(cwd),
3193
- );
3194
2438
 
3195
- assert.equal(execCalls, 0);
3196
- assert.match(result.content[0].text, /freeflow_run\|failed/);
3197
- assert.equal(result.details.result.toolStatus, "error");
3198
- assert.equal(result.details.result.producer.kind, "script");
3199
- assert.equal(result.details.result.producer.name, "pi-script");
3200
- assert.equal(result.details.result.failure.kind, "script_transform_disabled");
3201
- assert.equal(result.details.result.scriptProducer.status, "unavailable");
3202
- assert.doesNotMatch(JSON.stringify(result), /RAW_PI_SCRIPT/);
2439
+ await freeflowCommand.definition.handler("settings repo", settingsCtx);
2440
+ assert.deepEqual(JSON.parse(await readFile(join(cwd, ".freeflow/config.json"), "utf8")), original);
2441
+ assert.equal(settingsCtx.reloads.length, 0);
3203
2442
  } finally {
3204
2443
  await rm(cwd, { recursive: true, force: true });
3205
2444
  }
3206
2445
  });
3207
2446
 
3208
- test("Pi freeflow_batch returns compact summary and preserves child details", async () => {
3209
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-batch-compact-text-"));
2447
+ test("Pi Cognitive Routing settings refuse mid-run mutation", async () => {
2448
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-cognitive-busy-"));
3210
2449
  try {
3211
2450
  await mkdir(join(cwd, ".freeflow"));
3212
- await writeFile(
3213
- join(cwd, ".freeflow/config.json"),
3214
- JSON.stringify({
3215
- defaultMode: "workflow",
3216
- outputRouter: { enabled: true, vaultRoot: join(cwd, "vault") },
3217
- }),
3218
- "utf8",
3219
- );
3220
-
3221
- const tools = [];
3222
- const pi = {
3223
- registerTool(tool) {
3224
- tools.push(tool);
3225
- },
3226
- registerCommand() {},
3227
- on() {},
3228
- appendEntry() {},
3229
- sendUserMessage() {},
3230
- async exec(_bin, args) {
3231
- const command = args?.[1] ?? "unknown";
3232
- const prefix = command.includes("one") ? "ONE" : "TWO";
3233
- return {
3234
- stdout:
3235
- Array.from({ length: 20 }, (_, index) => `${prefix}_VISIBLE_BATCH_SENTINEL_${index + 1}`).join("\n") + "\n",
3236
- stderr: "",
3237
- code: 0,
3238
- killed: false,
3239
- };
3240
- },
2451
+ const original = JSON.stringify({ defaultMode: "workflow" }, null, 2);
2452
+ await writeFile(join(cwd, ".freeflow/config.json"), original);
2453
+ const { commands } = loadExtension();
2454
+ const freeflowCommand = commands.find((command) => command.name === "freeflow");
2455
+ assert.ok(freeflowCommand);
2456
+ const settingsCtx = context(cwd);
2457
+ settingsCtx.isIdle = () => false;
2458
+ settingsCtx.ui.custom = async () => {
2459
+ throw new Error("settings UI must not open while Pi is running");
3241
2460
  };
3242
- freeflowExtension(pi);
3243
- const runTool = tools.find((tool) => tool.name === "freeflow_run");
3244
- const batchTool = tools.find((tool) => tool.name === "freeflow_batch");
3245
- assert.ok(runTool);
3246
- assert.ok(batchTool);
3247
-
3248
- const batchCtx = context(cwd);
3249
- batchCtx.sessionManager.getSessionId = () => "batch-compact";
3250
- const batch = await batchTool.execute(
3251
- "batch-call",
3252
- {
3253
- steps: [
3254
- { id: "one", kind: "run", input: { command: "fixture one" } },
3255
- { id: "two", kind: "run", input: { command: "fixture two" } },
3256
- ],
3257
- },
3258
- undefined,
3259
- undefined,
3260
- batchCtx,
3261
- );
3262
2461
 
3263
- const separateCtx = context(cwd);
3264
- separateCtx.sessionManager.getSessionId = () => "batch-separate";
3265
- const separateOne = await runTool.execute("run-one", { command: "fixture one" }, undefined, undefined, separateCtx);
3266
- const separateTwo = await runTool.execute("run-two", { command: "fixture two" }, undefined, undefined, separateCtx);
3267
- const separateVisible = `${separateOne.content[0].text}\n${separateTwo.content[0].text}`;
3268
- const visibleText = batch.content[0].text;
3269
-
3270
- assert.match(visibleText, /freeflow_batch\|routed/);
3271
- assert.match(visibleText, /steps=2/);
3272
- assert.match(visibleText, /details\.result\.steps/);
3273
- assert.doesNotMatch(visibleText, /^\s*\{/);
3274
- assert.doesNotMatch(visibleText, /VISIBLE_BATCH_SENTINEL/);
3275
- assert.ok(Buffer.byteLength(visibleText, "utf8") < Buffer.byteLength(separateVisible, "utf8"));
3276
-
3277
- const payload = batch.details.result;
3278
- assert.equal(payload.stepCount, 2);
3279
- assert.equal(payload.okCount, 2);
3280
- assert.equal(payload.failedCount, 0);
3281
- assert.equal(payload.steps[0].result.importantLines[0].excerpt.includes("ONE_VISIBLE_BATCH_SENTINEL_1"), true);
3282
- assert.equal(payload.steps[1].result.importantLines[0].excerpt.includes("TWO_VISIBLE_BATCH_SENTINEL_1"), true);
3283
-
3284
- const expanded = renderText(batchTool.renderResult(batch, { expanded: true }, testTheme));
3285
- assert.match(expanded, /Steps/);
3286
- assert.match(expanded, /#1 one/);
3287
- assert.match(expanded, /#2 two/);
3288
- assert.match(expanded, /details\.result\.steps/);
2462
+ await freeflowCommand.definition.handler("settings repo", settingsCtx);
2463
+ assert.match(settingsCtx.notifications.at(-1).message, /only while Pi is idle/);
2464
+ assert.equal(await readFile(join(cwd, ".freeflow/config.json"), "utf8"), original);
3289
2465
  } finally {
3290
2466
  await rm(cwd, { recursive: true, force: true });
3291
2467
  }
3292
2468
  });
3293
2469
 
3294
- test("Pi freeflow_batch accepts queries and renders compact answers", async () => {
3295
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-batch-query-text-"));
2470
+ test("Pi settings recheck idle state before committing a selection", async () => {
2471
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-settings-busy-commit-"));
3296
2472
  try {
3297
2473
  await mkdir(join(cwd, ".freeflow"));
3298
- await writeFile(
3299
- join(cwd, ".freeflow/config.json"),
3300
- JSON.stringify({
3301
- defaultMode: "workflow",
3302
- outputRouter: { enabled: true, vaultRoot: join(cwd, "vault") },
3303
- }),
3304
- "utf8",
3305
- );
3306
-
3307
- const tools = [];
3308
- const pi = {
3309
- registerTool(tool) {
3310
- tools.push(tool);
3311
- },
3312
- registerCommand() {},
3313
- on() {},
3314
- appendEntry() {},
3315
- sendUserMessage() {},
3316
- async exec() {
3317
- return {
3318
- stdout: "query answer source\nBATCH_QUERY_VISIBLE_FACT_99\n",
3319
- stderr: "",
3320
- code: 0,
3321
- killed: false,
3322
- };
3323
- },
2474
+ const original = JSON.stringify({ defaultMode: "workflow" }, null, 2);
2475
+ await writeFile(join(cwd, ".freeflow/config.json"), original);
2476
+ const { commands } = loadExtension();
2477
+ const freeflowCommand = commands.find((command) => command.name === "freeflow");
2478
+ assert.ok(freeflowCommand);
2479
+ const settingsCtx = context(cwd);
2480
+ let idle = true;
2481
+ settingsCtx.isIdle = () => idle;
2482
+ settingsCtx.ui.custom = async (factory) => {
2483
+ let result;
2484
+ const component = factory({ requestRender() {} }, testTheme, {}, (value) => {
2485
+ result = value;
2486
+ });
2487
+ idle = false;
2488
+ for (let index = 0; index < 4; index++) component.handleInput("\u001b[B");
2489
+ component.handleInput("\r");
2490
+ component.handleInput("\u001b[B");
2491
+ component.handleInput("\r");
2492
+ await component.waitForWrites();
2493
+ component.handleInput("\u001b");
2494
+ return result;
3324
2495
  };
3325
- freeflowExtension(pi);
3326
- const batchTool = tools.find((tool) => tool.name === "freeflow_batch");
3327
- assert.ok(batchTool);
3328
- assert.ok(batchTool.parameters.properties.queries);
3329
-
3330
- const batchCtx = context(cwd);
3331
- batchCtx.sessionManager.getSessionId = () => "batch-query-render";
3332
- const batch = await batchTool.execute(
3333
- "batch-query-call",
3334
- {
3335
- queries: ["BATCH_QUERY_VISIBLE_FACT_99"],
3336
- steps: [
3337
- {
3338
- id: "fact",
3339
- kind: "run",
3340
- input: { command: "fixture fact", preserve: "full" },
3341
- },
3342
- ],
3343
- },
3344
- undefined,
3345
- undefined,
3346
- batchCtx,
3347
- );
3348
2496
 
3349
- const visibleText = batch.content[0].text;
3350
- assert.match(visibleText, /q\|answered\|BATCH_QUERY_VISIBLE_FACT_99/);
3351
- assert.match(visibleText, /BATCH_QUERY_VISIBLE_FACT_99/);
3352
- assert.equal(batch.details.result.queries[0].status, "answered");
3353
-
3354
- const expanded = renderText(batchTool.renderResult(batch, { expanded: true }, testTheme));
3355
- assert.match(expanded, /Query answers/);
3356
- assert.match(expanded, /BATCH_QUERY_VISIBLE_FACT_99/);
2497
+ await freeflowCommand.definition.handler("settings repo", settingsCtx);
2498
+ assert.match(settingsCtx.notifications.at(-1).message, /only while Pi is idle/);
2499
+ assert.equal(await readFile(join(cwd, ".freeflow/config.json"), "utf8"), original);
3357
2500
  } finally {
3358
2501
  await rm(cwd, { recursive: true, force: true });
3359
2502
  }
3360
2503
  });
3361
2504
 
3362
- test("Pi freeflow_run uses outputRouter thresholds and vault root from repo config", async () => {
3363
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-run-config-"));
2505
+ test("Pi before_agent_start does not inject retired Output Router context", async () => {
2506
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-router-default-disabled-"));
3364
2507
  try {
3365
2508
  await mkdir(join(cwd, ".freeflow"));
3366
- await writeFile(
3367
- join(cwd, ".freeflow/config.json"),
3368
- JSON.stringify({
3369
- defaultMode: "workflow",
3370
- outputRouter: {
3371
- enabled: true,
3372
- largeOutputLines: 1,
3373
- largeOutputBytes: 10_000,
3374
- vaultRoot: join(cwd, "vault"),
3375
- },
3376
- }),
3377
- "utf8",
3378
- );
3379
-
3380
- const tools = [];
3381
- const pi = {
3382
- registerTool(tool) {
3383
- tools.push(tool);
3384
- },
3385
- registerCommand() {},
3386
- on() {},
3387
- appendEntry() {},
3388
- sendUserMessage() {},
3389
- async exec() {
3390
- return { stdout: "one\ntwo\n", stderr: "", code: 0, killed: false };
3391
- },
3392
- };
3393
- freeflowExtension(pi);
3394
- const runTool = tools.find((tool) => tool.name === "freeflow_run");
3395
- assert.ok(runTool);
3396
-
3397
- const result = await runTool.execute("tool-call", { command: "fixture" }, undefined, undefined, context(cwd));
3398
- const visibleText = result.content[0].text;
3399
- const payload = result.details.result;
3400
-
3401
- assert.match(visibleText, /freeflow_run\|success/);
3402
- assert.match(visibleText, /route=partial/);
3403
- assert.match(visibleText, /raw=ffout_/);
3404
- assert.doesNotMatch(visibleText, /^\s*\{/);
3405
- assert.equal(payload.toolStatus, "ok");
3406
- assert.equal(payload.routing.status, "partial");
3407
- assert.ok(payload.recovery.outputId.startsWith("ffout_"));
3408
- } finally {
3409
- await rm(cwd, { recursive: true, force: true });
3410
- }
3411
- });
2509
+ await writeFile(join(cwd, ".freeflow/config.json"), JSON.stringify({ defaultMode: "workflow" }, null, 2), "utf8");
3412
2510
 
3413
- test("Pi freeflow_search applies configured generated path hints", async () => {
3414
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-generated-path-hints-"));
3415
- try {
3416
- await mkdir(join(cwd, ".freeflow"));
3417
- await mkdir(join(cwd, "custom-generated"));
3418
- await writeFile(
3419
- join(cwd, ".freeflow/config.json"),
3420
- JSON.stringify({
3421
- defaultMode: "workflow",
3422
- outputRouter: {
3423
- enabled: true,
3424
- generatedPaths: ["custom-generated/**"],
3425
- },
3426
- }),
3427
- "utf8",
3428
- );
3429
- await writeFile(join(cwd, "target.md"), "PI_GENERATED_HINT_MARKER source truth", "utf8");
3430
- await writeFile(
3431
- join(cwd, "custom-generated", "decoy.md"),
3432
- `${"PI_GENERATED_HINT_MARKER source truth ".repeat(1000)}pihintsentinel`,
3433
- "utf8",
3434
- );
2511
+ const { handlers } = loadExtension();
2512
+ const beforeAgentStart = handlers.get("before_agent_start");
2513
+ assert.ok(beforeAgentStart);
3435
2514
 
3436
- const { tools } = loadExtension();
3437
- const searchTool = tools.find((tool) => tool.name === "freeflow_search");
3438
- assert.ok(searchTool);
2515
+ const result = await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
3439
2516
 
3440
- const broad = await searchTool.execute(
3441
- "search-generated-hints",
3442
- {
3443
- action: "query",
3444
- source: { kind: "repo" },
3445
- query: "PI_GENERATED_HINT_MARKER source truth",
3446
- },
3447
- undefined,
3448
- undefined,
3449
- context(cwd),
3450
- );
3451
- const broadVisible = broad.content[0].text;
3452
- const broadPayload = broad.details.result;
3453
- assert.match(broadVisible, /freeflow_search\|routed/);
3454
- assert.doesNotMatch(broadVisible, /^\s*\{/);
3455
- assert.ok(
3456
- Buffer.byteLength(broadVisible, "utf8") < Buffer.byteLength(JSON.stringify(broadPayload, null, 2), "utf8"),
3457
- );
3458
- assert.equal(broadPayload.evidence[0].path, "target.md");
3459
- assert.doesNotMatch(broadPayload.evidence[0].excerpt, /pihintsentinel/);
2517
+ assert.match(result.systemPrompt, /# Freeflow Interaction Contract/);
2518
+ assert.doesNotMatch(result.systemPrompt, /# Freeflow Runtime Kernel/);
2519
+ assert.doesNotMatch(result.systemPrompt, /## Loaded Mode Contract Skill/);
2520
+ assert.doesNotMatch(result.systemPrompt, /## Loaded Workflow Skill/);
2521
+ assert.doesNotMatch(result.systemPrompt, /## Loaded Decision Gate Skill/);
2522
+ assert.doesNotMatch(result.systemPrompt, /## Discovery-light/);
2523
+ assert.doesNotMatch(result.systemPrompt, /## Loaded Output Router Skill/);
2524
+ assert.doesNotMatch(result.systemPrompt, /freeflow_search/);
3460
2525
 
3461
- const explicit = await searchTool.execute(
3462
- "search-generated-explicit",
3463
- {
3464
- action: "query",
3465
- source: { kind: "repo", path: "custom-generated/decoy.md" },
3466
- query: "pihintsentinel",
3467
- },
3468
- undefined,
3469
- undefined,
3470
- context(cwd),
3471
- );
3472
- const explicitPayload = explicit.details.result;
3473
- assert.equal(explicitPayload.evidence[0].path, "custom-generated/decoy.md");
2526
+ assert.doesNotMatch(result.systemPrompt, /Legacy `FFRESULT`/);
2527
+ assert.doesNotMatch(result.systemPrompt, /# Context Locality/);
2528
+ assert.doesNotMatch(result.systemPrompt, /freeflow_run/);
2529
+ assert.doesNotMatch(result.systemPrompt, /freeflow_capture/);
2530
+ assert.doesNotMatch(result.systemPrompt, /freeflow_search action=transform/);
2531
+ assert.doesNotMatch(result.systemPrompt, /Native tools stay direct/);
2532
+ assert.doesNotMatch(result.systemPrompt, /## Loaded Workflow Map/);
2533
+ assert.doesNotMatch(result.systemPrompt, /## Loaded Output Router Safety Policy/);
2534
+ assert.doesNotMatch(result.systemPrompt, /Do not silently summarize or compress exactness-sensitive output/);
2535
+ assert.doesNotMatch(result.systemPrompt, /large native read\/bash outputs may be vaulted/);
2536
+ assert.doesNotMatch(result.systemPrompt, /Output-router config note/);
3474
2537
  } finally {
3475
2538
  await rm(cwd, { recursive: true, force: true });
3476
2539
  }
3477
2540
  });
3478
2541
 
3479
- test("Pi freeflow_search supports vault-wide query without outputId", async () => {
3480
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-vault-wide-query-"));
2542
+ test("Pi before_agent_start keeps the per-turn system context to the compact interaction contract", async () => {
2543
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-interaction-contract-"));
3481
2544
  try {
3482
2545
  await mkdir(join(cwd, ".freeflow"));
3483
- const vaultRoot = join(cwd, "vault");
3484
- await writeFile(
3485
- join(cwd, ".freeflow/config.json"),
3486
- JSON.stringify({
3487
- defaultMode: "workflow",
3488
- outputRouter: { enabled: true, vaultRoot },
3489
- }),
3490
- "utf8",
3491
- );
2546
+ await writeFile(join(cwd, ".freeflow/config.json"), JSON.stringify({ defaultMode: "workflow" }, null, 2), "utf8");
3492
2547
 
3493
- const sessionId = "pi-vault-wide-query-session";
3494
- const stored = await storeTextOutput(createVault({ root: vaultRoot }), {
3495
- sessionId,
3496
- sourceKind: "mcp",
3497
- raw: "PI_VAULT_WIDE_TARGET through registered Pi tool",
3498
- producer: { kind: "mcp", server: "github", tool: "search_issues" },
3499
- createdAt: "2026-06-16T00:00:00.000Z",
3500
- });
2548
+ const { handlers } = loadExtension();
2549
+ const beforeAgentStart = handlers.get("before_agent_start");
2550
+ assert.ok(beforeAgentStart);
3501
2551
 
3502
- const { tools } = loadExtension();
3503
- const searchTool = tools.find((tool) => tool.name === "freeflow_search");
3504
- assert.ok(searchTool);
3505
- const ctx = context(cwd);
3506
- ctx.sessionManager.getSessionId = () => sessionId;
2552
+ const result = await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
3507
2553
 
3508
- const result = await searchTool.execute(
3509
- "search-vault-wide-query",
3510
- {
3511
- action: "query",
3512
- source: { kind: "vault" },
3513
- query: "PI_VAULT_WIDE_TARGET",
3514
- filters: { producerKind: "mcp", server: "github" },
3515
- },
3516
- undefined,
3517
- undefined,
3518
- ctx,
3519
- );
3520
- const visibleText = result.content[0].text;
3521
- const payload = result.details.result;
3522
- assert.match(visibleText, /freeflow_search\|routed/);
3523
- assert.match(visibleText, /PI_VAULT_WIDE_TARGET/);
3524
- assert.doesNotMatch(visibleText, /^\s*\{/);
3525
- assert.equal(payload.toolStatus, "ok");
3526
- assert.equal(payload.evidence[0].source.outputId, stored.outputId);
3527
- assert.equal(payload.evidence[0].source.stream, "raw");
2554
+ assert.match(result.systemPrompt, /# Freeflow Stable Guidance/);
2555
+ assert.match(result.systemPrompt, /# Freeflow Interaction Contract/);
2556
+ assert.match(result.systemPrompt, /## Shared Terms/);
2557
+ assert.match(result.systemPrompt, /## Workflow Cue/);
2558
+ assert.doesNotMatch(result.systemPrompt, /# Freeflow Runtime Kernel/);
2559
+ assert.doesNotMatch(result.systemPrompt, /## Conversation Mode Boundary/);
2560
+ assert.doesNotMatch(result.systemPrompt, /## Strict Workflow Overlay/);
2561
+ assert.match(result.systemPrompt, /Treat questions, criticism, examples, hypotheses, and tentative ideas as/);
2562
+ assert.match(result.systemPrompt, /When a turn mixes a direct question with a request or approval to/);
2563
+ assert.match(result.systemPrompt, /With a clear action request, recommend brief discussion/);
2564
+ assert.match(result.systemPrompt, /self-review/);
2565
+ assert.doesNotMatch(result.systemPrompt, /final assurance|standing authorization/i);
2566
+ assert.doesNotMatch(result.systemPrompt, /## Loaded Mode Contract Skill/);
2567
+ assert.doesNotMatch(result.systemPrompt, /## Loaded Workflow Skill/);
2568
+ assert.doesNotMatch(result.systemPrompt, /## Loaded Decision Gate Skill/);
2569
+ assert.doesNotMatch(result.systemPrompt, /## Discovery-light/);
3528
2570
  } finally {
3529
2571
  await rm(cwd, { recursive: true, force: true });
3530
2572
  }
3531
2573
  });
3532
2574
 
3533
- test("Pi reports invalid outputRouter config warnings", async () => {
3534
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-invalid-config-"));
2575
+ test("Pi exposes Workflow through discovery rather than a persistent bootstrap", async () => {
2576
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-workflow-discovery-"));
3535
2577
  try {
3536
2578
  await mkdir(join(cwd, ".freeflow"));
3537
- await writeFile(
3538
- join(cwd, ".freeflow/config.json"),
3539
- JSON.stringify({
3540
- defaultMode: "workflow",
3541
- outputRouter: { postToolRouting: "always" },
3542
- }),
3543
- "utf8",
3544
- );
2579
+ await writeFile(join(cwd, ".freeflow/config.json"), JSON.stringify({ defaultMode: "workflow" }), "utf8");
3545
2580
 
3546
2581
  const { handlers } = loadExtension();
3547
- const notifications = [];
3548
2582
  const ctx = context(cwd);
3549
- ctx.ui.notify = (message, level) => notifications.push({ message, level });
3550
- await handlers.get("session_start")({}, ctx);
2583
+ const resources = await handlers.get("resources_discover")({ cwd }, ctx);
2584
+ assert.ok(resources.skillPaths.some((path) => path.endsWith("/skills/workflow/SKILL.md")));
3551
2585
 
3552
- assert.equal(notifications[0].level, "warning");
3553
- assert.match(notifications[0].message, /postToolRouting/);
2586
+ const result = await handlers.get("before_agent_start")({ prompt: "hi", systemPrompt: "base prompt" }, ctx);
2587
+ assert.equal(result.message, undefined);
2588
+ assert.match(result.systemPrompt, /## Workflow Cue/);
2589
+ assert.doesNotMatch(result.systemPrompt, /^# Workflow$/m);
2590
+ assert.doesNotMatch(result.systemPrompt, /freeflow-workflow-bootstrap|Freeflow Workflow Bootstrap/);
3554
2591
  } finally {
3555
2592
  await rm(cwd, { recursive: true, force: true });
3556
2593
  }
3557
2594
  });
3558
2595
 
3559
- test("Pi observed routing vaults and labels configured MCP output before native safety net", async () => {
3560
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-observed-mcp-"));
2596
+ test("Pi suppresses persisted Workflow context while Skills are disabled", async () => {
2597
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-workflow-bootstrap-disabled-"));
3561
2598
  try {
3562
2599
  await mkdir(join(cwd, ".freeflow"));
2600
+ const configPath = join(cwd, ".freeflow/config.json");
3563
2601
  await writeFile(
3564
- join(cwd, ".freeflow/config.json"),
3565
- JSON.stringify({
3566
- defaultMode: "workflow",
3567
- outputRouter: {
3568
- enabled: true,
3569
- vaultRoot: join(cwd, "vault"),
3570
- observedRouting: {
3571
- enabled: true,
3572
- mcp: {
3573
- servers: { github: { enabled: true, persistence: "exact" } },
3574
- },
3575
- },
3576
- },
3577
- }),
2602
+ configPath,
2603
+ JSON.stringify({ defaultMode: "workflow", skills: { enabled: false } }, null, 2),
3578
2604
  "utf8",
3579
2605
  );
3580
2606
 
3581
2607
  const { handlers } = loadExtension();
3582
- const toolResult = handlers.get("tool_result");
3583
- const result = await toolResult(
3584
- {
3585
- type: "tool_result",
3586
- toolName: "mcp",
3587
- toolCallId: "mcp-1",
3588
- input: { server: "github", tool: "search_issues" },
3589
- content: [
3590
- {
3591
- type: "text",
3592
- text: JSON.stringify({
3593
- items: [
3594
- {
3595
- id: 1,
3596
- title: "Alpha",
3597
- html_url: "https://github.com/acme/repo/issues/1",
3598
- body: "x".repeat(500),
3599
- },
3600
- {
3601
- id: 2,
3602
- title: "Beta",
3603
- html_url: "https://github.com/acme/repo/issues/2",
3604
- body: "y".repeat(500),
3605
- },
3606
- {
3607
- id: 3,
3608
- title: "Gamma",
3609
- html_url: "https://github.com/acme/repo/issues/3",
3610
- body: "z".repeat(500),
3611
- },
3612
- {
3613
- id: 4,
3614
- title: "Delta",
3615
- html_url: "https://github.com/acme/repo/issues/4",
3616
- body: "w".repeat(500),
3617
- },
3618
- ],
3619
- }),
3620
- },
3621
- ],
3622
- details: undefined,
3623
- isError: false,
3624
- },
3625
- context(cwd),
3626
- );
3627
-
3628
- assert.ok(result);
3629
- assert.match(result.content[0].text, /Freeflow routed this observed mcp result/);
3630
- assert.match(result.content[0].text, /outputId=ffout_/);
3631
- assert.match(result.content[0].text, /Alpha/);
3632
- assert.match(result.content[0].text, /https:\/\/github\.com\/acme\/repo\/issues\/1/);
3633
- assert.doesNotMatch(result.content[0].text, /xxxxxxxxxxxxxxxx/);
3634
- assert.equal(result.details.freeflowObservedRouting.route, "observed");
3635
- assert.equal(result.details.freeflowObservedRouting.producer.server, "github");
3636
- } finally {
3637
- await rm(cwd, { recursive: true, force: true });
3638
- }
3639
- });
2608
+ const contextHandler = handlers.get("context");
2609
+ assert.ok(contextHandler);
3640
2610
 
3641
- test("Pi observed routing leaves disabled MCP producer result unchanged", async () => {
3642
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-observed-disabled-"));
3643
- try {
3644
- await mkdir(join(cwd, ".freeflow"));
3645
- await writeFile(
3646
- join(cwd, ".freeflow/config.json"),
3647
- JSON.stringify({
3648
- defaultMode: "workflow",
3649
- outputRouter: {
3650
- enabled: true,
3651
- observedRouting: {
3652
- enabled: true,
3653
- mcp: { servers: { github: { enabled: false } } },
3654
- },
3655
- },
3656
- }),
3657
- "utf8",
2611
+ const workflowMessage = {
2612
+ role: "custom",
2613
+ customType: "freeflow-workflow-bootstrap",
2614
+ content: "# Freeflow Workflow Bootstrap",
2615
+ display: false,
2616
+ timestamp: Date.now(),
2617
+ };
2618
+ const userMessage = {
2619
+ role: "user",
2620
+ content: "hello",
2621
+ timestamp: Date.now(),
2622
+ };
2623
+ const disabled = await contextHandler({ messages: [workflowMessage, userMessage] }, context(cwd));
2624
+ assert.deepEqual(
2625
+ disabled.messages.filter((message) => message.customType !== "freeflow-runtime-state"),
2626
+ [userMessage],
3658
2627
  );
2628
+ assert.equal(disabled.messages.at(-1).customType, "freeflow-runtime-state");
3659
2629
 
3660
- const { handlers } = loadExtension();
3661
- const result = await handlers.get("tool_result")(
3662
- {
3663
- type: "tool_result",
3664
- toolName: "mcp",
3665
- toolCallId: "mcp-disabled",
3666
- input: { server: "github", tool: "search_issues" },
3667
- content: [{ type: "text", text: "unchanged" }],
3668
- details: undefined,
3669
- isError: false,
3670
- },
3671
- context(cwd),
2630
+ await writeFile(configPath, JSON.stringify({ defaultMode: "workflow" }, null, 2), "utf8");
2631
+ const enabled = await contextHandler({ messages: [workflowMessage, userMessage] }, context(cwd));
2632
+ assert.ok(enabled);
2633
+ assert.deepEqual(
2634
+ enabled.messages.filter((message) => message.customType !== "freeflow-runtime-state"),
2635
+ [userMessage],
3672
2636
  );
3673
-
3674
- assert.equal(result, undefined);
2637
+ assert.equal(enabled.messages.at(-1).customType, "freeflow-runtime-state");
3675
2638
  } finally {
3676
2639
  await rm(cwd, { recursive: true, force: true });
3677
2640
  }
3678
2641
  });
3679
2642
 
3680
- test("Pi observed routing fails open without losing MCP output", async () => {
3681
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-observed-fail-open-"));
2643
+ test("Pi suppresses persisted Cognitive Routing bootstrap context when routing is disabled", async () => {
2644
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-cognitive-bootstrap-disabled-"));
3682
2645
  try {
3683
2646
  await mkdir(join(cwd, ".freeflow"));
3684
- const fileVault = join(cwd, "vault-file");
3685
- await writeFile(fileVault, "not a directory", "utf8");
3686
- await writeFile(
3687
- join(cwd, ".freeflow/config.json"),
3688
- JSON.stringify({
3689
- defaultMode: "workflow",
3690
- outputRouter: {
3691
- enabled: true,
3692
- vaultRoot: fileVault,
3693
- observedRouting: {
3694
- enabled: true,
3695
- mcp: {
3696
- servers: { github: { enabled: true, persistence: "exact" } },
3697
- },
3698
- },
3699
- },
3700
- }),
3701
- "utf8",
3702
- );
2647
+ await writeFile(join(cwd, ".freeflow/config.json"), JSON.stringify({ defaultMode: "workflow" }, null, 2), "utf8");
3703
2648
 
3704
2649
  const { handlers } = loadExtension();
3705
- const result = await handlers.get("tool_result")(
3706
- {
3707
- type: "tool_result",
3708
- toolName: "mcp",
3709
- toolCallId: "mcp-fail-open",
3710
- input: { server: "github", tool: "search_issues" },
3711
- content: [{ type: "text", text: "original mcp output survives" }],
3712
- details: undefined,
3713
- isError: false,
3714
- },
3715
- context(cwd),
3716
- );
2650
+ const contextHandler = handlers.get("context");
2651
+ assert.ok(contextHandler);
3717
2652
 
3718
- assert.ok(result);
3719
- assert.match(result.content[0].text, /Freeflow observed-routing warning/);
3720
- assert.match(result.content[0].text, /original mcp output survives/);
3721
- assert.equal(result.details.freeflowObservedRouting.routingStatus, "failed");
2653
+ const cognitiveMessage = {
2654
+ role: "custom",
2655
+ customType: "freeflow-cognitive-routing-bootstrap",
2656
+ content: "# Freeflow Cognitive Routing Bootstrap",
2657
+ display: false,
2658
+ timestamp: Date.now(),
2659
+ };
2660
+ const userMessage = {
2661
+ role: "user",
2662
+ content: "hello",
2663
+ timestamp: Date.now(),
2664
+ };
2665
+ const disabled = await contextHandler({ messages: [cognitiveMessage, userMessage] }, context(cwd));
2666
+ assert.deepEqual(
2667
+ disabled.messages.filter((message) => message.customType !== "freeflow-runtime-state"),
2668
+ [userMessage],
2669
+ );
2670
+ assert.equal(disabled.messages.at(-1).customType, "freeflow-runtime-state");
3722
2671
  } finally {
3723
2672
  await rm(cwd, { recursive: true, force: true });
3724
2673
  }
3725
2674
  });
3726
2675
 
3727
- test("Pi post-tool safety net passes native output unchanged when config is off", async () => {
3728
- const { handlers } = loadExtension();
3729
- const toolResult = handlers.get("tool_result");
3730
- assert.ok(toolResult);
3731
-
3732
- const raw = Array.from({ length: 20 }, (_, index) => `line ${index + 1}`).join("\n");
3733
- const result = await toolResult(
3734
- {
3735
- type: "tool_result",
3736
- toolName: "read",
3737
- toolCallId: "read-1",
3738
- input: { path: "large.txt" },
3739
- content: [{ type: "text", text: raw }],
3740
- details: undefined,
3741
- isError: false,
3742
- },
3743
- context(),
3744
- );
3745
-
3746
- assert.equal(result, undefined);
3747
- });
3748
-
3749
- test("Pi post-tool safety net vaults and labels large native bash output when enabled", async () => {
3750
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-safety-net-"));
2676
+ test("Pi before_agent_start injects the Freeflow interaction contract on every turn", async () => {
2677
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-core-context-"));
3751
2678
  try {
3752
2679
  await mkdir(join(cwd, ".freeflow"));
3753
2680
  await writeFile(
3754
2681
  join(cwd, ".freeflow/config.json"),
3755
- JSON.stringify({
3756
- defaultMode: "workflow",
3757
- outputRouter: {
3758
- enabled: true,
3759
- postToolRouting: "safety-net",
3760
- largeOutputLines: 3,
3761
- largeOutputBytes: 100_000,
3762
- vaultRoot: join(cwd, "vault"),
3763
- },
3764
- }),
2682
+ JSON.stringify({ defaultMode: "workflow", contextVirtualization: true, conversationHistory: true }, null, 2),
3765
2683
  "utf8",
3766
2684
  );
3767
2685
 
3768
- const { handlers, tools } = loadExtension();
3769
- const toolResult = handlers.get("tool_result");
3770
- const raw = Array.from({ length: 20 }, (_, index) => `line ${index + 1}`).join("\n");
3771
- const result = await toolResult(
3772
- {
3773
- type: "tool_result",
3774
- toolName: "bash",
3775
- toolCallId: "bash-1",
3776
- input: { command: "npm test" },
3777
- content: [{ type: "text", text: raw }],
3778
- details: undefined,
3779
- isError: false,
3780
- },
3781
- context(cwd),
3782
- );
2686
+ const { handlers } = loadExtension();
2687
+ const beforeAgentStart = handlers.get("before_agent_start");
2688
+ assert.ok(beforeAgentStart);
3783
2689
 
3784
- assert.ok(result);
3785
- const routedText = result.content[0].text;
3786
- assert.match(routedText, /Freeflow routed this native bash result/);
3787
- assert.match(routedText, /outputId=ffout_/);
3788
- assert.doesNotMatch(routedText, /line 20/);
3789
-
3790
- const outputId = routedText.match(/outputId=(ffout_[a-f0-9]+)/)?.[1];
3791
- assert.ok(outputId);
3792
- const searchTool = tools.find((tool) => tool.name === "freeflow_search");
3793
- const searched = await searchTool.execute(
3794
- "search-1",
3795
- {
3796
- action: "retrieve",
3797
- source: { kind: "vault", outputId, stream: "raw" },
3798
- lineRange: { start: 18, end: 20 },
3799
- },
3800
- undefined,
3801
- undefined,
3802
- context(cwd),
3803
- );
3804
- const payload = searched.details.result;
3805
- assert.doesNotMatch(searched.content[0].text, /^\s*\{/);
3806
- assert.equal(payload.evidence[0].excerpt, "line 18\nline 19\nline 20");
2690
+ const first = await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
2691
+ const second = await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
2692
+
2693
+ for (const result of [first, second]) {
2694
+ assert.match(result.systemPrompt, /# Freeflow Stable Guidance/);
2695
+ assert.match(result.systemPrompt, /## Shared Terms/);
2696
+ assert.match(result.systemPrompt, /## Context Virtualization Cue/);
2697
+ assert.match(result.systemPrompt, /## Conversation History Cue/);
2698
+ assert.match(result.systemPrompt, /# Freeflow Interaction Contract/);
2699
+ assert.doesNotMatch(result.systemPrompt, /# Freeflow Runtime Kernel/);
2700
+ assert.match(result.systemPrompt, /Treat questions, criticism, examples, hypotheses, and tentative ideas as/);
2701
+ assert.doesNotMatch(result.systemPrompt, /## Freeflow Runtime Priority/);
2702
+ assert.doesNotMatch(result.systemPrompt, /## Loaded Mode Contract Skill/);
2703
+ assert.doesNotMatch(result.systemPrompt, /## Loaded Workflow Skill/);
2704
+ assert.doesNotMatch(result.systemPrompt, /## Loaded Decision Gate Skill/);
2705
+ assert.doesNotMatch(result.systemPrompt, /## Discovery-light/);
2706
+ assert.doesNotMatch(result.systemPrompt, /Output Router|freeflow_(search|run|batch)/i);
2707
+ assert.doesNotMatch(result.systemPrompt, /## Loaded Workflow Map/);
2708
+ }
3807
2709
  } finally {
3808
2710
  await rm(cwd, { recursive: true, force: true });
3809
2711
  }
3810
2712
  });
3811
2713
 
3812
- test("Pi post-tool safety net notes exact duplicate native output", async () => {
3813
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-safety-net-duplicate-"));
2714
+ test("Pi separates stable Freeflow guidance from volatile provider runtime state", async () => {
2715
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-runtime-state-layering-"));
3814
2716
  try {
3815
2717
  await mkdir(join(cwd, ".freeflow"));
3816
2718
  await writeFile(
3817
2719
  join(cwd, ".freeflow/config.json"),
3818
- JSON.stringify({
3819
- defaultMode: "workflow",
3820
- outputRouter: {
3821
- enabled: true,
3822
- postToolRouting: "safety-net",
3823
- largeOutputLines: 3,
3824
- largeOutputBytes: 100_000,
3825
- vaultRoot: join(cwd, "vault"),
2720
+ JSON.stringify(
2721
+ {
2722
+ defaultMode: "workflow",
2723
+ contextVirtualization: true,
2724
+ conversationHistory: true,
3826
2725
  },
3827
- }),
2726
+ null,
2727
+ 2,
2728
+ ),
3828
2729
  "utf8",
3829
2730
  );
3830
2731
 
3831
2732
  const { handlers } = loadExtension();
3832
- const toolResult = handlers.get("tool_result");
3833
- const raw = Array.from({ length: 20 }, (_, index) => `line ${index + 1}`).join("\n");
3834
- const first = await toolResult(
3835
- {
3836
- type: "tool_result",
3837
- toolName: "bash",
3838
- toolCallId: "bash-duplicate-1",
3839
- input: { command: "npm test" },
3840
- content: [{ type: "text", text: raw }],
3841
- details: undefined,
3842
- isError: false,
3843
- },
3844
- context(cwd),
3845
- );
3846
- const firstOutputId = first.content[0].text.match(/outputId=(ffout_[a-f0-9]+)/)?.[1];
3847
- assert.ok(firstOutputId);
3848
-
3849
- const second = await toolResult(
3850
- {
3851
- type: "tool_result",
3852
- toolName: "bash",
3853
- toolCallId: "bash-duplicate-2",
3854
- input: { command: "npm test" },
3855
- content: [{ type: "text", text: raw }],
3856
- details: undefined,
3857
- isError: false,
3858
- },
3859
- context(cwd),
2733
+ const beforeAgentStart = handlers.get("before_agent_start");
2734
+ const contextHandler = handlers.get("context");
2735
+ const ctx = context(cwd);
2736
+ const before = await beforeAgentStart({ systemPrompt: "base prompt" }, ctx);
2737
+
2738
+ assert.match(before.systemPrompt, /## Mode/);
2739
+ assert.match(before.systemPrompt, /Use the latest extension-generated Freeflow Runtime State/);
2740
+ assert.match(before.systemPrompt, /## Context Virtualization Cue/);
2741
+ assert.match(before.systemPrompt, /## Conversation History Cue/);
2742
+ assert.doesNotMatch(before.systemPrompt, /# Automatic Routing Kernel/);
2743
+ assert.doesNotMatch(before.systemPrompt, /Repository default mode:/);
2744
+ assert.doesNotMatch(before.systemPrompt, /Configured default mode:/);
2745
+ assert.doesNotMatch(before.systemPrompt, /Effective Freeflow mode:/);
2746
+ assert.doesNotMatch(before.systemPrompt, /- Interaction contract: enabled/);
2747
+
2748
+ assert.equal(before.message, undefined);
2749
+
2750
+ const firstContext = await contextHandler({ messages: [] }, ctx);
2751
+ assert.equal(firstContext.messages.length, 1);
2752
+ assert.equal(firstContext.messages[0].customType, "freeflow-runtime-state");
2753
+ assert.equal(
2754
+ firstContext.messages[0].content,
2755
+ [
2756
+ "# Freeflow Runtime State",
2757
+ "",
2758
+ "This is extension-generated runtime state. Use it to interpret the stable Freeflow guidance.",
2759
+ "",
2760
+ "Default mode: `workflow`",
2761
+ "Active mode: `workflow`",
2762
+ "",
2763
+ "Capabilities:",
2764
+ "- Interaction Contract: active",
2765
+ "- Skills: active",
2766
+ "- Context Virtualization: active",
2767
+ "- Conversation History: active",
2768
+ "- Cognitive Routing: inactive",
2769
+ "",
2770
+ "Cognitive Routing:",
2771
+ "- Control: `unavailable`",
2772
+ "- Profile: `unavailable`",
2773
+ ].join("\n"),
3860
2774
  );
3861
2775
 
3862
- assert.match(
3863
- second.content[0].text,
3864
- new RegExp(`Duplicate: exact native output matches previous outputId=${firstOutputId}`),
3865
- );
3866
- assert.equal(second.details.freeflowOutputRouter.duplicateOfOutputId, firstOutputId);
3867
- assert.match(second.content[0].text, /current raw output was vaulted as outputId=ffout_/);
2776
+ const repeatedContext = await contextHandler({ messages: firstContext.messages }, ctx);
2777
+ assert.equal(repeatedContext.messages.length, 1);
2778
+ assert.equal(repeatedContext.messages[0].customType, "freeflow-runtime-state");
3868
2779
  } finally {
3869
2780
  await rm(cwd, { recursive: true, force: true });
3870
2781
  }
3871
2782
  });
3872
2783
 
3873
- test("Pi post-tool safety net leaves small native output alone when enabled", async () => {
3874
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-small-output-"));
2784
+ test("Pi session_start and session_compact keep the interaction contract on later turns", async () => {
2785
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-session-cache-"));
3875
2786
  try {
3876
2787
  await mkdir(join(cwd, ".freeflow"));
3877
- await writeFile(
3878
- join(cwd, ".freeflow/config.json"),
3879
- JSON.stringify({
3880
- defaultMode: "workflow",
3881
- outputRouter: {
3882
- enabled: true,
3883
- postToolRouting: "safety-net",
3884
- largeOutputLines: 100,
3885
- largeOutputBytes: 10_000,
3886
- },
3887
- }),
3888
- "utf8",
3889
- );
2788
+ await writeFile(join(cwd, ".freeflow/config.json"), JSON.stringify({ defaultMode: "workflow" }, null, 2), "utf8");
3890
2789
 
3891
2790
  const { handlers } = loadExtension();
3892
- const result = await handlers.get("tool_result")(
3893
- {
3894
- type: "tool_result",
3895
- toolName: "bash",
3896
- toolCallId: "bash-small",
3897
- input: { command: "pwd" },
3898
- content: [{ type: "text", text: "small output" }],
3899
- details: undefined,
3900
- isError: false,
3901
- },
3902
- context(cwd),
3903
- );
2791
+ const beforeAgentStart = handlers.get("before_agent_start");
2792
+ const sessionStart = handlers.get("session_start");
2793
+ const sessionCompact = handlers.get("session_compact");
2794
+ assert.ok(beforeAgentStart);
2795
+ assert.ok(sessionStart);
2796
+ assert.ok(sessionCompact);
2797
+
2798
+ await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
2799
+ const afterFirst = await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
2800
+ assert.match(afterFirst.systemPrompt, /# Freeflow Interaction Contract/);
2801
+
2802
+ await sessionCompact({ reason: "manual" }, context(cwd));
2803
+ const afterCompact = await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
2804
+ assert.match(afterCompact.systemPrompt, /# Freeflow Interaction Contract/);
2805
+ assert.doesNotMatch(afterCompact.systemPrompt, /## Loaded Workflow Skill/);
2806
+ assert.doesNotMatch(afterCompact.systemPrompt, /Output Router|freeflow_(search|run|batch)/i);
3904
2807
 
3905
- assert.equal(result, undefined);
2808
+ await sessionStart({ reason: "resume" }, context(cwd));
2809
+ const afterResume = await beforeAgentStart({ systemPrompt: "base prompt" }, context(cwd));
2810
+ assert.match(afterResume.systemPrompt, /# Freeflow Interaction Contract/);
2811
+ assert.doesNotMatch(afterResume.systemPrompt, /## Loaded Workflow Skill/);
2812
+ assert.doesNotMatch(afterResume.systemPrompt, /Output Router|freeflow_(search|run|batch)/i);
3906
2813
  } finally {
3907
2814
  await rm(cwd, { recursive: true, force: true });
3908
2815
  }
3909
2816
  });
3910
2817
 
3911
- test("Pi post-tool safety net fails open without losing native output", async () => {
3912
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-safety-fail-open-"));
2818
+ test("Pi ignores retired router config without affecting core activation", async () => {
2819
+ const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-retired-router-config-"));
3913
2820
  try {
3914
2821
  await mkdir(join(cwd, ".freeflow"));
3915
- const blockedVaultPath = join(cwd, "vault-file");
3916
- await writeFile(blockedVaultPath, "not a directory", "utf8");
3917
2822
  await writeFile(
3918
2823
  join(cwd, ".freeflow/config.json"),
3919
2824
  JSON.stringify({
3920
2825
  defaultMode: "workflow",
3921
- outputRouter: {
3922
- enabled: true,
3923
- postToolRouting: "safety-net",
3924
- largeOutputLines: 1,
3925
- largeOutputBytes: 1,
3926
- vaultRoot: blockedVaultPath,
3927
- },
2826
+ outputRouter: { postToolRouting: "always" },
3928
2827
  }),
3929
2828
  "utf8",
3930
2829
  );
3931
2830
 
3932
2831
  const { handlers } = loadExtension();
3933
- const raw = "line 1\nline 2";
3934
- const result = await handlers.get("tool_result")(
3935
- {
3936
- type: "tool_result",
3937
- toolName: "read",
3938
- toolCallId: "read-fail-open",
3939
- input: { path: "large.txt" },
3940
- content: [{ type: "text", text: raw }],
3941
- details: undefined,
3942
- isError: false,
3943
- },
3944
- context(cwd),
3945
- );
2832
+ const notifications = [];
2833
+ const ctx = context(cwd);
2834
+ ctx.ui.notify = (message, level) => notifications.push({ message, level });
2835
+ await handlers.get("session_start")({}, ctx);
2836
+ const result = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
3946
2837
 
3947
- assert.ok(result);
3948
- assert.deepEqual(result.content[0], { type: "text", text: raw });
3949
- assert.match(result.content.at(-1).text, /Freeflow safety-net warning/);
2838
+ assert.equal(notifications.length, 0);
2839
+ assert.match(result.systemPrompt, /# Freeflow Interaction Contract/);
2840
+ assert.doesNotMatch(result.systemPrompt, /Output Router|freeflow_(search|run|batch)/i);
3950
2841
  } finally {
3951
2842
  await rm(cwd, { recursive: true, force: true });
3952
2843
  }
@@ -3960,7 +2851,6 @@ test("Pi appends the interaction contract after existing project context", async
3960
2851
  join(cwd, ".freeflow/config.json"),
3961
2852
  JSON.stringify({
3962
2853
  defaultMode: "workflow",
3963
- outputRouter: { enabled: true },
3964
2854
  }),
3965
2855
  "utf8",
3966
2856
  );
@@ -3975,18 +2865,13 @@ test("Pi appends the interaction contract after existing project context", async
3975
2865
  assert.doesNotMatch(result.systemPrompt, /# Freeflow Runtime Kernel/);
3976
2866
  assert.doesNotMatch(result.systemPrompt, /## Loaded Workflow Skill/);
3977
2867
  assert.doesNotMatch(result.systemPrompt, /## Discovery-light/);
3978
- assert.match(result.systemPrompt, /## Loaded Output Router Skill/);
3979
- assert.match(result.systemPrompt, /freeflow_search/);
3980
- assert.match(result.systemPrompt, /freeflow_run/);
3981
- assert.match(result.systemPrompt, /freeflow_search action=transform/);
3982
- assert.doesNotMatch(result.systemPrompt, /## Freeflow Output Router Reminder/);
3983
- assert.doesNotMatch(result.systemPrompt, /## Loaded Output Router Safety Policy/);
2868
+ assert.doesNotMatch(result.systemPrompt, /Output Router|freeflow_(search|run|batch)/i);
3984
2869
  } finally {
3985
2870
  await rm(cwd, { recursive: true, force: true });
3986
2871
  }
3987
2872
  });
3988
2873
 
3989
- test("Pi keeps enabled capability context alongside the interaction contract", async () => {
2874
+ test("Pi keeps core capability context alongside the interaction contract", async () => {
3990
2875
  const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-already-full-"));
3991
2876
  try {
3992
2877
  await mkdir(join(cwd, ".freeflow"));
@@ -3994,7 +2879,6 @@ test("Pi keeps enabled capability context alongside the interaction contract", a
3994
2879
  join(cwd, ".freeflow/config.json"),
3995
2880
  JSON.stringify({
3996
2881
  defaultMode: "workflow",
3997
- outputRouter: { enabled: true },
3998
2882
  }),
3999
2883
  "utf8",
4000
2884
  );
@@ -4007,10 +2891,7 @@ test("Pi keeps enabled capability context alongside the interaction contract", a
4007
2891
  assert.doesNotMatch(result.systemPrompt, /# Freeflow Runtime Kernel/);
4008
2892
  assert.doesNotMatch(result.systemPrompt, /## Loaded Workflow Skill/);
4009
2893
  assert.doesNotMatch(result.systemPrompt, /## Discovery-light/);
4010
- assert.match(result.systemPrompt, /## Loaded Output Router Skill/);
4011
- assert.match(result.systemPrompt, /name: output-router/);
4012
- assert.doesNotMatch(result.systemPrompt, /## Freeflow Output Router Reminder/);
4013
- assert.doesNotMatch(result.systemPrompt, /## Loaded Output Router Safety Policy/);
2894
+ assert.doesNotMatch(result.systemPrompt, /Output Router|freeflow_(search|run|batch)/i);
4014
2895
  assert.doesNotMatch(result.systemPrompt, /Capture raw evidence before transformation/);
4015
2896
  } finally {
4016
2897
  await rm(cwd, { recursive: true, force: true });