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