@hassangameryt/freeflow 0.5.0 → 0.7.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 (301) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/.codex-plugin/plugin.json +4 -5
  4. package/README.md +141 -163
  5. package/capabilities/cognitive-routing/SKILL.md +387 -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 -37
  9. package/hooks/freeflow-runtime-context.mjs +50 -541
  10. package/hooks/hooks.json +1 -26
  11. package/hooks/tests/check-runtime-context-hook.sh +114 -308
  12. package/package.json +27 -17
  13. package/pi-extension/dist/cognitive-routing/commands.js +96 -0
  14. package/pi-extension/dist/cognitive-routing/config.js +232 -0
  15. package/pi-extension/dist/cognitive-routing/controller.js +1292 -0
  16. package/pi-extension/dist/cognitive-routing/history.js +451 -0
  17. package/pi-extension/dist/cognitive-routing/host.js +188 -0
  18. package/pi-extension/dist/cognitive-routing/pi-routing-session.js +414 -0
  19. package/pi-extension/dist/cognitive-routing/pi-session-control.js +320 -0
  20. package/pi-extension/dist/cognitive-routing/runtime.js +43 -0
  21. package/pi-extension/dist/cognitive-routing/tool.js +212 -0
  22. package/pi-extension/dist/cognitive-routing/types.js +6 -0
  23. package/pi-extension/dist/context-virtualization/commands.js +89 -0
  24. package/pi-extension/dist/context-virtualization/projector.js +42 -0
  25. package/pi-extension/dist/context-virtualization/runtime.js +356 -0
  26. package/pi-extension/dist/context-virtualization/state.js +57 -0
  27. package/pi-extension/dist/context-virtualization/types.js +10 -0
  28. package/pi-extension/dist/conversation-history/passages.js +66 -0
  29. package/pi-extension/dist/conversation-history/ranking.js +105 -0
  30. package/pi-extension/dist/conversation-history/runtime.js +226 -0
  31. package/pi-extension/dist/conversation-history/search.js +56 -0
  32. package/pi-extension/dist/conversation-history/types.js +1 -0
  33. package/pi-extension/dist/freeflow-context/resolver.js +120 -0
  34. package/pi-extension/dist/freeflow-context/runtime.js +80 -0
  35. package/pi-extension/dist/freeflow-context/source-projector.js +95 -0
  36. package/pi-extension/dist/freeflow-context/tool.js +308 -0
  37. package/pi-extension/dist/freeflow-context/types.js +12 -0
  38. package/pi-extension/dist/freeflow-context/visibility.js +28 -0
  39. package/pi-extension/dist/index.js +564 -129
  40. package/pi-extension/dist/runtime/runtime-context.js +357 -470
  41. package/pi-extension/dist/runtime/runtime-identity.js +5 -0
  42. package/pi-extension/dist/settings/settings-tui.js +78 -0
  43. package/pi-extension/dist/settings/settings-ui.js +572 -826
  44. package/pi-extension/dist/ui/text-component.js +57 -0
  45. package/pi-extension/src/cognitive-routing/commands.ts +129 -0
  46. package/pi-extension/src/cognitive-routing/config.ts +303 -0
  47. package/pi-extension/src/cognitive-routing/controller.ts +1628 -0
  48. package/pi-extension/src/cognitive-routing/history.ts +650 -0
  49. package/pi-extension/src/cognitive-routing/host.ts +266 -0
  50. package/pi-extension/src/cognitive-routing/pi-routing-session.ts +538 -0
  51. package/pi-extension/src/cognitive-routing/pi-session-control.ts +444 -0
  52. package/pi-extension/src/cognitive-routing/runtime.ts +57 -0
  53. package/pi-extension/src/cognitive-routing/tool.ts +273 -0
  54. package/pi-extension/src/cognitive-routing/types.ts +89 -0
  55. package/pi-extension/src/context-virtualization/commands.ts +111 -0
  56. package/pi-extension/src/context-virtualization/projector.ts +62 -0
  57. package/pi-extension/src/context-virtualization/runtime.ts +406 -0
  58. package/pi-extension/src/context-virtualization/state.ts +79 -0
  59. package/pi-extension/src/context-virtualization/types.ts +92 -0
  60. package/pi-extension/src/conversation-history/passages.ts +81 -0
  61. package/pi-extension/src/conversation-history/ranking.ts +155 -0
  62. package/pi-extension/src/conversation-history/runtime.ts +250 -0
  63. package/pi-extension/src/conversation-history/search.ts +79 -0
  64. package/pi-extension/src/conversation-history/types.ts +31 -0
  65. package/pi-extension/src/freeflow-context/resolver.ts +139 -0
  66. package/pi-extension/src/freeflow-context/runtime.ts +100 -0
  67. package/pi-extension/src/freeflow-context/source-projector.ts +106 -0
  68. package/pi-extension/src/freeflow-context/tool.ts +360 -0
  69. package/pi-extension/src/freeflow-context/types.ts +70 -0
  70. package/pi-extension/src/freeflow-context/visibility.ts +53 -0
  71. package/pi-extension/src/index.ts +597 -135
  72. package/pi-extension/src/runtime/runtime-context.ts +373 -491
  73. package/pi-extension/src/runtime/runtime-identity.ts +8 -0
  74. package/pi-extension/src/settings/settings-tui.ts +93 -0
  75. package/pi-extension/src/settings/settings-ui.ts +679 -910
  76. package/pi-extension/src/ui/text-component.ts +66 -0
  77. package/pi-extension/tests/cognitive-routing/commands.test.js +134 -0
  78. package/pi-extension/tests/cognitive-routing/controller.test.js +1128 -0
  79. package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +1142 -0
  80. package/pi-extension/tests/cognitive-routing/history.test.js +274 -0
  81. package/pi-extension/tests/cognitive-routing/host-fixture.js +4 -0
  82. package/pi-extension/tests/cognitive-routing/host-identity.test.js +63 -0
  83. package/pi-extension/tests/cognitive-routing/pi-normal-lifecycle.test.js +434 -0
  84. package/pi-extension/tests/cognitive-routing/pi-session-control.test.js +174 -0
  85. package/pi-extension/tests/cognitive-routing/pi-session-history.test.js +91 -0
  86. package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +279 -0
  87. package/pi-extension/tests/cognitive-routing/tool.test.js +230 -0
  88. package/pi-extension/tests/context-virtualization/extension.test.js +749 -0
  89. package/pi-extension/tests/context-virtualization/projector.test.js +110 -0
  90. package/pi-extension/tests/context-virtualization/runtime.test.js +216 -0
  91. package/pi-extension/tests/conversation-history/runtime.test.js +98 -0
  92. package/pi-extension/tests/conversation-history/search.test.js +97 -0
  93. package/pi-extension/tests/integration/mode-free-core.test.js +218 -0
  94. package/pi-extension/tests/integration/pi-extension.test.js +532 -3701
  95. package/pi-extension/tests/integration/prompt-architecture.test.js +350 -0
  96. package/pi-extension/tests/integration/unaffected-regressions.test.js +411 -0
  97. package/runtime/prompts/cognitive-routing.md +10 -0
  98. package/runtime/prompts/context-virtualization.md +3 -0
  99. package/runtime/prompts/conversation-history.md +3 -0
  100. package/runtime/prompts/core.md +84 -0
  101. package/runtime/prompts/interaction-contract.md +11 -0
  102. package/skills/action-selection/SKILL.md +140 -0
  103. package/skills/action-selection/references/trajectory-stalls.md +71 -0
  104. package/skills/bypass/SKILL.md +109 -38
  105. package/skills/commit-work/SKILL.md +94 -55
  106. package/skills/commit-work/references/git-state-edges.md +94 -0
  107. package/skills/commit-work/references/simple-push.md +92 -0
  108. package/skills/commit-work/references/staging-decisions.md +79 -46
  109. package/skills/decision-gate/SKILL.md +80 -22
  110. package/skills/design-for-depth/SKILL.md +206 -60
  111. package/skills/design-for-depth/references/design-pressure-signals.md +108 -240
  112. package/skills/design-for-depth/references/interface-design-loop.md +77 -97
  113. package/skills/design-for-depth/references/module-and-dependency-design.md +147 -0
  114. package/skills/design-for-depth/references/software-design-philosophy.md +128 -174
  115. package/skills/design-for-depth/references/state-and-failure-boundaries.md +136 -0
  116. package/skills/diagnose-failure/SKILL.md +200 -48
  117. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +18 -6
  118. package/skills/diagnose-failure/references/{flaky-and-performance.md → flaky-and-performance-diagnosis.md} +13 -5
  119. package/skills/discuss/SKILL.md +119 -65
  120. package/skills/discuss/references/discussion-continuity.md +123 -0
  121. package/skills/evaluate-skill/SKILL.md +166 -52
  122. package/skills/evaluate-skill/references/{definition-schema.md → evaluation-definition-schema.md} +43 -3
  123. package/skills/evaluate-skill/references/evaluation-design.md +56 -30
  124. package/skills/evaluate-skill/references/execution-and-evidence.md +8 -3
  125. package/skills/evaluate-skill/references/review-and-revision.md +62 -21
  126. package/skills/evaluate-skill/scripts/lib/definitions.mjs +114 -3
  127. package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +156 -1
  128. package/skills/evaluate-skill/scripts/lib/grade.mjs +9 -1
  129. package/skills/evaluate-skill/scripts/lib/pi.mjs +147 -32
  130. package/skills/evaluate-skill/scripts/lib/rpc.mjs +4 -4
  131. package/skills/evaluate-skill/scripts/lib/runner.mjs +36 -14
  132. package/skills/evaluate-skill/scripts/lib/sandbox.mjs +92 -0
  133. package/skills/evaluate-skill/scripts/pi-guard.mjs +1 -1
  134. package/skills/evaluate-skill/scripts/pi-observer.mjs +66 -0
  135. package/skills/evaluate-skill/scripts/skill-eval.mjs +6 -5
  136. package/skills/execute-work/SKILL.md +99 -57
  137. package/skills/execute-work/references/code-practices.md +4 -4
  138. package/skills/execute-work/references/execute-work-edges.md +62 -0
  139. package/skills/finish-branch/SKILL.md +2 -2
  140. package/skills/finish-branch/references/integration-options.md +1 -1
  141. package/skills/handoff/SKILL.md +3 -3
  142. package/skills/launch-work/SKILL.md +1 -1
  143. package/skills/launch-work/references/launch-readiness.md +2 -2
  144. package/skills/migration-work/SKILL.md +2 -2
  145. package/skills/migration-work/references/migration-lifecycle.md +1 -1
  146. package/skills/release-work/SKILL.md +1 -1
  147. package/skills/release-work/references/release-evidence.md +1 -1
  148. package/skills/review-artifact/SKILL.md +129 -66
  149. package/skills/review-artifact/references/adjudicate-artifact-review.md +101 -0
  150. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +159 -0
  151. package/skills/review-work/SKILL.md +113 -68
  152. package/skills/review-work/references/adjudicate-work-review.md +97 -0
  153. package/skills/review-work/references/independent-work-reviewer-contract.md +139 -0
  154. package/skills/review-work/references/security-risk-lens.md +18 -16
  155. package/skills/setup-freeflow/SKILL.md +16 -17
  156. package/skills/setup-freeflow/references/activation-contract.md +24 -30
  157. package/skills/setup-freeflow/references/host-setup.md +6 -7
  158. package/skills/simplify-code/SKILL.md +97 -39
  159. package/skills/simplify-code/references/simplification-patterns.md +1 -1
  160. package/skills/tdd/SKILL.md +100 -53
  161. package/skills/tdd/references/test-design.md +3 -3
  162. package/skills/track-work/SKILL.md +195 -58
  163. package/skills/track-work/references/working-record-format.md +515 -0
  164. package/skills/track-work/scripts/lib/document.mjs +713 -0
  165. package/skills/track-work/scripts/lib/format.mjs +166 -0
  166. package/skills/track-work/scripts/lib/store.mjs +233 -0
  167. package/skills/track-work/scripts/lib/transitions.mjs +790 -0
  168. package/skills/track-work/scripts/lib/views.mjs +40 -0
  169. package/skills/track-work/scripts/working-record.mjs +316 -0
  170. package/skills/verify-work/SKILL.md +57 -19
  171. package/skills/verify-work/references/browser-runtime-evidence.md +2 -2
  172. package/skills/verify-work/references/integration-evidence.md +1 -7
  173. package/skills/verify-work/references/performance-evidence.md +2 -2
  174. package/skills/workflow/SKILL.md +137 -55
  175. package/skills/workflow/references/domain-skill-composition.md +26 -40
  176. package/skills/write-plan/SKILL.md +1 -1
  177. package/skills/write-skill/SKILL.md +218 -61
  178. package/skills/write-skill/references/activation-boundaries.md +63 -21
  179. package/skills/write-skill/references/agent-first-instructions.md +61 -27
  180. package/skills/write-skill/references/progressive-disclosure.md +97 -33
  181. package/skills/write-skill/references/skill-dependency-graphs.md +190 -0
  182. package/capabilities/output-router/SKILL.md +0 -206
  183. package/capabilities/output-router/references/safety-policy.md +0 -40
  184. package/capabilities/output-router/references/setup.md +0 -110
  185. package/pi-extension/dist/output-router/host-producer-identification.js +0 -232
  186. package/pi-extension/dist/output-router/native-safety-net.js +0 -216
  187. package/pi-extension/dist/output-router/observed-tool-routing.js +0 -122
  188. package/pi-extension/dist/output-router/renderers.js +0 -1023
  189. package/pi-extension/dist/output-router/router-tools.js +0 -554
  190. package/pi-extension/dist/output-router/schemas.js +0 -383
  191. package/pi-extension/dist/output-router/status.js +0 -744
  192. package/pi-extension/dist/output-router/utils.js +0 -502
  193. package/pi-extension/src/output-router/host-producer-identification.ts +0 -256
  194. package/pi-extension/src/output-router/native-safety-net.ts +0 -248
  195. package/pi-extension/src/output-router/observed-tool-routing.ts +0 -134
  196. package/pi-extension/src/output-router/renderers.ts +0 -1108
  197. package/pi-extension/src/output-router/router-tools.ts +0 -579
  198. package/pi-extension/src/output-router/schemas.ts +0 -387
  199. package/pi-extension/src/output-router/status.ts +0 -791
  200. package/pi-extension/src/output-router/utils.ts +0 -554
  201. package/pi-extension/tests/output-router/observed-producers.test.js +0 -132
  202. package/pi-extension/tests/output-router/search.test.js +0 -722
  203. package/router/dist/benchmarks/benchmark-harness.d.ts +0 -35
  204. package/router/dist/benchmarks/benchmark-harness.js +0 -92
  205. package/router/dist/benchmarks/benchmarks.d.ts +0 -109
  206. package/router/dist/benchmarks/benchmarks.js +0 -979
  207. package/router/dist/benchmarks/codex-qa-benchmarks.d.ts +0 -95
  208. package/router/dist/benchmarks/codex-qa-benchmarks.js +0 -500
  209. package/router/dist/benchmarks/command-benchmarks.d.ts +0 -143
  210. package/router/dist/benchmarks/command-benchmarks.js +0 -698
  211. package/router/dist/benchmarks/context-mode-normalized-benchmarks.d.ts +0 -82
  212. package/router/dist/benchmarks/context-mode-normalized-benchmarks.js +0 -773
  213. package/router/dist/benchmarks/context-mode-real-deep-benchmark.d.ts +0 -132
  214. package/router/dist/benchmarks/context-mode-real-deep-benchmark.js +0 -1380
  215. package/router/dist/benchmarks/context-saving-baseline.d.ts +0 -60
  216. package/router/dist/benchmarks/context-saving-baseline.js +0 -470
  217. package/router/dist/benchmarks/index-benchmarks.d.ts +0 -103
  218. package/router/dist/benchmarks/index-benchmarks.js +0 -862
  219. package/router/dist/benchmarks/storage-policy-benchmarks.d.ts +0 -101
  220. package/router/dist/benchmarks/storage-policy-benchmarks.js +0 -618
  221. package/router/dist/benchmarks/vault-index-benchmarks.d.ts +0 -56
  222. package/router/dist/benchmarks/vault-index-benchmarks.js +0 -322
  223. package/router/dist/config/config.d.ts +0 -103
  224. package/router/dist/config/config.js +0 -632
  225. package/router/dist/config/router-contract.d.ts +0 -10
  226. package/router/dist/config/router-contract.js +0 -54
  227. package/router/dist/config/schema.d.ts +0 -27
  228. package/router/dist/config/schema.js +0 -648
  229. package/router/dist/config/types.d.ts +0 -573
  230. package/router/dist/config/types.js +0 -41
  231. package/router/dist/evidence/bounded-evidence.d.ts +0 -40
  232. package/router/dist/evidence/bounded-evidence.js +0 -323
  233. package/router/dist/evidence/evidence-range-selector.d.ts +0 -24
  234. package/router/dist/evidence/evidence-range-selector.js +0 -232
  235. package/router/dist/evidence/evidence-search.d.ts +0 -27
  236. package/router/dist/evidence/evidence-search.js +0 -490
  237. package/router/dist/evidence/evidence.d.ts +0 -38
  238. package/router/dist/evidence/evidence.js +0 -200
  239. package/router/dist/evidence/failure-contracts.d.ts +0 -37
  240. package/router/dist/evidence/failure-contracts.js +0 -166
  241. package/router/dist/evidence/line-ranges.d.ts +0 -24
  242. package/router/dist/evidence/line-ranges.js +0 -22
  243. package/router/dist/experiments/local-index.d.ts +0 -64
  244. package/router/dist/experiments/local-index.js +0 -581
  245. package/router/dist/index.d.ts +0 -30
  246. package/router/dist/index.js +0 -32
  247. package/router/dist/local/local-traversal.d.ts +0 -18
  248. package/router/dist/local/local-traversal.js +0 -334
  249. package/router/dist/processing/engine.d.ts +0 -132
  250. package/router/dist/processing/engine.js +0 -533
  251. package/router/dist/processing/reducers.d.ts +0 -280
  252. package/router/dist/processing/reducers.js +0 -1673
  253. package/router/dist/processing/renderers.d.ts +0 -31
  254. package/router/dist/processing/renderers.js +0 -195
  255. package/router/dist/processing/scripts.d.ts +0 -91
  256. package/router/dist/processing/scripts.js +0 -386
  257. package/router/dist/repo/repo-traversal.d.ts +0 -18
  258. package/router/dist/repo/repo-traversal.js +0 -245
  259. package/router/dist/routing/observed-reducers.d.ts +0 -41
  260. package/router/dist/routing/observed-reducers.js +0 -661
  261. package/router/dist/routing/observed-routing.d.ts +0 -26
  262. package/router/dist/routing/observed-routing.js +0 -378
  263. package/router/dist/routing/parsers.d.ts +0 -16
  264. package/router/dist/routing/parsers.js +0 -521
  265. package/router/dist/routing/run-filters.d.ts +0 -60
  266. package/router/dist/routing/run-filters.js +0 -238
  267. package/router/dist/routing/run-reducers.d.ts +0 -49
  268. package/router/dist/routing/run-reducers.js +0 -122
  269. package/router/dist/sandbox/adapter-roots.d.ts +0 -46
  270. package/router/dist/sandbox/adapter-roots.js +0 -66
  271. package/router/dist/sandbox/eryx-python-adapter.d.ts +0 -13
  272. package/router/dist/sandbox/eryx-python-adapter.js +0 -868
  273. package/router/dist/sandbox/jq-wasm-adapter.d.ts +0 -9
  274. package/router/dist/sandbox/jq-wasm-adapter.js +0 -429
  275. package/router/dist/sandbox/quickjs-wasi-adapter.d.ts +0 -13
  276. package/router/dist/sandbox/quickjs-wasi-adapter.js +0 -384
  277. package/router/dist/sandbox/script-sandbox.d.ts +0 -134
  278. package/router/dist/sandbox/script-sandbox.js +0 -337
  279. package/router/dist/setup/script-transform-adapters.d.ts +0 -32
  280. package/router/dist/setup/script-transform-adapters.js +0 -278
  281. package/router/dist/tools/batch.d.ts +0 -33
  282. package/router/dist/tools/batch.js +0 -586
  283. package/router/dist/tools/run.d.ts +0 -62
  284. package/router/dist/tools/run.js +0 -1304
  285. package/router/dist/tools/search.d.ts +0 -66
  286. package/router/dist/tools/search.js +0 -1364
  287. package/router/dist/transform/engine.d.ts +0 -168
  288. package/router/dist/transform/engine.js +0 -2072
  289. package/router/dist/vault/vault-index.d.ts +0 -125
  290. package/router/dist/vault/vault-index.js +0 -416
  291. package/router/dist/vault/vault.d.ts +0 -138
  292. package/router/dist/vault/vault.js +0 -630
  293. package/runtime/interaction-contract.md +0 -16
  294. package/skills/discuss/references/checkpoints.md +0 -66
  295. package/skills/execute-work/references/execution-loop.md +0 -77
  296. package/skills/mode-contract/SKILL.md +0 -104
  297. package/skills/review-artifact/references/reviewer-prompt.md +0 -148
  298. package/skills/review-work/references/reviewer-prompt.md +0 -134
  299. package/skills/track-work/references/working-record-schema.md +0 -378
  300. package/skills/workflow/references/workflow-loop.md +0 -92
  301. /package/skills/handoff/references/{templates.md → handoff-templates.md} +0 -0
@@ -3,47 +3,19 @@ import { appendFile, mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
3
  import { dirname, isAbsolute, join, resolve } from "node:path";
4
4
  import { promisify } from "node:util";
5
5
  import {
6
- DEFAULT_OBSERVED_ROUTING_CONFIG,
7
- DEFAULT_OUTPUT_ROUTER_ENABLED,
8
- DEFAULT_POST_TOOL_ROUTING,
9
- DEFAULT_ROUTER_THRESHOLDS,
10
- DEFAULT_SCRIPT_TRANSFORM_CONFIG,
11
- DEFAULT_STORAGE_POLICY,
12
- DEFAULT_VAULT_RETENTION,
13
- DEFAULT_VAULT_ROOT,
14
- normalizeFreeflowConfig,
15
- } from "../../../router/dist/index.js";
16
- import {
17
- handleModeCommand,
18
6
  readCapabilityState,
19
7
  readFreeflowConfig,
20
8
  readFreeflowConfigLayers,
21
9
  readFreeflowLocalConfig,
22
- readModeState,
23
10
  resetSessionOverrides,
24
11
  setSessionCoreOverride,
25
- VALID_MODES,
26
12
  } from "../runtime/runtime-context.js";
27
13
  import { PiSettingsComponent } from "./settings-tui.js";
28
- const POST_TOOL_ROUTING_VALUES = ["off", "safety-net", "strict"];
29
- const STORAGE_POLICY_VALUES = ["hybrid-dedupe", "store-everything"];
30
- const OBSERVED_PERSISTENCE_VALUES = ["none", "metadata-only", "exact"];
31
- const SCRIPT_LANGUAGES = ["javascript", "python", "jq"];
14
+ import { supportsPiSessionModelStateApi } from "../cognitive-routing/pi-session-control.js";
15
+ import { isPiFlowHost } from "../runtime/runtime-identity.js";
32
16
  const DEFAULT_FREEFLOW_ENABLED = true;
33
- const DEFAULT_INTERACTION_CONTRACT_ENABLED = true;
34
- const DEFAULT_SKILLS_ENABLED = true;
35
- const MODE_VALUES = ["conversation", "workflow", "strict-workflow"];
36
- const MODE_LABELS = {
37
- default: "Use configured default",
38
- conversation: "conversation",
39
- workflow: "workflow",
40
- "strict-workflow": "strict-workflow",
41
- };
42
- const MODE_DESCRIPTIONS = {
43
- conversation: "Discussion and read-only inspection",
44
- workflow: "Adaptive workflow for consequential work",
45
- "strict-workflow": "Stronger decision and evidence pressure for high-risk work",
46
- };
17
+ const DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED = false;
18
+ const DEFAULT_CONVERSATION_HISTORY_ENABLED = false;
47
19
  const LOCAL_INHERIT = "inherit";
48
20
  const execFileAsync = promisify(execFile);
49
21
  function isRecord(value) {
@@ -114,288 +86,9 @@ function setConfigValue(config, item, value) {
114
86
  }
115
87
  setPath(config, item.path, value);
116
88
  }
117
- function parseStringList(text) {
118
- return text
119
- .split(",")
120
- .map((item) => item.trim())
121
- .filter(Boolean);
122
- }
123
- function parsePositiveInteger(text) {
124
- const value = Number(text.trim());
125
- if (!Number.isInteger(value) || value <= 0) {
126
- throw new Error("Expected a positive integer.");
127
- }
128
- return value;
129
- }
130
- function parseJsonObject(text) {
131
- const trimmed = text.trim();
132
- if (!trimmed) return {};
133
- let parsed;
134
- try {
135
- parsed = JSON.parse(trimmed);
136
- } catch (error) {
137
- throw new Error(`Expected valid JSON: ${error instanceof Error ? error.message : String(error)}`);
138
- }
139
- if (!isRecord(parsed)) {
140
- throw new Error("Expected a JSON object.");
141
- }
142
- return parsed;
143
- }
144
- function formatList(value) {
145
- return Array.isArray(value) ? value.join(", ") : "";
146
- }
147
- function formatJson(value) {
148
- return JSON.stringify(isRecord(value) ? value : {});
149
- }
150
89
  function booleanValue(value) {
151
90
  return value === true ? "enabled" : "disabled";
152
91
  }
153
- function normalizedSettingsConfig(rawConfig) {
154
- const rawRouter = isRecord(rawConfig.outputRouter) ? rawConfig.outputRouter : {};
155
- const normalized = normalizeFreeflowConfig({
156
- ...rawConfig,
157
- outputRouter: { ...rawRouter, enabled: true },
158
- });
159
- return normalized.config;
160
- }
161
- function rawValue(rawConfig, path, fallback) {
162
- const value = getPath(rawConfig, path);
163
- return value === undefined ? fallback : value;
164
- }
165
- function outputRouterItems(rawConfig, freeflowInactive = false) {
166
- const effective = normalizedSettingsConfig(rawConfig);
167
- const routerEnabled = normalizeFreeflowConfig(rawConfig).config.outputRouter.enabled;
168
- const router = effective.outputRouter;
169
- const scriptTransform = effective.scriptTransform;
170
- const observedRouting = effective.observedRouting;
171
- const inactive = freeflowInactive || !routerEnabled;
172
- return [
173
- {
174
- id: "outputRouter.enabled",
175
- label: "Output Router",
176
- description: "Master switch for Freeflow routed evidence tools and router runtime guidance.",
177
- path: ["outputRouter", "enabled"],
178
- kind: "boolean",
179
- value: routerEnabled,
180
- defaultValue: DEFAULT_OUTPUT_ROUTER_ENABLED,
181
- inactive: freeflowInactive,
182
- },
183
- {
184
- id: "outputRouter.postToolRouting",
185
- label: "Native safety net",
186
- description:
187
- "Post-process large/noisy native read/bash output. safety-net can replace oversized native output with vaulted routed evidence; strict is stronger.",
188
- path: ["outputRouter", "postToolRouting"],
189
- kind: "enum",
190
- value: router.postToolRouting,
191
- values: [...POST_TOOL_ROUTING_VALUES],
192
- valueDescriptions: {
193
- off: "Leave native tool output unchanged",
194
- "safety-net": "Route oversized or noisy native output",
195
- strict: "Apply stronger native output routing",
196
- },
197
- defaultValue: DEFAULT_POST_TOOL_ROUTING,
198
- inactive,
199
- },
200
- {
201
- id: "outputRouter.storagePolicy",
202
- label: "Storage policy",
203
- description: "Vault storage behavior for exactness-sensitive and noisy output.",
204
- path: ["outputRouter", "storagePolicy"],
205
- kind: "enum",
206
- value: router.storagePolicy,
207
- values: [...STORAGE_POLICY_VALUES],
208
- defaultValue: DEFAULT_STORAGE_POLICY,
209
- inactive,
210
- },
211
- {
212
- id: "outputRouter.thresholds.largeOutputBytes",
213
- label: "Large output bytes",
214
- description: "Byte threshold for treating command/tool output as large.",
215
- path: ["outputRouter", "thresholds", "largeOutputBytes"],
216
- kind: "integer",
217
- value: router.thresholds.largeOutputBytes,
218
- defaultValue: DEFAULT_ROUTER_THRESHOLDS.largeOutputBytes,
219
- parse: parsePositiveInteger,
220
- inactive,
221
- },
222
- {
223
- id: "outputRouter.thresholds.largeOutputLines",
224
- label: "Large output lines",
225
- description: "Line threshold for treating command/tool output as large.",
226
- path: ["outputRouter", "thresholds", "largeOutputLines"],
227
- kind: "integer",
228
- value: router.thresholds.largeOutputLines,
229
- defaultValue: DEFAULT_ROUTER_THRESHOLDS.largeOutputLines,
230
- parse: parsePositiveInteger,
231
- inactive,
232
- },
233
- {
234
- id: "outputRouter.vault.root",
235
- label: "Vault root",
236
- description: "Root directory for recoverable routed output.",
237
- path: ["outputRouter", "vault", "root"],
238
- kind: "string",
239
- value: router.vault.root,
240
- defaultValue: DEFAULT_VAULT_ROOT,
241
- inactive,
242
- },
243
- {
244
- id: "outputRouter.vault.retention.ttlDays",
245
- label: "Vault retention days",
246
- description: "TTL retention for vaulted output. Set a positive integer; default is 7 days.",
247
- path: ["outputRouter", "vault", "retention", "ttlDays"],
248
- kind: "integer",
249
- value:
250
- router.vault.retention?.strategy === "ttl" ? router.vault.retention.ttlDays : DEFAULT_VAULT_RETENTION.ttlDays,
251
- defaultValue: DEFAULT_VAULT_RETENTION.ttlDays,
252
- parse: parsePositiveInteger,
253
- inactive,
254
- },
255
- {
256
- id: "outputRouter.generatedPaths",
257
- label: "Generated paths",
258
- description:
259
- "Comma-separated globs for generated repo paths that should be treated as noisy or lower-priority evidence.",
260
- path: ["outputRouter", "hints", "generatedPathGlobs"],
261
- kind: "list",
262
- value: rawValue(
263
- rawConfig,
264
- ["outputRouter", "hints", "generatedPathGlobs"],
265
- rawValue(rawConfig, ["outputRouter", "generatedPaths"], router.hints?.generatedPathGlobs ?? []),
266
- ),
267
- format: formatList,
268
- parse: parseStringList,
269
- inactive,
270
- },
271
- {
272
- id: "outputRouter.noisyCommandHints",
273
- label: "Noisy command hints",
274
- description: "Comma-separated command patterns that are likely to produce noisy output.",
275
- path: ["outputRouter", "hints", "noisyCommandPatterns"],
276
- kind: "list",
277
- value: rawValue(
278
- rawConfig,
279
- ["outputRouter", "hints", "noisyCommandPatterns"],
280
- rawValue(rawConfig, ["outputRouter", "noisyCommandHints"], router.hints?.noisyCommandPatterns ?? []),
281
- ),
282
- format: formatList,
283
- parse: parseStringList,
284
- inactive,
285
- },
286
- {
287
- id: "outputRouter.scriptTransform.enabled",
288
- label: "Script transform",
289
- description:
290
- "Enable sandboxed script transforms/producers. Requires proof-backed adapters; no unsandboxed fallback.",
291
- path: ["outputRouter", "scriptTransform", "enabled"],
292
- kind: "boolean",
293
- value: scriptTransform.enabled,
294
- defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.enabled,
295
- inactive,
296
- },
297
- {
298
- id: "outputRouter.scriptTransform.languages",
299
- label: "Script languages",
300
- description: "Comma-separated sandbox languages to allow: javascript, python, jq.",
301
- path: ["outputRouter", "scriptTransform", "languages"],
302
- kind: "list",
303
- value: scriptTransform.languages,
304
- defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.languages,
305
- format: formatList,
306
- parse: (text) => parseStringList(text).filter((value) => SCRIPT_LANGUAGES.includes(value)),
307
- inactive,
308
- },
309
- {
310
- id: "outputRouter.scriptTransform.limits.timeoutMs",
311
- label: "Script timeout ms",
312
- description: "Maximum sandboxed script execution time.",
313
- path: ["outputRouter", "scriptTransform", "limits", "timeoutMs"],
314
- kind: "integer",
315
- value: scriptTransform.limits.timeoutMs,
316
- defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.timeoutMs,
317
- parse: parsePositiveInteger,
318
- inactive,
319
- },
320
- {
321
- id: "outputRouter.scriptTransform.limits.maxInputBytes",
322
- label: "Script max input bytes",
323
- description: "Maximum input size for sandboxed scripts.",
324
- path: ["outputRouter", "scriptTransform", "limits", "maxInputBytes"],
325
- kind: "integer",
326
- value: scriptTransform.limits.maxInputBytes,
327
- defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.maxInputBytes,
328
- parse: parsePositiveInteger,
329
- inactive,
330
- },
331
- {
332
- id: "outputRouter.scriptTransform.limits.maxOutputBytes",
333
- label: "Script max output bytes",
334
- description: "Maximum output size for sandboxed scripts.",
335
- path: ["outputRouter", "scriptTransform", "limits", "maxOutputBytes"],
336
- kind: "integer",
337
- value: scriptTransform.limits.maxOutputBytes,
338
- defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.maxOutputBytes,
339
- parse: parsePositiveInteger,
340
- inactive,
341
- },
342
- {
343
- id: "outputRouter.observedRouting.enabled",
344
- label: "Observed routing",
345
- description: "Route configured host tool results after they run, e.g. MCP/web/fetch/code-search output.",
346
- path: ["outputRouter", "observedRouting", "enabled"],
347
- kind: "boolean",
348
- value: observedRouting.enabled,
349
- defaultValue: DEFAULT_OBSERVED_ROUTING_CONFIG.enabled,
350
- inactive,
351
- },
352
- ...observedProducerItems("web", "Web search", observedRouting.web, inactive),
353
- ...observedProducerItems("fetch", "Fetch content", observedRouting.fetch, inactive),
354
- ...observedProducerItems("codeSearch", "Code search", observedRouting.codeSearch, inactive),
355
- {
356
- id: "outputRouter.observedRouting.mcp.servers",
357
- label: "MCP servers JSON",
358
- description: 'JSON object keyed by MCP server id. Example: {"github":{"enabled":true,"persistence":"exact"}}',
359
- path: ["outputRouter", "observedRouting", "mcp", "servers"],
360
- kind: "json",
361
- value: observedRouting.mcp?.servers ?? {},
362
- defaultValue: DEFAULT_OBSERVED_ROUTING_CONFIG.mcp.servers,
363
- format: formatJson,
364
- parse: parseJsonObject,
365
- inactive,
366
- },
367
- ];
368
- }
369
- function observedProducerItems(id, label, value, inactive) {
370
- return [
371
- {
372
- id: `outputRouter.observedRouting.${id}.enabled`,
373
- label: `${label} observed`,
374
- description: `Enable observed routing for ${label.toLowerCase()} output.`,
375
- path: ["outputRouter", "observedRouting", id, "enabled"],
376
- kind: "boolean",
377
- value: value?.enabled === true,
378
- defaultValue: false,
379
- inactive,
380
- },
381
- {
382
- id: `outputRouter.observedRouting.${id}.persistence`,
383
- label: `${label} persistence`,
384
- description: "Persistence mode for this observed producer: none, metadata-only, or exact.",
385
- path: ["outputRouter", "observedRouting", id, "persistence"],
386
- kind: "enum",
387
- value: value?.persistence ?? "none",
388
- values: [...OBSERVED_PERSISTENCE_VALUES],
389
- valueDescriptions: {
390
- none: "Do not persist observed output",
391
- "metadata-only": "Persist metadata without raw output",
392
- exact: "Persist exact recoverable output",
393
- },
394
- defaultValue: value?.enabled === true ? undefined : "none",
395
- inactive,
396
- },
397
- ];
398
- }
399
92
  function configValueForChoice(item, value) {
400
93
  const key = String(value);
401
94
  if (item.configValues && Object.hasOwn(item.configValues, key)) {
@@ -404,10 +97,15 @@ function configValueForChoice(item, value) {
404
97
  return value;
405
98
  }
406
99
  function effectiveItemValue(item) {
407
- return item.effectiveValue !== undefined ? item.effectiveValue : item.value;
100
+ return item.effectiveValue === undefined ? item.value : item.effectiveValue;
408
101
  }
409
102
  function formatCoreValue(value) {
410
- return typeof value === "boolean" ? booleanValue(value) : String(value ?? "");
103
+ if (typeof value === "boolean") return booleanValue(value);
104
+ if (isRecord(value) && typeof value.provider === "string" && typeof value.model === "string") {
105
+ const effort = typeof value.thinkingLevel === "string" ? ` · ${value.thinkingLevel}` : "";
106
+ return `${value.provider}/${value.model}${effort}`;
107
+ }
108
+ return String(value ?? "");
411
109
  }
412
110
  function coreDisplaySuffix(item, inactive = false) {
413
111
  const source = item.effectiveSource ?? "builtin";
@@ -494,78 +192,18 @@ function createScopedBooleanItem(options) {
494
192
  item.displaySuffix = coreDisplaySuffix(item);
495
193
  return item;
496
194
  }
497
- function createScopedDefaultModeItem(options) {
498
- let item;
499
- if (options.scope === "local") {
500
- item = {
501
- id: "freeflow.defaultMode",
502
- label: "Default mode",
503
- description:
504
- "Personal default mode for this checkout. Choose inherit to use the repository default; use /freeflow settings repo to edit shared defaults.",
505
- path: ["defaultMode"],
506
- kind: "enum",
507
- value: options.localDefaultMode ?? LOCAL_INHERIT,
508
- values: [LOCAL_INHERIT, ...MODE_VALUES],
509
- valueLabels: {
510
- inherit: "Inherit repository",
511
- ...MODE_LABELS,
512
- },
513
- valueDescriptions: {
514
- inherit: `Use ${options.repositoryDefaultMode} from ${options.repositoryDefaultSource}.`,
515
- ...MODE_DESCRIPTIONS,
516
- },
517
- format: (value) => String(value),
518
- configScope: "local",
519
- configValues: { inherit: undefined },
520
- effectiveValue: options.effectiveValue,
521
- effectiveSource: options.effectiveSource,
522
- inheritedValue: options.repositoryDefaultMode,
523
- inheritedSource: options.repositoryDefaultSource,
524
- };
525
- } else {
526
- item = {
527
- id: "freeflow.defaultMode",
528
- label: "Default mode",
529
- description:
530
- "Shared repository default Freeflow mode used when Skills are enabled. This edits .freeflow/config.json.",
531
- path: ["defaultMode"],
532
- kind: "enum",
533
- value: options.repositoryDefaultMode,
534
- values: MODE_VALUES,
535
- valueLabels: MODE_LABELS,
536
- valueDescriptions: MODE_DESCRIPTIONS,
537
- configScope: "repository",
538
- effectiveValue: options.effectiveValue,
539
- effectiveSource: options.effectiveSource,
540
- localOverrideValue: options.localDefaultMode,
541
- };
542
- }
543
- item.displaySuffix = coreDisplaySuffix(item);
544
- return item;
545
- }
546
- function sessionModeDisplaySuffix(sessionMode, defaultMode, defaultSource, inactive) {
547
- if (sessionMode !== "default") return undefined;
548
- const inactiveSuffix = inactive ? " · inactive" : "";
549
- return `(${defaultMode} · ${defaultSource}${inactiveSuffix})`;
550
- }
551
- function validModeOrUndefined(value) {
552
- return typeof value === "string" && VALID_MODES.has(value) ? value : undefined;
553
- }
554
195
  function resolveSettingsCoreView(rawConfig, layers) {
555
196
  const localConfig = layers?.local.valid && isRecord(layers.local.parsed) ? layers.local.parsed : {};
556
- const repositorySkillsValue = getPath(rawConfig, ["skills"]);
557
- const repositorySkills = isRecord(repositorySkillsValue) ? repositorySkillsValue : {};
558
197
  const fallbackCore = {
559
198
  enabled: getPath(rawConfig, ["enabled"]) !== false,
560
- interactionContract: getPath(rawConfig, ["interactionContract"]) !== false,
561
- skills: { enabled: getPath(repositorySkills, ["enabled"]) !== false },
562
- defaultMode: validModeOrUndefined(rawConfig.defaultMode) ?? "workflow",
199
+ contextVirtualization: getPath(rawConfig, ["contextVirtualization"]) === true,
200
+ conversationHistory: getPath(rawConfig, ["conversationHistory"]) === true,
563
201
  };
564
202
  const fallbackSources = {
565
203
  enabled: typeof getPath(rawConfig, ["enabled"]) === "boolean" ? "repository" : "builtin",
566
- interactionContract: typeof getPath(rawConfig, ["interactionContract"]) === "boolean" ? "repository" : "builtin",
567
- skillsEnabled: typeof getPath(repositorySkills, ["enabled"]) === "boolean" ? "repository" : "builtin",
568
- defaultMode: validModeOrUndefined(rawConfig.defaultMode) !== undefined ? "repository" : "builtin",
204
+ contextVirtualization:
205
+ typeof getPath(rawConfig, ["contextVirtualization"]) === "boolean" ? "repository" : "builtin",
206
+ conversationHistory: typeof getPath(rawConfig, ["conversationHistory"]) === "boolean" ? "repository" : "builtin",
569
207
  };
570
208
  return {
571
209
  localConfig,
@@ -573,9 +211,6 @@ function resolveSettingsCoreView(rawConfig, layers) {
573
211
  sources: layers?.sources ?? fallbackSources,
574
212
  };
575
213
  }
576
- function groupEnabled(items, id) {
577
- return items.find((item) => item.id === id)?.value === true;
578
- }
579
214
  function createSessionBooleanItem(options) {
580
215
  const override = options.sessionOverrides[options.key];
581
216
  const item = {
@@ -613,7 +248,7 @@ function createSessionBooleanItem(options) {
613
248
  item.displaySuffix = coreDisplaySuffix(item);
614
249
  return item;
615
250
  }
616
- function sessionFreeflowItems(state, modeState) {
251
+ function sessionFreeflowItems(state, cognitiveRoutingController) {
617
252
  const sessionOverrides = state.sessionOverrides;
618
253
  const configured = state.configuredCoreConfig;
619
254
  const configuredSources = state.configuredSources;
@@ -629,67 +264,68 @@ function sessionFreeflowItems(state, modeState) {
629
264
  effectiveSource: effectiveSources.enabled,
630
265
  sessionOverrides,
631
266
  });
632
- const interactionItem = createSessionBooleanItem({
633
- id: "freeflow.interactionContract",
634
- label: "Interaction Contract",
635
- description: "Temporary Interaction Contract override for this Pi session.",
636
- key: "interactionContract",
637
- inheritedValue: configured.interactionContract,
638
- inheritedSource: configuredSources.interactionContract,
639
- effectiveValue: state.interactionContract.enabled,
640
- effectiveSource: effectiveSources.interactionContract,
267
+ const contextVirtualizationItem = createSessionBooleanItem({
268
+ id: "freeflow.contextVirtualization",
269
+ label: "Context Virtualization",
270
+ description: "Temporary Context Virtualization override for this Pi session.",
271
+ key: "contextVirtualization",
272
+ inheritedValue: configured.contextVirtualization,
273
+ inheritedSource: configuredSources.contextVirtualization,
274
+ effectiveValue: state.contextVirtualization.enabled,
275
+ effectiveSource: effectiveSources.contextVirtualization,
641
276
  sessionOverrides,
642
277
  });
643
- const skillsItem = createSessionBooleanItem({
644
- id: "freeflow.skills.enabled",
645
- label: "Skills",
646
- description: "Temporary Freeflow Skills override for this Pi session.",
647
- key: "skillsEnabled",
648
- inheritedValue: configured.skills.enabled,
649
- inheritedSource: configuredSources.skillsEnabled,
650
- effectiveValue: state.skills.enabled,
651
- effectiveSource: effectiveSources.skillsEnabled,
278
+ const conversationHistoryItem = createSessionBooleanItem({
279
+ id: "freeflow.conversationHistory",
280
+ label: "Conversation History",
281
+ description: "Temporary Conversation History override for this Pi session.",
282
+ key: "conversationHistory",
283
+ inheritedValue: configured.conversationHistory,
284
+ inheritedSource: configuredSources.conversationHistory,
285
+ effectiveValue: state.conversationHistory.enabled,
286
+ effectiveSource: effectiveSources.conversationHistory,
652
287
  sessionOverrides,
653
288
  });
654
289
  const freeflowInactive = !state.enabled;
655
- const skillsEnabled = state.skills.enabled;
656
- interactionItem.inactive = freeflowInactive;
657
- skillsItem.inactive = freeflowInactive;
658
- const sessionMode = modeState.currentMode ?? "default";
659
- const sessionModeItem = {
660
- id: "freeflow.sessionMode",
661
- label: "Mode",
662
- description: "Temporary mode override for this Pi session.",
663
- kind: "enum",
664
- value: sessionMode,
665
- values: ["default", ...MODE_VALUES],
666
- valueLabels: {
667
- ...MODE_LABELS,
668
- default: "Use configured default",
669
- },
670
- valueDescriptions: {
671
- default: `${modeState.defaultMode} from ${modeState.defaultModeSource}`,
672
- ...MODE_DESCRIPTIONS,
673
- },
674
- inactive: freeflowInactive || !skillsEnabled,
675
- displaySuffix: sessionModeDisplaySuffix(
676
- sessionMode,
677
- modeState.defaultMode,
678
- modeState.defaultModeSource,
679
- freeflowInactive || !skillsEnabled,
680
- ),
681
- inheritedValue: modeState.defaultMode,
682
- inheritedSource: modeState.defaultModeSource,
683
- };
290
+ contextVirtualizationItem.inactive = freeflowInactive;
291
+ conversationHistoryItem.inactive = freeflowInactive;
292
+ const cognitiveRoutingState = cognitiveRoutingController?.state();
293
+ const cognitiveRoutingProfile =
294
+ cognitiveRoutingState?.controlMode === "manual-standard"
295
+ ? "standard"
296
+ : cognitiveRoutingState?.controlMode === "manual-reasoning"
297
+ ? "reasoning"
298
+ : "auto";
299
+ const cognitiveRoutingItem = state.cognitiveRouting
300
+ ? {
301
+ id: "freeflow.cognitiveRouting.profile",
302
+ label: "Cognitive Routing",
303
+ description: "Hold a profile manually or release the hold for automatic model control.",
304
+ kind: "enum",
305
+ value: cognitiveRoutingProfile,
306
+ values: ["auto", "standard", "reasoning"],
307
+ valueLabels: {
308
+ auto: "automatic",
309
+ standard: "manual · standard",
310
+ reasoning: "manual · reasoning",
311
+ },
312
+ valueDescriptions: {
313
+ auto: "Release the manual hold without forcing a model transition.",
314
+ standard: "Hold the standard profile until /freeflow profile auto.",
315
+ reasoning: "Hold the reasoning profile until /freeflow profile auto.",
316
+ },
317
+ inactive: freeflowInactive || !state.cognitiveRouting.effective || cognitiveRoutingController === undefined,
318
+ runtimeInactive: state.cognitiveRouting.blockingReason?.code === "runtime_disabled",
319
+ displaySuffix: cognitiveRoutingState?.effective ? cognitiveRoutingProfile : "unavailable",
320
+ }
321
+ : undefined;
684
322
  return [
685
323
  freeflowItem,
686
- interactionItem,
687
- skillsItem,
688
- sessionModeItem,
324
+ ...(cognitiveRoutingItem ? [cognitiveRoutingItem] : []),
689
325
  {
690
326
  id: "freeflow.session.reset",
691
327
  label: "Reset session overrides",
692
- description: "Clear Freeflow, Interaction Contract, Skills, and mode overrides for this Pi session.",
328
+ description: "Clear Freeflow, Context Virtualization, and Conversation History overrides for this Pi session.",
693
329
  kind: "enum",
694
330
  value: "available",
695
331
  values: ["reset"],
@@ -698,9 +334,308 @@ function sessionFreeflowItems(state, modeState) {
698
334
  format: () => "available",
699
335
  transient: true,
700
336
  },
337
+ {
338
+ id: "freeflow.context",
339
+ label: "Freeflow Context",
340
+ description: "Choose which context projection and conversation-history operations are available to the model.",
341
+ kind: "group",
342
+ value: contextVirtualizationItem.effectiveValue === true || conversationHistoryItem.effectiveValue === true,
343
+ displaySuffix: `${[contextVirtualizationItem, conversationHistoryItem].filter((item) => item.effectiveValue === true).length}/2 enabled`,
344
+ children: [contextVirtualizationItem, conversationHistoryItem],
345
+ },
346
+ ];
347
+ }
348
+ const COGNITIVE_ROUTING_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
349
+ const COGNITIVE_ROUTING_EFFORT_DESCRIPTIONS = {
350
+ off: "No reasoning",
351
+ minimal: "Very brief reasoning",
352
+ low: "Light reasoning",
353
+ medium: "Moderate reasoning",
354
+ high: "Deep reasoning",
355
+ xhigh: "Extra-high reasoning",
356
+ max: "Maximum reasoning",
357
+ };
358
+ function cognitiveRoutingModelKey(provider, model) {
359
+ return `${provider}/${model}`;
360
+ }
361
+ function isCognitiveRoutingProfile(value) {
362
+ return (
363
+ isRecord(value) &&
364
+ typeof value.provider === "string" &&
365
+ typeof value.model === "string" &&
366
+ typeof value.thinkingLevel === "string" &&
367
+ COGNITIVE_ROUTING_THINKING_LEVELS.includes(value.thinkingLevel)
368
+ );
369
+ }
370
+ function cognitiveRoutingThinkingLevels(model, registry) {
371
+ if (typeof registry?.clampThinkingLevel !== "function") {
372
+ return model?.reasoning === true ? [...COGNITIVE_ROUTING_THINKING_LEVELS] : ["off"];
373
+ }
374
+ return COGNITIVE_ROUTING_THINKING_LEVELS.filter((level) => {
375
+ try {
376
+ return registry.clampThinkingLevel(model, level) === level;
377
+ } catch {
378
+ return false;
379
+ }
380
+ });
381
+ }
382
+ function cognitiveRoutingModelOptions(ctx) {
383
+ const registry = ctx?.modelRegistry;
384
+ if (typeof registry?.getAvailable !== "function") return [];
385
+ let models;
386
+ try {
387
+ models = registry.getAvailable();
388
+ } catch {
389
+ return [];
390
+ }
391
+ if (!Array.isArray(models)) return [];
392
+ return models
393
+ .filter((model) => typeof model?.provider === "string" && typeof model?.id === "string")
394
+ .map((model) => {
395
+ const thinkingLevels = cognitiveRoutingThinkingLevels(model, registry);
396
+ const provider = model.provider;
397
+ const modelId = model.id;
398
+ const key = cognitiveRoutingModelKey(provider, modelId);
399
+ const displayName = typeof model.name === "string" && model.name !== modelId ? model.name : undefined;
400
+ return {
401
+ key,
402
+ provider,
403
+ model: modelId,
404
+ label: key,
405
+ description: displayName ? `${displayName} · ${thinkingLevels.join(", ")}` : thinkingLevels.join(", "),
406
+ thinkingLevels,
407
+ };
408
+ })
409
+ .filter((model) => model.thinkingLevels.length > 0)
410
+ .sort((a, b) => a.label.localeCompare(b.label));
411
+ }
412
+ function cognitiveRoutingProfileDisplay(value) {
413
+ return isCognitiveRoutingProfile(value) ? formatCoreValue(value) : "not configured";
414
+ }
415
+ function cognitiveRoutingSettingsSource(source) {
416
+ if (source === "repository") return "repository";
417
+ if (source === "personal" || source === "local") return "local";
418
+ return "builtin";
419
+ }
420
+ function cognitiveRoutingProfileDisplaySuffix(source, scope) {
421
+ if (!source) return undefined;
422
+ if (source === "personal" && scope === "repository") return "(effective personal)";
423
+ return `(${source})`;
424
+ }
425
+ function cognitiveRoutingConfirmStep(summary) {
426
+ return {
427
+ title: "Confirm preset",
428
+ choices: [
429
+ {
430
+ key: "__save__",
431
+ value: "save",
432
+ label: `Save ${summary}`,
433
+ description: "Write the complete profile atomically.",
434
+ },
435
+ { key: "__cancel__", value: "cancel", label: "Cancel", description: "Leave the previous preset unchanged." },
436
+ ],
437
+ selectedKey: "__save__",
438
+ };
439
+ }
440
+ function createCognitiveRoutingProfileWizard(currentValue, models, allowInherit) {
441
+ const currentProfile = isCognitiveRoutingProfile(currentValue) ? currentValue : undefined;
442
+ const currentModelKey = currentProfile
443
+ ? cognitiveRoutingModelKey(currentProfile.provider, currentProfile.model)
444
+ : undefined;
445
+ const firstChoices = [
446
+ ...(allowInherit
447
+ ? [
448
+ {
449
+ key: LOCAL_INHERIT,
450
+ value: LOCAL_INHERIT,
451
+ label: "Inherit repository preset",
452
+ description: "Remove the personal override without changing the repository preset.",
453
+ },
454
+ ]
455
+ : []),
456
+ ...models.map((model) => ({
457
+ key: model.key,
458
+ value: model,
459
+ label: model.label,
460
+ description: model.description,
461
+ })),
701
462
  ];
463
+ if (firstChoices.length === 0) {
464
+ firstChoices.push({
465
+ key: "__cancel__",
466
+ value: "cancel",
467
+ label: "No authenticated models available",
468
+ description: "Authenticate a provider before configuring this preset.",
469
+ });
470
+ }
471
+ return {
472
+ firstStep: () => ({
473
+ title: "Choose model",
474
+ choices: firstChoices,
475
+ selectedKey:
476
+ currentValue === LOCAL_INHERIT
477
+ ? LOCAL_INHERIT
478
+ : firstChoices.some((choice) => choice.key === currentModelKey)
479
+ ? currentModelKey
480
+ : firstChoices[0]?.key,
481
+ }),
482
+ nextStep: (selectedValues) => {
483
+ if (selectedValues.length === 1) {
484
+ const selected = selectedValues[0];
485
+ if (selected === LOCAL_INHERIT) {
486
+ return cognitiveRoutingConfirmStep("inherit repository preset");
487
+ }
488
+ const model = selected;
489
+ if (!model?.key || model.thinkingLevels.length === 0) return undefined;
490
+ const currentEffort =
491
+ currentProfile && currentModelKey === model.key ? currentProfile.thinkingLevel : undefined;
492
+ return {
493
+ title: `Choose effort · ${model.label}`,
494
+ choices: model.thinkingLevels.map((level) => ({
495
+ key: level,
496
+ value: level,
497
+ label: level,
498
+ description: COGNITIVE_ROUTING_EFFORT_DESCRIPTIONS[level],
499
+ })),
500
+ selectedKey:
501
+ currentEffort && model.thinkingLevels.includes(currentEffort) ? currentEffort : model.thinkingLevels[0],
502
+ };
503
+ }
504
+ if (selectedValues.length === 2) {
505
+ const model = selectedValues[0];
506
+ const effort = selectedValues[1];
507
+ return cognitiveRoutingConfirmStep(`${model.label} · ${effort}`);
508
+ }
509
+ return undefined;
510
+ },
511
+ valueFromSelections: (selectedValues) => {
512
+ if (selectedValues[0] === LOCAL_INHERIT) return undefined;
513
+ const model = selectedValues[0];
514
+ return {
515
+ provider: model.provider,
516
+ model: model.model,
517
+ thinkingLevel: selectedValues[1],
518
+ };
519
+ },
520
+ };
521
+ }
522
+ function cognitiveRoutingProfileItem(options) {
523
+ const path = ["cognitiveRouting", "profiles", options.name];
524
+ const repositoryProfile = getPath(options.rawConfig, path);
525
+ const localProfile = getPath(options.localConfig, path);
526
+ const effectiveProfile = options.capabilityState?.profiles?.[options.name] ?? repositoryProfile;
527
+ const localValue = isCognitiveRoutingProfile(localProfile) ? localProfile : LOCAL_INHERIT;
528
+ const value = options.scope === "local" ? localValue : repositoryProfile;
529
+ const source =
530
+ options.capabilityState?.profileSources?.[options.name] ??
531
+ (isCognitiveRoutingProfile(repositoryProfile) ? "repository" : undefined);
532
+ const models = cognitiveRoutingModelOptions(options.ctx);
533
+ const inactive = options.scope === "local" && models.length === 0 && localValue !== LOCAL_INHERIT;
534
+ return {
535
+ id: `freeflow.cognitiveRouting.${options.name}`,
536
+ label: `${options.name.charAt(0).toUpperCase()}${options.name.slice(1)} preset`,
537
+ description:
538
+ "Choose an authenticated available model and one effort supported by that model. Confirming writes the complete preset; cancel leaves it unchanged.",
539
+ path,
540
+ kind: "string",
541
+ value,
542
+ format: cognitiveRoutingProfileDisplay,
543
+ configScope: options.scope,
544
+ effectiveValue: effectiveProfile,
545
+ effectiveSource: cognitiveRoutingSettingsSource(source),
546
+ inheritedValue: repositoryProfile,
547
+ inheritedSource: "repository",
548
+ inactive,
549
+ displaySuffix: cognitiveRoutingProfileDisplaySuffix(source, options.scope),
550
+ wizard: () => createCognitiveRoutingProfileWizard(value, models, options.scope === "local"),
551
+ };
552
+ }
553
+ function cognitiveRoutingSessionStartItem(options) {
554
+ const path = ["cognitiveRouting", "sessionStart", options.setting];
555
+ const defaultValue = options.setting === "control" ? "automatic" : "reasoning";
556
+ const values = options.setting === "control" ? ["automatic", "manual"] : ["standard", "reasoning"];
557
+ const labels = Object.fromEntries(values.map((value) => [value, value]));
558
+ const descriptions =
559
+ options.setting === "control"
560
+ ? {
561
+ automatic: "Start the new session in Reasoning; automatic delegation may use Standard during execution.",
562
+ manual: "Hold the selected profile for the new session until the user changes it.",
563
+ }
564
+ : {
565
+ standard: "Start a new manual session with the configured Standard preset.",
566
+ reasoning: "Start a new manual session with the configured Reasoning preset.",
567
+ };
568
+ const repositoryValue = getPath(options.rawConfig, path);
569
+ const localValue = getPath(options.localConfig, path);
570
+ const isValidValue = (value) => values.includes(value);
571
+ const repositorySetting = isValidValue(repositoryValue) ? repositoryValue : undefined;
572
+ const localSetting = isValidValue(localValue) ? localValue : undefined;
573
+ const inheritedValue = repositorySetting ?? defaultValue;
574
+ const inheritedSource = repositorySetting ? "repository" : "builtin";
575
+ const configuredSource =
576
+ options.capabilityState?.sessionStartSources?.[options.setting] ?? (repositorySetting ? "repository" : undefined);
577
+ const effectiveSessionStartControl =
578
+ options.capabilityState?.sessionStart?.control ??
579
+ getPath(options.localConfig, ["cognitiveRouting", "sessionStart", "control"]) ??
580
+ getPath(options.rawConfig, ["cognitiveRouting", "sessionStart", "control"]) ??
581
+ "automatic";
582
+ const effectiveValue =
583
+ options.setting === "profile" && effectiveSessionStartControl === "automatic"
584
+ ? "reasoning"
585
+ : (options.capabilityState?.sessionStart?.[options.setting] ?? inheritedValue);
586
+ const effectiveSource = cognitiveRoutingSettingsSource(configuredSource);
587
+ const label = options.setting === "control" ? "Session start control" : "Manual session-start profile";
588
+ const description =
589
+ options.setting === "control"
590
+ ? "Choose automatic or manual control for new sessions only; changing this does not alter the active session."
591
+ : "Choose the profile for manual session starts; automatic sessions always start in Reasoning.";
592
+ const item =
593
+ options.scope === "local"
594
+ ? {
595
+ id: `freeflow.cognitiveRouting.sessionStart.${options.setting}`,
596
+ label,
597
+ description: `${description} Choose inherit to use the repository value; use /freeflow settings repo to edit shared defaults.`,
598
+ path,
599
+ kind: "enum",
600
+ value: localSetting ?? LOCAL_INHERIT,
601
+ values: [LOCAL_INHERIT, ...values],
602
+ valueLabels: { inherit: "Inherit repository", ...labels },
603
+ valueDescriptions: {
604
+ inherit: `Use ${inheritedValue} from ${inheritedSource}.`,
605
+ ...descriptions,
606
+ },
607
+ format: (value) => String(value),
608
+ configScope: "local",
609
+ configValues: { inherit: undefined },
610
+ effectiveValue,
611
+ effectiveSource,
612
+ inheritedValue,
613
+ inheritedSource,
614
+ }
615
+ : {
616
+ id: `freeflow.cognitiveRouting.sessionStart.${options.setting}`,
617
+ label,
618
+ description: `${description} This edits shared .freeflow/config.json.`,
619
+ path,
620
+ kind: "enum",
621
+ value: inheritedValue,
622
+ values,
623
+ valueLabels: labels,
624
+ valueDescriptions: descriptions,
625
+ format: (value) => String(value),
626
+ defaultValue,
627
+ configScope: "repository",
628
+ effectiveValue,
629
+ effectiveSource,
630
+ localOverrideValue: localSetting,
631
+ };
632
+ item.displaySuffix = coreDisplaySuffix(item);
633
+ return item;
702
634
  }
703
- function freeflowItems(rawConfig, modeState, options = {}) {
635
+ function isCognitiveRoutingRuntimeAvailable(pi) {
636
+ return isPiFlowHost(pi?.host) || supportsPiSessionModelStateApi(pi);
637
+ }
638
+ function freeflowItems(rawConfig, options = {}) {
704
639
  const scope = options.scope ?? "repository";
705
640
  const layers = options.layers;
706
641
  const { localConfig, core, sources } = resolveSettingsCoreView(rawConfig, layers);
@@ -710,248 +645,128 @@ function freeflowItems(rawConfig, modeState, options = {}) {
710
645
  localConfig,
711
646
  id: "freeflow.enabled",
712
647
  label: "Freeflow",
713
- description: "Master switch for the Interaction Contract and Freeflow skills in this checkout.",
648
+ description: "Master switch for Freeflow's core guidance, skills, and optional capabilities in this checkout.",
714
649
  path: ["enabled"],
715
650
  effectiveValue: core.enabled,
716
651
  effectiveSource: sources.enabled,
717
652
  defaultValue: DEFAULT_FREEFLOW_ENABLED,
718
653
  });
719
- const interactionItem = createScopedBooleanItem({
654
+ const contextVirtualizationItem = createScopedBooleanItem({
720
655
  scope,
721
656
  rawConfig,
722
657
  localConfig,
723
- id: "freeflow.interactionContract",
724
- label: "Interaction Contract",
725
- description: "Apply Freeflow's compact turn-interpretation and collaboration guidance.",
726
- path: ["interactionContract"],
727
- effectiveValue: core.interactionContract,
728
- effectiveSource: sources.interactionContract,
729
- defaultValue: DEFAULT_INTERACTION_CONTRACT_ENABLED,
658
+ id: "freeflow.contextVirtualization",
659
+ label: "Context Virtualization",
660
+ description: "Let the model archive consumed tool results from future context while preserving session history.",
661
+ path: ["contextVirtualization"],
662
+ effectiveValue: core.contextVirtualization,
663
+ effectiveSource: sources.contextVirtualization,
664
+ defaultValue: DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED,
730
665
  });
731
- const skillsItem = createScopedBooleanItem({
666
+ const conversationHistoryItem = createScopedBooleanItem({
732
667
  scope,
733
668
  rawConfig,
734
669
  localConfig,
735
- id: "freeflow.skills.enabled",
736
- label: "Skills",
737
- description: "Expose Freeflow skills and load Workflow once on the first turn.",
738
- path: ["skills", "enabled"],
739
- effectiveValue: core.skills.enabled,
740
- effectiveSource: sources.skillsEnabled,
741
- defaultValue: DEFAULT_SKILLS_ENABLED,
742
- });
743
- const repositoryModeValue = validModeOrUndefined(rawConfig.defaultMode);
744
- const repositoryDefaultMode = repositoryModeValue ?? "workflow";
745
- const repositoryDefaultSource = repositoryModeValue ? "repository" : "builtin";
746
- const localDefaultMode = validModeOrUndefined(localConfig.defaultMode);
747
- const defaultModeItem = createScopedDefaultModeItem({
748
- scope,
749
- repositoryDefaultMode,
750
- repositoryDefaultSource,
751
- localDefaultMode,
752
- effectiveValue: core.defaultMode,
753
- effectiveSource: sources.defaultMode,
670
+ id: "freeflow.conversationHistory",
671
+ label: "Conversation History",
672
+ description: "Let the model search and retrieve hidden conversation history on the active branch.",
673
+ path: ["conversationHistory"],
674
+ effectiveValue: core.conversationHistory,
675
+ effectiveSource: sources.conversationHistory,
676
+ defaultValue: DEFAULT_CONVERSATION_HISTORY_ENABLED,
754
677
  });
755
678
  const freeflowInactive = !core.enabled;
756
- const skillsEnabled = core.skills.enabled;
757
- interactionItem.inactive = freeflowInactive;
758
- skillsItem.inactive = freeflowInactive;
759
- defaultModeItem.inactive = freeflowInactive;
760
- defaultModeItem.displaySuffix = coreDisplaySuffix(defaultModeItem, freeflowInactive || !skillsEnabled);
761
- const sessionMode = modeState?.currentMode ?? "default";
762
- const sessionModeItem = {
763
- id: "freeflow.sessionMode",
764
- label: "Session mode",
765
- description:
766
- "Temporary mode override for this Pi session. Use configured default clears the override without changing either config file.",
767
- kind: "enum",
768
- value: sessionMode,
769
- values: ["default", ...MODE_VALUES],
770
- valueLabels: {
771
- ...MODE_LABELS,
772
- default: "Use configured default",
773
- },
774
- valueDescriptions: {
775
- default: `${core.defaultMode} from ${sources.defaultMode}`,
776
- ...MODE_DESCRIPTIONS,
777
- },
778
- inactive: freeflowInactive || !skillsEnabled,
779
- displaySuffix: sessionModeDisplaySuffix(
780
- sessionMode,
781
- core.defaultMode,
782
- sources.defaultMode,
783
- freeflowInactive || !skillsEnabled,
784
- ),
785
- };
786
- const routerItems = outputRouterItems(rawConfig, freeflowInactive);
787
- const routerEnabled = groupEnabled(routerItems, "outputRouter.enabled");
679
+ contextVirtualizationItem.inactive = freeflowInactive;
680
+ conversationHistoryItem.inactive = freeflowInactive;
681
+ const cognitiveRoutingState = options.cognitiveRouting;
682
+ const cognitiveRoutingRuntimeDisabled = options.runtimeAvailable !== true;
683
+ const cognitiveRoutingGroup = (() => {
684
+ const cognitiveRoutingEnabledItem = createScopedBooleanItem({
685
+ scope,
686
+ rawConfig,
687
+ localConfig,
688
+ id: "freeflow.cognitiveRouting.enabled",
689
+ label: "Enabled",
690
+ description: cognitiveRoutingRuntimeDisabled
691
+ ? "Requires a host model-state control API; configuration is read-only on this host."
692
+ : "Allow Cognitive Routing to own the session model lease and switch the configured profiles.",
693
+ path: ["cognitiveRouting", "enabled"],
694
+ effectiveValue: cognitiveRoutingState?.enabled ?? false,
695
+ effectiveSource: cognitiveRoutingSettingsSource(cognitiveRoutingState?.enabledSource),
696
+ defaultValue: false,
697
+ });
698
+ cognitiveRoutingEnabledItem.inactive = freeflowInactive || cognitiveRoutingRuntimeDisabled;
699
+ cognitiveRoutingEnabledItem.runtimeInactive = cognitiveRoutingRuntimeDisabled;
700
+ const cognitiveRoutingProfiles = ["standard", "reasoning"].map((name) =>
701
+ cognitiveRoutingProfileItem({
702
+ name: name,
703
+ scope,
704
+ rawConfig,
705
+ localConfig,
706
+ capabilityState: cognitiveRoutingState,
707
+ ctx: options.ctx,
708
+ }),
709
+ );
710
+ const cognitiveRoutingSessionStart = ["control", "profile"].map((setting) =>
711
+ cognitiveRoutingSessionStartItem({
712
+ setting,
713
+ scope,
714
+ rawConfig,
715
+ localConfig,
716
+ capabilityState: cognitiveRoutingState,
717
+ }),
718
+ );
719
+ for (const item of [...cognitiveRoutingProfiles, ...cognitiveRoutingSessionStart]) {
720
+ item.inactive ||= freeflowInactive || cognitiveRoutingRuntimeDisabled;
721
+ item.runtimeInactive = cognitiveRoutingRuntimeDisabled;
722
+ }
723
+ const cognitiveRoutingStatus = cognitiveRoutingRuntimeDisabled
724
+ ? "unavailable · host unsupported"
725
+ : cognitiveRoutingState
726
+ ? cognitiveRoutingState.effective
727
+ ? "active"
728
+ : cognitiveRoutingState.blockingReason?.code === "profile_missing"
729
+ ? "not configured"
730
+ : (cognitiveRoutingState.blockingReason?.code ?? "inactive")
731
+ : "unavailable";
732
+ return {
733
+ id: "freeflow.cognitiveRouting",
734
+ label: "Cognitive Routing",
735
+ description: cognitiveRoutingRuntimeDisabled
736
+ ? "Cognitive Routing configuration is visible for inspection but requires a host model-state control API."
737
+ : "Configure the Standard and Reasoning profiles and choose whether Freeflow may manage model state for this repository.",
738
+ kind: "group",
739
+ value: cognitiveRoutingRuntimeDisabled ? false : (cognitiveRoutingState?.enabled ?? false),
740
+ inactive: freeflowInactive,
741
+ displaySuffix: cognitiveRoutingStatus,
742
+ children: [cognitiveRoutingEnabledItem, ...cognitiveRoutingProfiles, ...cognitiveRoutingSessionStart],
743
+ };
744
+ })();
788
745
  return [
789
746
  freeflowItem,
790
- interactionItem,
791
- skillsItem,
792
- sessionModeItem,
793
- defaultModeItem,
747
+ ...(cognitiveRoutingGroup ? [cognitiveRoutingGroup] : []),
794
748
  {
795
- id: "outputRouter.group",
796
- label: "Output Router",
797
- description:
798
- "Shared repository settings for routed evidence tools, native output safety net, vault storage, script transforms, and observed tool routing.",
749
+ id: "freeflow.context",
750
+ label: "Freeflow Context",
751
+ description: "Choose which context projection and conversation-history operations are available to the model.",
799
752
  kind: "group",
800
- value: routerEnabled,
801
- inactive: freeflowInactive,
802
- displaySuffix: "(repository)",
803
- children: routerItems,
753
+ value: contextVirtualizationItem.effectiveValue === true || conversationHistoryItem.effectiveValue === true,
754
+ displaySuffix: `${[contextVirtualizationItem, conversationHistoryItem].filter((item) => item.effectiveValue === true).length}/2 enabled`,
755
+ children: [contextVirtualizationItem, conversationHistoryItem],
804
756
  },
805
757
  ];
806
758
  }
807
- function migrateLegacyRouterConfig(config) {
808
- const outputRouter = isRecord(config.outputRouter) ? config.outputRouter : undefined;
809
- if (!outputRouter) return;
810
- if (config.scriptTransform !== undefined && outputRouter.scriptTransform === undefined) {
811
- outputRouter.scriptTransform = config.scriptTransform;
812
- delete config.scriptTransform;
813
- }
814
- if (config.observedRouting !== undefined && outputRouter.observedRouting === undefined) {
815
- outputRouter.observedRouting = config.observedRouting;
816
- delete config.observedRouting;
817
- }
818
- const thresholds = isRecord(outputRouter.thresholds) ? outputRouter.thresholds : {};
819
- if (outputRouter.largeOutputBytes !== undefined && thresholds.largeOutputBytes === undefined) {
820
- thresholds.largeOutputBytes = outputRouter.largeOutputBytes;
821
- outputRouter.thresholds = thresholds;
822
- delete outputRouter.largeOutputBytes;
823
- }
824
- if (outputRouter.largeOutputLines !== undefined && thresholds.largeOutputLines === undefined) {
825
- thresholds.largeOutputLines = outputRouter.largeOutputLines;
826
- outputRouter.thresholds = thresholds;
827
- delete outputRouter.largeOutputLines;
828
- }
829
- const vault = isRecord(outputRouter.vault) ? outputRouter.vault : {};
830
- if (outputRouter.vaultRoot !== undefined && vault.root === undefined) {
831
- vault.root = outputRouter.vaultRoot;
832
- outputRouter.vault = vault;
833
- delete outputRouter.vaultRoot;
834
- }
835
- if (outputRouter.vaultRetentionDays !== undefined && !isRecord(vault.retention)) {
836
- vault.retention = {
837
- strategy: "ttl",
838
- ttlDays: outputRouter.vaultRetentionDays,
839
- };
840
- outputRouter.vault = vault;
841
- delete outputRouter.vaultRetentionDays;
842
- }
843
- const hints = isRecord(outputRouter.hints) ? outputRouter.hints : {};
844
- if (outputRouter.generatedPaths !== undefined && hints.generatedPathGlobs === undefined) {
845
- hints.generatedPathGlobs = outputRouter.generatedPaths;
846
- outputRouter.hints = hints;
847
- delete outputRouter.generatedPaths;
848
- }
849
- if (outputRouter.noisyCommandHints !== undefined && hints.noisyCommandPatterns === undefined) {
850
- hints.noisyCommandPatterns = outputRouter.noisyCommandHints;
851
- outputRouter.hints = hints;
852
- delete outputRouter.noisyCommandHints;
853
- }
854
- }
855
759
  function pruneKnownDefaults(config) {
856
760
  const defaultPaths = [
857
761
  { path: ["enabled"], value: DEFAULT_FREEFLOW_ENABLED },
858
- {
859
- path: ["interactionContract"],
860
- value: DEFAULT_INTERACTION_CONTRACT_ENABLED,
861
- },
862
- { path: ["skills", "enabled"], value: DEFAULT_SKILLS_ENABLED },
863
- { path: ["outputRouter", "enabled"], value: DEFAULT_OUTPUT_ROUTER_ENABLED },
864
- {
865
- path: ["outputRouter", "postToolRouting"],
866
- value: DEFAULT_POST_TOOL_ROUTING,
867
- },
868
- { path: ["outputRouter", "storagePolicy"], value: DEFAULT_STORAGE_POLICY },
869
- {
870
- path: ["outputRouter", "thresholds", "largeOutputBytes"],
871
- value: DEFAULT_ROUTER_THRESHOLDS.largeOutputBytes,
872
- },
873
- {
874
- path: ["outputRouter", "thresholds", "largeOutputLines"],
875
- value: DEFAULT_ROUTER_THRESHOLDS.largeOutputLines,
876
- },
877
- { path: ["outputRouter", "vault", "root"], value: DEFAULT_VAULT_ROOT },
878
- {
879
- path: ["outputRouter", "vault", "retention", "ttlDays"],
880
- value: DEFAULT_VAULT_RETENTION.ttlDays,
881
- },
882
- {
883
- path: ["outputRouter", "scriptTransform", "enabled"],
884
- value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.enabled,
885
- },
886
- {
887
- path: ["outputRouter", "scriptTransform", "sandbox"],
888
- value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.sandbox,
889
- },
890
- {
891
- path: ["outputRouter", "scriptTransform", "languages"],
892
- value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.languages,
893
- },
894
- {
895
- path: ["outputRouter", "scriptTransform", "network"],
896
- value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.network,
897
- },
898
- {
899
- path: ["outputRouter", "scriptTransform", "rawScriptPersistence"],
900
- value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.rawScriptPersistence,
901
- },
902
- {
903
- path: ["outputRouter", "scriptTransform", "limits", "timeoutMs"],
904
- value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.timeoutMs,
905
- },
906
- {
907
- path: ["outputRouter", "scriptTransform", "limits", "maxInputBytes"],
908
- value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.maxInputBytes,
909
- },
910
- {
911
- path: ["outputRouter", "scriptTransform", "limits", "maxOutputBytes"],
912
- value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.maxOutputBytes,
913
- },
914
- {
915
- path: ["outputRouter", "observedRouting", "enabled"],
916
- value: DEFAULT_OBSERVED_ROUTING_CONFIG.enabled,
917
- },
918
- {
919
- path: ["outputRouter", "observedRouting", "onRoutingFailure"],
920
- value: DEFAULT_OBSERVED_ROUTING_CONFIG.onRoutingFailure,
921
- },
922
- {
923
- path: ["outputRouter", "observedRouting", "mcp", "servers"],
924
- value: DEFAULT_OBSERVED_ROUTING_CONFIG.mcp.servers,
925
- },
926
- {
927
- path: ["outputRouter", "observedRouting", "web", "enabled"],
928
- value: DEFAULT_OBSERVED_ROUTING_CONFIG.web.enabled,
929
- },
930
- {
931
- path: ["outputRouter", "observedRouting", "fetch", "enabled"],
932
- value: DEFAULT_OBSERVED_ROUTING_CONFIG.fetch.enabled,
933
- },
934
- {
935
- path: ["outputRouter", "observedRouting", "codeSearch", "enabled"],
936
- value: DEFAULT_OBSERVED_ROUTING_CONFIG.codeSearch.enabled,
937
- },
762
+ { path: ["contextVirtualization"], value: DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED },
763
+ { path: ["conversationHistory"], value: DEFAULT_CONVERSATION_HISTORY_ENABLED },
938
764
  ];
939
765
  for (const item of defaultPaths) {
940
766
  if (valuesEqual(getPath(config, item.path), item.value)) {
941
767
  deletePath(config, item.path);
942
768
  }
943
769
  }
944
- for (const producer of ["web", "fetch", "codeSearch"]) {
945
- const enabledPath = ["outputRouter", "observedRouting", producer, "enabled"];
946
- const persistencePath = ["outputRouter", "observedRouting", producer, "persistence"];
947
- if (getPath(config, enabledPath) !== true && getPath(config, persistencePath) === "none") {
948
- deletePath(config, persistencePath);
949
- }
950
- }
951
- const servers = getPath(config, ["outputRouter", "observedRouting", "mcp", "servers"]);
952
- if (isRecord(servers) && Object.keys(servers).length === 0) {
953
- deletePath(config, ["outputRouter", "observedRouting", "mcp", "servers"]);
954
- }
955
770
  }
956
771
  async function ensureLocalConfigIgnored(cwd) {
957
772
  let gitPath;
@@ -1014,14 +829,18 @@ async function updateConfig(cwd, item, value, scope = item.configScope ?? "repos
1014
829
  const current = await readFreeflowConfig(cwd);
1015
830
  const next = cloneJson(current);
1016
831
  setConfigValue(next, item, value);
1017
- migrateLegacyRouterConfig(next);
1018
832
  pruneKnownDefaults(next);
1019
833
  await mkdir(join(cwd, ".freeflow"), { recursive: true });
1020
834
  await writeFile(join(cwd, ".freeflow/config.json"), `${JSON.stringify(next, null, 2)}\n`, "utf8");
1021
835
  }
836
+ function isCognitiveRoutingProfileItem(item) {
837
+ return item.id === "freeflow.cognitiveRouting.standard" || item.id === "freeflow.cognitiveRouting.reasoning";
838
+ }
1022
839
  function valueForDisplay(item) {
1023
840
  let value;
1024
- if (item.configScope === "local" || item.configScope === "session") {
841
+ if (isCognitiveRoutingProfileItem(item) && item.format) {
842
+ value = item.format(effectiveItemValue(item));
843
+ } else if (item.configScope === "local" || item.configScope === "session") {
1025
844
  value = formatCoreValue(effectiveItemValue(item));
1026
845
  } else if (item.kind === "boolean") {
1027
846
  value = booleanValue(item.value);
@@ -1053,51 +872,43 @@ function findSettingsItem(items, id) {
1053
872
  function refreshSettingsDerivedState(items) {
1054
873
  const freeflowItem = findSettingsItem(items, "freeflow.enabled");
1055
874
  const freeflowInactive = freeflowItem ? effectiveItemValue(freeflowItem) !== true : false;
1056
- const skillsItem = findSettingsItem(items, "freeflow.skills.enabled");
1057
- const skillsEnabled = skillsItem ? effectiveItemValue(skillsItem) === true : true;
1058
- const routerEnabled = findSettingsItem(items, "outputRouter.enabled")?.value === true;
1059
- const sessionModeItem = findSettingsItem(items, "freeflow.sessionMode");
1060
- const defaultModeItem = findSettingsItem(items, "freeflow.defaultMode");
1061
- const routerGroup = findSettingsItem(items, "outputRouter.group");
1062
- if (sessionModeItem) {
1063
- const defaultMode = String(
1064
- defaultModeItem ? effectiveItemValue(defaultModeItem) : (sessionModeItem.inheritedValue ?? "workflow"),
1065
- );
1066
- const defaultSource = defaultModeItem?.effectiveSource ?? sessionModeItem.inheritedSource ?? "builtin";
1067
- sessionModeItem.inactive = freeflowInactive || !skillsEnabled;
1068
- sessionModeItem.displaySuffix = sessionModeDisplaySuffix(
1069
- String(sessionModeItem.value),
1070
- defaultMode,
1071
- defaultSource,
1072
- freeflowInactive || !skillsEnabled,
875
+ const cognitiveRoutingGroup = findSettingsItem(items, "freeflow.cognitiveRouting");
876
+ const cognitiveRoutingEnabledItem = findSettingsItem(items, "freeflow.cognitiveRouting.enabled");
877
+ const cognitiveRoutingProfiles = [
878
+ findSettingsItem(items, "freeflow.cognitiveRouting.standard"),
879
+ findSettingsItem(items, "freeflow.cognitiveRouting.reasoning"),
880
+ ];
881
+ if (cognitiveRoutingGroup && cognitiveRoutingEnabledItem) {
882
+ const enabled = effectiveItemValue(cognitiveRoutingEnabledItem) === true;
883
+ const profilesConfigured = cognitiveRoutingProfiles.every((item) =>
884
+ item ? isCognitiveRoutingProfile(effectiveItemValue(item)) : false,
1073
885
  );
1074
- sessionModeItem.valueDescriptions = {
1075
- default: `${defaultMode} from ${defaultSource}`,
1076
- ...MODE_DESCRIPTIONS,
1077
- };
886
+ cognitiveRoutingGroup.value = enabled;
887
+ cognitiveRoutingGroup.displaySuffix = enabled
888
+ ? profilesConfigured
889
+ ? cognitiveRoutingGroup.displaySuffix === "active"
890
+ ? "active"
891
+ : "configured"
892
+ : "not configured"
893
+ : "disabled";
894
+ cognitiveRoutingGroup.inactive = freeflowInactive;
1078
895
  }
1079
- if (routerGroup) {
1080
- routerGroup.value = routerEnabled;
1081
- routerGroup.inactive = freeflowInactive;
896
+ const contextGroup = findSettingsItem(items, "freeflow.context");
897
+ if (contextGroup?.children?.length) {
898
+ const enabledCount = contextGroup.children.filter((item) => effectiveItemValue(item) === true).length;
899
+ contextGroup.value = enabledCount > 0;
900
+ contextGroup.displaySuffix = `${enabledCount}/${contextGroup.children.length} enabled`;
1082
901
  }
1083
902
  walkSettingsItems(items, (candidate) => {
1084
903
  if (candidate.id === "freeflow.session.reset") {
1085
904
  candidate.inactive = false;
1086
905
  } else if (candidate.configScope) {
1087
- const inactive = candidate.id === "freeflow.enabled" ? false : freeflowInactive;
1088
- const displayInactive = candidate.id === "freeflow.defaultMode" ? freeflowInactive || !skillsEnabled : inactive;
906
+ const inactive =
907
+ candidate.id === "freeflow.enabled" ? false : candidate.runtimeInactive === true || freeflowInactive;
1089
908
  candidate.inactive = inactive;
1090
- candidate.displaySuffix = coreDisplaySuffix(candidate, displayInactive);
1091
- } else if (candidate.id === "freeflow.sessionMode") {
1092
- candidate.inactive = freeflowInactive || !skillsEnabled;
1093
- } else if (candidate.id === "outputRouter.group") {
1094
- candidate.inactive = freeflowInactive;
1095
- } else if (candidate.id === "outputRouter.enabled") {
1096
- candidate.inactive = freeflowInactive;
1097
- } else if (candidate.id.startsWith("outputRouter.")) {
1098
- candidate.inactive = freeflowInactive || !routerEnabled;
909
+ candidate.displaySuffix = coreDisplaySuffix(candidate, inactive);
1099
910
  } else {
1100
- candidate.inactive = freeflowInactive;
911
+ candidate.inactive = candidate.runtimeInactive === true || freeflowInactive;
1101
912
  }
1102
913
  });
1103
914
  }
@@ -1137,6 +948,7 @@ function settingsEntries(items, rootItems, onChange) {
1137
948
  currentChoiceKey: () => String(item.value),
1138
949
  choices: settingsChoices(item),
1139
950
  children: item.children ? () => settingsEntries(item.children, rootItems, onChange) : undefined,
951
+ wizard: item.wizard ? () => item.wizard() : undefined,
1140
952
  edit:
1141
953
  !item.children && !["boolean", "enum", "group"].includes(item.kind)
1142
954
  ? {
@@ -1191,42 +1003,40 @@ async function openSettings(options) {
1191
1003
  await component?.waitForWrites();
1192
1004
  return component?.sessionResult() ?? { changed: false, configChanged: false, failed: false };
1193
1005
  }
1194
- function outputRouterStatusText(rawConfig) {
1195
- const settingsConfig = normalizedSettingsConfig(rawConfig);
1196
- const actual = normalizeFreeflowConfig(rawConfig).config;
1197
- const freeflowEnabled = rawConfig.enabled !== false;
1198
- const router = actual.outputRouter;
1199
- const configured = settingsConfig;
1200
- const generatedPaths = router.hints?.generatedPathGlobs?.length ?? 0;
1201
- const noisyHints = router.hints?.noisyCommandPatterns?.length ?? 0;
1202
- return [
1203
- `Output Router: ${freeflowEnabled && router.enabled ? "enabled" : "disabled"}${freeflowEnabled ? "" : " (inactive: Freeflow off)"}`,
1204
- `script transform: ${freeflowEnabled && configured.scriptTransform.enabled ? "enabled" : "disabled"}${freeflowEnabled && router.enabled ? "" : " (inactive)"}`,
1205
- `observed routing: ${freeflowEnabled && configured.observedRouting.enabled ? "enabled" : "disabled"}${freeflowEnabled && router.enabled ? "" : " (inactive)"}`,
1206
- `native safety net: ${freeflowEnabled ? router.postToolRouting : "off"}`,
1207
- `storage: ${router.storagePolicy}`,
1208
- `generated paths: ${generatedPaths}`,
1209
- `noisy command hints: ${noisyHints}`,
1210
- ].join("; ");
1211
- }
1212
- function freeflowStatusText(state) {
1006
+ function freeflowStatusText(state, cognitiveRoutingController) {
1213
1007
  if (!state.configured) {
1214
1008
  return state.configExists
1215
1009
  ? `Freeflow: inactive (invalid config: ${state.parseError ?? "unknown parse error"}); run /setup-freeflow or fix .freeflow/config.json`
1216
1010
  : "Freeflow: inactive (repo not set up); run /setup-freeflow";
1217
1011
  }
1218
1012
  const sessionSuffix = (source) => (source === "session" ? " (session override)" : "");
1013
+ const routingState = cognitiveRoutingController?.state();
1014
+ const cognitiveRouting = state.cognitiveRouting;
1015
+ const cognitiveRoutingStatus = cognitiveRouting
1016
+ ? cognitiveRouting.blockingReason?.code === "runtime_disabled"
1017
+ ? "unavailable (host unsupported)"
1018
+ : cognitiveRouting.effective
1019
+ ? routingState?.effective
1020
+ ? `active (${routingState.activeProfile ?? "unknown"}, ${routingState.controlMode})`
1021
+ : "effective (inactive)"
1022
+ : cognitiveRouting.enabled
1023
+ ? `blocked (${cognitiveRouting.blockingReason.code})`
1024
+ : "disabled"
1025
+ : undefined;
1026
+ const contextEnabled = state.contextVirtualization?.effective || state.conversationHistory?.effective;
1219
1027
  return [
1220
1028
  `Freeflow: ${state.enabled ? "enabled" : "disabled"}${sessionSuffix(state.configSources.enabled)}`,
1221
- `interaction contract: ${state.interactionContract.effective ? "enabled" : "disabled"}${sessionSuffix(state.configSources.interactionContract)}`,
1222
- `skills: ${state.skills.effective ? "enabled" : "disabled (workflow modes inactive)"}${sessionSuffix(state.configSources.skillsEnabled)}`,
1223
- `output router: ${state.outputRouter.enabled ? "enabled" : "disabled"}`,
1029
+ `context: ${contextEnabled ? "enabled" : "disabled"} (virtualization ${state.contextVirtualization?.effective ? "enabled" : "disabled"}, history ${state.conversationHistory?.effective ? "enabled" : "disabled"})`,
1030
+ ...(cognitiveRoutingStatus ? [`cognitive routing: ${cognitiveRoutingStatus}`] : []),
1224
1031
  ].join("; ");
1225
1032
  }
1226
1033
  const NON_TUI_SETTINGS_GUIDANCE =
1227
- "Freeflow settings require Pi TUI mode. Use /freeflow status to inspect current state; supported non-TUI changes are /freeflow enable, /freeflow disable, and /freeflow mode <conversation|workflow|strict-workflow|reset>.";
1228
- const NON_TUI_MODE_GUIDANCE =
1229
- "Freeflow mode selector requires Pi TUI mode. Use /freeflow mode conversation, /freeflow mode workflow, /freeflow mode strict-workflow, or /freeflow mode reset.";
1034
+ "Freeflow settings require Pi TUI mode. Use /freeflow status to inspect current state; supported non-TUI changes are /freeflow enable and /freeflow disable.";
1035
+ function ensureSettingsIdle(ctx) {
1036
+ if (typeof ctx?.isIdle !== "function" || ctx.isIdle()) return true;
1037
+ ctx.ui?.notify?.("Freeflow settings and profile changes are available only while Pi is idle.", "warning");
1038
+ return false;
1039
+ }
1230
1040
  function nonTuiGuidance(ctx, message) {
1231
1041
  if (ctx?.hasUI === false) throw new Error(message);
1232
1042
  ctx?.ui?.notify?.(message, "warning");
@@ -1238,9 +1048,10 @@ async function finalizeSettingsSession(
1238
1048
  afterChange,
1239
1049
  savedMessage,
1240
1050
  reloadWarning = "Run /reload for Freeflow changes to fully apply.",
1051
+ afterChangeOptions = {},
1241
1052
  ) {
1242
1053
  if (!session.configChanged) return false;
1243
- await afterChange(true);
1054
+ await afterChange(true, afterChangeOptions);
1244
1055
  if (session.failed) return false;
1245
1056
  ctx.ui.notify(savedMessage, "info");
1246
1057
  if (typeof ctx.reload !== "function") {
@@ -1266,55 +1077,29 @@ async function finalizeSessionSettings(session, ctx, afterChange) {
1266
1077
  await ctx.reload();
1267
1078
  return true;
1268
1079
  }
1269
- function actionIsMutation(action) {
1270
- return action === "" || action === "settings" || ["enable", "on", "true", "disable", "off", "false"].includes(action);
1271
- }
1272
- async function maybeBlockLayerMutation(action, ctx, layerName) {
1273
- if (!actionIsMutation(action)) return false;
1274
- const state = await readCapabilityState(ctx.cwd);
1275
- if (!state.configured) {
1276
- ctx.ui.notify(
1277
- `Freeflow is installed but this repo is not set up. Run /setup-freeflow before configuring ${layerName}.`,
1278
- "warning",
1279
- );
1280
- return true;
1281
- }
1282
- if (!state.enabled) {
1283
- ctx.ui.notify(
1284
- `Freeflow is disabled for this repo. Use /freeflow enable before configuring ${layerName}.`,
1285
- "warning",
1286
- );
1287
- return true;
1288
- }
1289
- return false;
1290
- }
1291
- export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
1080
+ export async function handleFreeflowCommand(args, ctx, afterChange, pi, cognitiveRoutingController) {
1292
1081
  const input = (args ?? "settings").trim().toLowerCase() || "settings";
1293
1082
  const [action, ...rest] = input.split(/\s+/);
1294
1083
  const actionValue = rest.join(" ");
1295
1084
  const nonTui = ctx?.mode && ctx.mode !== "tui";
1296
- if (nonTui && action === "mode" && !actionValue) {
1297
- return nonTuiGuidance(ctx, NON_TUI_MODE_GUIDANCE);
1298
- }
1299
1085
  const settingsSelector =
1300
1086
  action === "settings" &&
1301
1087
  (!actionValue || ["session", "local", "personal", "repo", "repository", "shared"].includes(actionValue));
1302
1088
  if (nonTui && settingsSelector) {
1303
1089
  return nonTuiGuidance(ctx, NON_TUI_SETTINGS_GUIDANCE);
1304
1090
  }
1305
- const [layers, state, modeState] = await Promise.all([
1091
+ const [layers, state] = await Promise.all([
1306
1092
  readFreeflowConfigLayers(ctx.cwd),
1307
- readCapabilityState(ctx.cwd),
1308
- readModeState(ctx.cwd),
1093
+ readCapabilityState(ctx.cwd, ctx, pi?.host),
1309
1094
  ]);
1310
1095
  const configState = layers.repository;
1311
1096
  const raw = configState.valid ? configState.parsed : {};
1312
1097
  if (action === "status") {
1313
- ctx.ui.notify(freeflowStatusText(state), "info");
1098
+ ctx.ui.notify(freeflowStatusText(state, cognitiveRoutingController), "info");
1314
1099
  return { changed: false, reloaded: false };
1315
1100
  }
1316
1101
  if (!configState.valid) {
1317
- ctx.ui.notify(freeflowStatusText(state), "warning");
1102
+ ctx.ui.notify(freeflowStatusText(state, cognitiveRoutingController), "warning");
1318
1103
  return { changed: false, reloaded: false, error: "not_configured" };
1319
1104
  }
1320
1105
  if (layers.local.exists && !layers.local.valid) {
@@ -1324,40 +1109,15 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
1324
1109
  );
1325
1110
  return { changed: false, reloaded: false, error: "invalid_local_config" };
1326
1111
  }
1327
- if (action === "mode") {
1328
- if (actionValue) {
1329
- const result = await handleModeCommand(actionValue, ctx, pi);
1330
- return { changed: result.changed, reloaded: false, error: result.error };
1331
- }
1332
- if (!state.enabled || !state.skills.effective) {
1333
- const result = await handleModeCommand(undefined, ctx, pi);
1334
- return { changed: false, reloaded: false, error: result.error };
1335
- }
1336
- if (typeof ctx?.ui?.custom !== "function") {
1337
- const result = await handleModeCommand(undefined, ctx, pi);
1338
- return { changed: false, reloaded: false, error: result.error };
1339
- }
1340
- const item = freeflowItems(raw, modeState, {
1341
- scope: "local",
1342
- layers,
1343
- }).find((candidate) => candidate.id === "freeflow.sessionMode");
1344
- const session = await openSettings({
1345
- title: "Freeflow Mode",
1346
- items: [item],
1347
- initialChoice: item,
1348
- ctx,
1349
- onChange: async (_item, value) => {
1350
- const result = await handleModeCommand(String(value), ctx, pi);
1351
- return { changed: result.changed, reloadRequired: false };
1352
- },
1353
- });
1354
- return { changed: session.changed, reloaded: false, error: session.failed ? "write_failed" : undefined };
1355
- }
1356
1112
  if (["enable", "on", "true", "disable", "off", "false"].includes(action)) {
1113
+ if (!ensureSettingsIdle(ctx)) return { changed: false, reloaded: false, error: "busy" };
1357
1114
  const enabled = ["enable", "on", "true"].includes(action);
1358
- const item = freeflowItems(raw, modeState, {
1115
+ const item = freeflowItems(raw, {
1359
1116
  scope: "repository",
1360
1117
  layers,
1118
+ cognitiveRouting: state.cognitiveRouting,
1119
+ ctx,
1120
+ runtimeAvailable: isCognitiveRoutingRuntimeAvailable(pi),
1361
1121
  }).find((candidate) => candidate.id === "freeflow.enabled");
1362
1122
  await updateConfig(ctx.cwd, item, enabled, "repository");
1363
1123
  await afterChange(true);
@@ -1371,7 +1131,7 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
1371
1131
  }
1372
1132
  if (action && action !== "settings") {
1373
1133
  ctx.ui.notify(
1374
- "Usage: /freeflow, /freeflow settings [local|repo], /freeflow status, /freeflow mode [conversation|workflow|strict-workflow|reset], /freeflow enable, or /freeflow disable",
1134
+ "Usage: /freeflow, /freeflow settings [local|repo], /freeflow status, /freeflow enable, or /freeflow disable",
1375
1135
  "warning",
1376
1136
  );
1377
1137
  return { changed: false, reloaded: false, error: "invalid_action" };
@@ -1388,10 +1148,18 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
1388
1148
  );
1389
1149
  return { changed: false, reloaded: false, error: "invalid_scope" };
1390
1150
  }
1151
+ if (!ensureSettingsIdle(ctx)) return { changed: false, reloaded: false, error: "busy" };
1152
+ let reconcileCognitiveRouting = settingsScope === "session";
1391
1153
  const items =
1392
1154
  settingsScope === "session"
1393
- ? sessionFreeflowItems(state, modeState)
1394
- : freeflowItems(raw, modeState, { scope: settingsScope, layers });
1155
+ ? sessionFreeflowItems(state, cognitiveRoutingController)
1156
+ : freeflowItems(raw, {
1157
+ scope: settingsScope,
1158
+ layers,
1159
+ cognitiveRouting: state.cognitiveRouting,
1160
+ ctx,
1161
+ runtimeAvailable: isCognitiveRoutingRuntimeAvailable(pi),
1162
+ });
1395
1163
  const session = await openSettings({
1396
1164
  title:
1397
1165
  settingsScope === "session"
@@ -1402,9 +1170,29 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
1402
1170
  items,
1403
1171
  ctx,
1404
1172
  onChange: async (item, value) => {
1405
- if (item.id === "freeflow.sessionMode") {
1406
- const result = await handleModeCommand(String(value), ctx, pi);
1407
- return { changed: result.changed, reloadRequired: false };
1173
+ if (!ensureSettingsIdle(ctx)) return { changed: false, reloadRequired: false };
1174
+ if (!isCognitiveRoutingRuntimeAvailable(pi) && item.id.startsWith("freeflow.cognitiveRouting.")) {
1175
+ ctx.ui.notify("Cognitive Routing is unavailable because this host lacks model-state controls.", "warning");
1176
+ return { changed: false, reloadRequired: false };
1177
+ }
1178
+ if (item.id === "freeflow.cognitiveRouting.profile") {
1179
+ if (!cognitiveRoutingController) {
1180
+ ctx.ui.notify("Cognitive Routing is unavailable for this session.", "warning");
1181
+ return { changed: false, reloadRequired: false };
1182
+ }
1183
+ const result =
1184
+ value === "auto"
1185
+ ? await cognitiveRoutingController.setAutomaticControl("profile-settings")
1186
+ : await cognitiveRoutingController.setManualProfile(value, "profile-settings");
1187
+ if ((result.status !== "automatic" && result.status !== "active") || result.reason) {
1188
+ ctx.ui.notify(
1189
+ `Cognitive Routing settings could not be applied: ${result.reason ?? result.status}.`,
1190
+ "warning",
1191
+ );
1192
+ return { changed: false, reloadRequired: false };
1193
+ }
1194
+ await afterChange(false);
1195
+ return { changed: true, reloadRequired: false };
1408
1196
  }
1409
1197
  if (item.id === "freeflow.session.reset") {
1410
1198
  const result = await resetSessionOverrides(ctx, pi);
@@ -1413,8 +1201,8 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
1413
1201
  if (item.configScope === "session") {
1414
1202
  const keyById = {
1415
1203
  "freeflow.enabled": "enabled",
1416
- "freeflow.interactionContract": "interactionContract",
1417
- "freeflow.skills.enabled": "skillsEnabled",
1204
+ "freeflow.contextVirtualization": "contextVirtualization",
1205
+ "freeflow.conversationHistory": "conversationHistory",
1418
1206
  };
1419
1207
  const key = keyById[item.id];
1420
1208
  const override = value === LOCAL_INHERIT ? null : value === "true";
@@ -1422,6 +1210,9 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
1422
1210
  return { changed: result.changed, reloadRequired: result.reloadRequired === true };
1423
1211
  }
1424
1212
  await updateConfig(ctx.cwd, item, value, item.configScope ?? "repository");
1213
+ if (!item.id.startsWith("freeflow.cognitiveRouting.sessionStart.")) {
1214
+ reconcileCognitiveRouting = true;
1215
+ }
1425
1216
  return { changed: true, reloadRequired: true };
1426
1217
  },
1427
1218
  });
@@ -1435,53 +1226,8 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
1435
1226
  ctx,
1436
1227
  afterChange,
1437
1228
  `Freeflow ${savedTarget} saved. Reloading Freeflow runtime...`,
1438
- );
1439
- return { changed: session.changed, reloaded, error: session.failed ? "write_failed" : undefined };
1440
- }
1441
- export async function handleOutputRouterCommand(args, ctx, afterChange) {
1442
- const action = (args ?? "").trim().toLowerCase();
1443
- const raw = await readFreeflowConfig(ctx.cwd);
1444
- if (await maybeBlockLayerMutation(action, ctx, "Output Router")) {
1445
- return { changed: false, reloaded: false, error: "freeflow_inactive" };
1446
- }
1447
- if (action === "status") {
1448
- const state = await readCapabilityState(ctx.cwd);
1449
- const prefix = !state.configured || !state.enabled ? `${freeflowStatusText(state)}; ` : "";
1450
- ctx.ui.notify(`${prefix}${outputRouterStatusText(raw)}`, !state.configured || !state.enabled ? "warning" : "info");
1451
- return { changed: false, reloaded: false };
1452
- }
1453
- if (["enable", "on", "true", "disable", "off", "false"].includes(action)) {
1454
- const enabled = ["enable", "on", "true"].includes(action);
1455
- const item = outputRouterItems(raw).find((candidate) => candidate.id === "outputRouter.enabled");
1456
- await updateConfig(ctx.cwd, item, enabled);
1457
- await afterChange(true);
1458
- ctx.ui.notify(`Output Router ${enabled ? "enabled" : "disabled"}. Reloading Freeflow runtime...`, "info");
1459
- if (typeof ctx.reload === "function") {
1460
- await ctx.reload();
1461
- return { changed: true, reloaded: true };
1462
- }
1463
- ctx.ui.notify("Run /reload for Output Router changes to fully apply.", "warning");
1464
- return { changed: true, reloaded: false };
1465
- }
1466
- if (action && action !== "settings") {
1467
- ctx.ui.notify("Usage: /output-router, /output-router settings, or /output-router status", "warning");
1468
- return { changed: false, reloaded: false, error: "invalid_action" };
1469
- }
1470
- const session = await openSettings({
1471
- title: "Output Router Settings",
1472
- items: outputRouterItems(raw),
1473
- ctx,
1474
- onChange: async (item, value) => {
1475
- await updateConfig(ctx.cwd, item, value);
1476
- return { changed: true, reloadRequired: true };
1477
- },
1478
- });
1479
- const reloaded = await finalizeSettingsSession(
1480
- session,
1481
- ctx,
1482
- afterChange,
1483
- "Output Router settings saved. Reloading Freeflow runtime...",
1484
- "Run /reload for Output Router changes to fully apply.",
1229
+ "Run /reload for Freeflow changes to fully apply.",
1230
+ { reconcileCognitiveRouting },
1485
1231
  );
1486
1232
  return { changed: session.changed, reloaded, error: session.failed ? "write_failed" : undefined };
1487
1233
  }