@hassangameryt/freeflow 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -1
  3. package/.codex-plugin/plugin.json +2 -1
  4. package/README.md +150 -155
  5. package/capabilities/cognitive-routing/SKILL.md +229 -0
  6. package/capabilities/context-virtualization/SKILL.md +103 -0
  7. package/capabilities/conversation-history/SKILL.md +53 -0
  8. package/command-surface.json +17 -13
  9. package/hooks/freeflow-runtime-context.mjs +393 -159
  10. package/hooks/hooks.json +26 -1
  11. package/hooks/tests/check-runtime-context-hook.sh +223 -54
  12. package/package.json +25 -16
  13. package/pi-extension/dist/cognitive-routing/commands.js +96 -0
  14. package/pi-extension/dist/cognitive-routing/config.js +236 -0
  15. package/pi-extension/dist/cognitive-routing/controller.js +862 -0
  16. package/pi-extension/dist/cognitive-routing/history.js +439 -0
  17. package/pi-extension/dist/cognitive-routing/host.js +126 -0
  18. package/pi-extension/dist/cognitive-routing/runtime.js +43 -0
  19. package/pi-extension/dist/cognitive-routing/tool.js +211 -0
  20. package/pi-extension/dist/cognitive-routing/types.js +6 -0
  21. package/pi-extension/dist/context-virtualization/commands.js +89 -0
  22. package/pi-extension/dist/context-virtualization/projector.js +42 -0
  23. package/pi-extension/dist/context-virtualization/runtime.js +356 -0
  24. package/pi-extension/dist/context-virtualization/state.js +57 -0
  25. package/pi-extension/dist/context-virtualization/types.js +10 -0
  26. package/pi-extension/dist/conversation-history/passages.js +66 -0
  27. package/pi-extension/dist/conversation-history/ranking.js +105 -0
  28. package/pi-extension/dist/conversation-history/runtime.js +226 -0
  29. package/pi-extension/dist/conversation-history/search.js +56 -0
  30. package/pi-extension/dist/conversation-history/types.js +1 -0
  31. package/pi-extension/dist/freeflow-context/resolver.js +120 -0
  32. package/pi-extension/dist/freeflow-context/runtime.js +80 -0
  33. package/pi-extension/dist/freeflow-context/source-projector.js +95 -0
  34. package/pi-extension/dist/freeflow-context/tool.js +308 -0
  35. package/pi-extension/dist/freeflow-context/types.js +12 -0
  36. package/pi-extension/dist/freeflow-context/visibility.js +28 -0
  37. package/pi-extension/dist/index.js +516 -98
  38. package/pi-extension/dist/runtime/runtime-context.js +318 -271
  39. package/pi-extension/dist/runtime/runtime-identity.js +5 -0
  40. package/pi-extension/dist/settings/settings-tui.js +78 -0
  41. package/pi-extension/dist/settings/settings-ui.js +593 -563
  42. package/pi-extension/dist/ui/text-component.js +57 -0
  43. package/pi-extension/src/cognitive-routing/commands.ts +119 -0
  44. package/pi-extension/src/cognitive-routing/config.ts +307 -0
  45. package/pi-extension/src/cognitive-routing/controller.ts +1118 -0
  46. package/pi-extension/src/cognitive-routing/history.ts +638 -0
  47. package/pi-extension/src/cognitive-routing/host.ts +172 -0
  48. package/pi-extension/src/cognitive-routing/runtime.ts +57 -0
  49. package/pi-extension/src/cognitive-routing/tool.ts +272 -0
  50. package/pi-extension/src/cognitive-routing/types.ts +89 -0
  51. package/pi-extension/src/context-virtualization/commands.ts +111 -0
  52. package/pi-extension/src/context-virtualization/projector.ts +62 -0
  53. package/pi-extension/src/context-virtualization/runtime.ts +406 -0
  54. package/pi-extension/src/context-virtualization/state.ts +79 -0
  55. package/pi-extension/src/context-virtualization/types.ts +92 -0
  56. package/pi-extension/src/conversation-history/passages.ts +81 -0
  57. package/pi-extension/src/conversation-history/ranking.ts +155 -0
  58. package/pi-extension/src/conversation-history/runtime.ts +250 -0
  59. package/pi-extension/src/conversation-history/search.ts +79 -0
  60. package/pi-extension/src/conversation-history/types.ts +31 -0
  61. package/pi-extension/src/freeflow-context/resolver.ts +139 -0
  62. package/pi-extension/src/freeflow-context/runtime.ts +100 -0
  63. package/pi-extension/src/freeflow-context/source-projector.ts +106 -0
  64. package/pi-extension/src/freeflow-context/tool.ts +360 -0
  65. package/pi-extension/src/freeflow-context/types.ts +70 -0
  66. package/pi-extension/src/freeflow-context/visibility.ts +53 -0
  67. package/pi-extension/src/index.ts +553 -101
  68. package/pi-extension/src/runtime/runtime-context.ts +339 -281
  69. package/pi-extension/src/runtime/runtime-identity.ts +8 -0
  70. package/pi-extension/src/settings/settings-tui.ts +93 -0
  71. package/pi-extension/src/settings/settings-ui.ts +700 -613
  72. package/pi-extension/src/ui/text-component.ts +66 -0
  73. package/pi-extension/tests/cognitive-routing/commands.test.js +134 -0
  74. package/pi-extension/tests/cognitive-routing/controller.test.js +1099 -0
  75. package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +1071 -0
  76. package/pi-extension/tests/cognitive-routing/history.test.js +274 -0
  77. package/pi-extension/tests/cognitive-routing/host-fixture.js +4 -0
  78. package/pi-extension/tests/cognitive-routing/host-identity.test.js +63 -0
  79. package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +236 -0
  80. package/pi-extension/tests/cognitive-routing/tool.test.js +230 -0
  81. package/pi-extension/tests/context-virtualization/extension.test.js +749 -0
  82. package/pi-extension/tests/context-virtualization/projector.test.js +110 -0
  83. package/pi-extension/tests/context-virtualization/runtime.test.js +216 -0
  84. package/pi-extension/tests/conversation-history/runtime.test.js +98 -0
  85. package/pi-extension/tests/conversation-history/search.test.js +97 -0
  86. package/pi-extension/tests/integration/pi-extension.test.js +928 -2047
  87. package/pi-extension/tests/integration/prompt-architecture.test.js +305 -0
  88. package/runtime/prompts/cognitive-routing.md +9 -0
  89. package/runtime/prompts/context-virtualization.md +3 -0
  90. package/runtime/prompts/conversation-history.md +3 -0
  91. package/runtime/prompts/core.md +13 -0
  92. package/runtime/prompts/interaction-contract.md +7 -0
  93. package/runtime/prompts/skills.md +65 -0
  94. package/skills/action-selection/SKILL.md +91 -0
  95. package/skills/bypass/SKILL.md +3 -2
  96. package/skills/commit-work/SKILL.md +1 -1
  97. package/skills/commit-work/references/staging-decisions.md +1 -1
  98. package/skills/decision-gate/SKILL.md +1 -1
  99. package/skills/design-for-depth/SKILL.md +14 -33
  100. package/skills/design-for-depth/references/design-pressure-signals.md +1 -1
  101. package/skills/design-for-depth/references/interface-design-loop.md +1 -1
  102. package/skills/design-for-depth/references/software-design-philosophy.md +1 -1
  103. package/skills/diagnose-failure/SKILL.md +20 -14
  104. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +1 -3
  105. package/skills/diagnose-failure/references/{flaky-and-performance.md → flaky-and-performance-diagnosis.md} +1 -1
  106. package/skills/discuss/SKILL.md +34 -22
  107. package/skills/discuss/references/discussion-continuity.md +71 -0
  108. package/skills/evaluate-skill/SKILL.md +5 -5
  109. package/skills/evaluate-skill/references/{definition-schema.md → evaluation-definition-schema.md} +42 -2
  110. package/skills/evaluate-skill/references/execution-and-evidence.md +8 -3
  111. package/skills/evaluate-skill/scripts/lib/definitions.mjs +114 -3
  112. package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +156 -1
  113. package/skills/evaluate-skill/scripts/lib/grade.mjs +9 -1
  114. package/skills/evaluate-skill/scripts/lib/pi.mjs +147 -32
  115. package/skills/evaluate-skill/scripts/lib/rpc.mjs +4 -4
  116. package/skills/evaluate-skill/scripts/lib/runner.mjs +36 -14
  117. package/skills/evaluate-skill/scripts/lib/sandbox.mjs +92 -0
  118. package/skills/evaluate-skill/scripts/pi-guard.mjs +1 -1
  119. package/skills/evaluate-skill/scripts/pi-observer.mjs +66 -0
  120. package/skills/evaluate-skill/scripts/skill-eval.mjs +6 -5
  121. package/skills/execute-work/SKILL.md +18 -15
  122. package/skills/execute-work/references/code-practices.md +1 -1
  123. package/skills/execute-work/references/execute-work-edges.md +42 -0
  124. package/skills/finish-branch/SKILL.md +2 -2
  125. package/skills/finish-branch/references/integration-options.md +1 -1
  126. package/skills/handoff/SKILL.md +3 -3
  127. package/skills/launch-work/SKILL.md +1 -1
  128. package/skills/launch-work/references/launch-readiness.md +2 -2
  129. package/skills/migration-work/SKILL.md +2 -2
  130. package/skills/migration-work/references/migration-lifecycle.md +1 -1
  131. package/skills/mode-contract/SKILL.md +32 -10
  132. package/skills/release-work/SKILL.md +1 -1
  133. package/skills/release-work/references/release-evidence.md +1 -1
  134. package/skills/review-artifact/SKILL.md +28 -26
  135. package/skills/review-work/SKILL.md +28 -26
  136. package/skills/review-work/references/{reviewer-prompt.md → independent-work-reviewer-contract.md} +1 -1
  137. package/skills/review-work/references/security-risk-lens.md +1 -1
  138. package/skills/setup-freeflow/SKILL.md +6 -10
  139. package/skills/setup-freeflow/references/activation-contract.md +5 -10
  140. package/skills/setup-freeflow/references/host-setup.md +3 -7
  141. package/skills/simplify-code/SKILL.md +1 -1
  142. package/skills/simplify-code/references/simplification-patterns.md +1 -1
  143. package/skills/tdd/SKILL.md +18 -38
  144. package/skills/tdd/references/test-design.md +2 -2
  145. package/skills/track-work/SKILL.md +362 -58
  146. package/skills/track-work/scripts/lib/codec.mjs +570 -0
  147. package/skills/track-work/scripts/lib/commands.mjs +568 -0
  148. package/skills/track-work/scripts/lib/contract.mjs +303 -0
  149. package/skills/track-work/scripts/lib/model.mjs +506 -0
  150. package/skills/track-work/scripts/lib/operations.mjs +965 -0
  151. package/skills/track-work/scripts/lib/record-store.mjs +264 -0
  152. package/skills/track-work/scripts/lib/result.mjs +37 -0
  153. package/skills/track-work/scripts/lib/rewrites.mjs +740 -0
  154. package/skills/track-work/scripts/lib/schema.mjs +277 -0
  155. package/skills/track-work/scripts/lib/source-inventory.mjs +162 -0
  156. package/skills/track-work/scripts/lib/views.mjs +443 -0
  157. package/skills/track-work/scripts/lib/workspace.mjs +134 -0
  158. package/skills/track-work/scripts/working-record.mjs +122 -0
  159. package/skills/verify-work/SKILL.md +14 -2
  160. package/skills/verify-work/references/browser-runtime-evidence.md +1 -1
  161. package/skills/verify-work/references/integration-evidence.md +1 -1
  162. package/skills/verify-work/references/performance-evidence.md +1 -1
  163. package/skills/workflow/SKILL.md +105 -53
  164. package/skills/workflow/references/domain-skill-composition.md +22 -42
  165. package/skills/write-plan/SKILL.md +1 -1
  166. package/skills/write-skill/SKILL.md +3 -2
  167. package/skills/write-skill/references/progressive-disclosure.md +17 -3
  168. package/pi-extension/dist/output-router/host-producer-identification.js +0 -232
  169. package/pi-extension/dist/output-router/native-safety-net.js +0 -216
  170. package/pi-extension/dist/output-router/observed-tool-routing.js +0 -122
  171. package/pi-extension/dist/output-router/renderers.js +0 -1023
  172. package/pi-extension/dist/output-router/router-tools.js +0 -554
  173. package/pi-extension/dist/output-router/schemas.js +0 -383
  174. package/pi-extension/dist/output-router/status.js +0 -744
  175. package/pi-extension/dist/output-router/utils.js +0 -502
  176. package/pi-extension/src/output-router/host-producer-identification.ts +0 -256
  177. package/pi-extension/src/output-router/native-safety-net.ts +0 -248
  178. package/pi-extension/src/output-router/observed-tool-routing.ts +0 -134
  179. package/pi-extension/src/output-router/renderers.ts +0 -1108
  180. package/pi-extension/src/output-router/router-tools.ts +0 -579
  181. package/pi-extension/src/output-router/schemas.ts +0 -387
  182. package/pi-extension/src/output-router/status.ts +0 -791
  183. package/pi-extension/src/output-router/utils.ts +0 -554
  184. package/pi-extension/tests/output-router/observed-producers.test.js +0 -132
  185. package/pi-extension/tests/output-router/search.test.js +0 -722
  186. package/router/dist/benchmarks/benchmark-harness.d.ts +0 -35
  187. package/router/dist/benchmarks/benchmark-harness.js +0 -92
  188. package/router/dist/benchmarks/benchmarks.d.ts +0 -109
  189. package/router/dist/benchmarks/benchmarks.js +0 -979
  190. package/router/dist/benchmarks/codex-qa-benchmarks.d.ts +0 -95
  191. package/router/dist/benchmarks/codex-qa-benchmarks.js +0 -500
  192. package/router/dist/benchmarks/command-benchmarks.d.ts +0 -143
  193. package/router/dist/benchmarks/command-benchmarks.js +0 -698
  194. package/router/dist/benchmarks/context-mode-normalized-benchmarks.d.ts +0 -82
  195. package/router/dist/benchmarks/context-mode-normalized-benchmarks.js +0 -773
  196. package/router/dist/benchmarks/context-mode-real-deep-benchmark.d.ts +0 -132
  197. package/router/dist/benchmarks/context-mode-real-deep-benchmark.js +0 -1380
  198. package/router/dist/benchmarks/context-saving-baseline.d.ts +0 -60
  199. package/router/dist/benchmarks/context-saving-baseline.js +0 -470
  200. package/router/dist/benchmarks/index-benchmarks.d.ts +0 -103
  201. package/router/dist/benchmarks/index-benchmarks.js +0 -862
  202. package/router/dist/benchmarks/storage-policy-benchmarks.d.ts +0 -101
  203. package/router/dist/benchmarks/storage-policy-benchmarks.js +0 -618
  204. package/router/dist/benchmarks/vault-index-benchmarks.d.ts +0 -56
  205. package/router/dist/benchmarks/vault-index-benchmarks.js +0 -322
  206. package/router/dist/config/config.d.ts +0 -103
  207. package/router/dist/config/config.js +0 -632
  208. package/router/dist/config/router-contract.d.ts +0 -10
  209. package/router/dist/config/router-contract.js +0 -54
  210. package/router/dist/config/schema.d.ts +0 -27
  211. package/router/dist/config/schema.js +0 -648
  212. package/router/dist/config/types.d.ts +0 -573
  213. package/router/dist/config/types.js +0 -41
  214. package/router/dist/evidence/bounded-evidence.d.ts +0 -40
  215. package/router/dist/evidence/bounded-evidence.js +0 -323
  216. package/router/dist/evidence/evidence-range-selector.d.ts +0 -24
  217. package/router/dist/evidence/evidence-range-selector.js +0 -232
  218. package/router/dist/evidence/evidence-search.d.ts +0 -27
  219. package/router/dist/evidence/evidence-search.js +0 -490
  220. package/router/dist/evidence/evidence.d.ts +0 -38
  221. package/router/dist/evidence/evidence.js +0 -200
  222. package/router/dist/evidence/failure-contracts.d.ts +0 -37
  223. package/router/dist/evidence/failure-contracts.js +0 -166
  224. package/router/dist/evidence/line-ranges.d.ts +0 -24
  225. package/router/dist/evidence/line-ranges.js +0 -22
  226. package/router/dist/experiments/local-index.d.ts +0 -64
  227. package/router/dist/experiments/local-index.js +0 -581
  228. package/router/dist/index.d.ts +0 -30
  229. package/router/dist/index.js +0 -32
  230. package/router/dist/local/local-traversal.d.ts +0 -18
  231. package/router/dist/local/local-traversal.js +0 -334
  232. package/router/dist/processing/engine.d.ts +0 -132
  233. package/router/dist/processing/engine.js +0 -533
  234. package/router/dist/processing/reducers.d.ts +0 -280
  235. package/router/dist/processing/reducers.js +0 -1673
  236. package/router/dist/processing/renderers.d.ts +0 -31
  237. package/router/dist/processing/renderers.js +0 -195
  238. package/router/dist/processing/scripts.d.ts +0 -91
  239. package/router/dist/processing/scripts.js +0 -386
  240. package/router/dist/repo/repo-traversal.d.ts +0 -18
  241. package/router/dist/repo/repo-traversal.js +0 -245
  242. package/router/dist/routing/observed-reducers.d.ts +0 -41
  243. package/router/dist/routing/observed-reducers.js +0 -661
  244. package/router/dist/routing/observed-routing.d.ts +0 -26
  245. package/router/dist/routing/observed-routing.js +0 -378
  246. package/router/dist/routing/parsers.d.ts +0 -16
  247. package/router/dist/routing/parsers.js +0 -521
  248. package/router/dist/routing/run-filters.d.ts +0 -60
  249. package/router/dist/routing/run-filters.js +0 -238
  250. package/router/dist/routing/run-reducers.d.ts +0 -49
  251. package/router/dist/routing/run-reducers.js +0 -122
  252. package/router/dist/sandbox/adapter-roots.d.ts +0 -46
  253. package/router/dist/sandbox/adapter-roots.js +0 -66
  254. package/router/dist/sandbox/eryx-python-adapter.d.ts +0 -13
  255. package/router/dist/sandbox/eryx-python-adapter.js +0 -868
  256. package/router/dist/sandbox/jq-wasm-adapter.d.ts +0 -9
  257. package/router/dist/sandbox/jq-wasm-adapter.js +0 -429
  258. package/router/dist/sandbox/quickjs-wasi-adapter.d.ts +0 -13
  259. package/router/dist/sandbox/quickjs-wasi-adapter.js +0 -384
  260. package/router/dist/sandbox/script-sandbox.d.ts +0 -134
  261. package/router/dist/sandbox/script-sandbox.js +0 -337
  262. package/router/dist/setup/script-transform-adapters.d.ts +0 -32
  263. package/router/dist/setup/script-transform-adapters.js +0 -278
  264. package/router/dist/tools/batch.d.ts +0 -33
  265. package/router/dist/tools/batch.js +0 -586
  266. package/router/dist/tools/run.d.ts +0 -62
  267. package/router/dist/tools/run.js +0 -1304
  268. package/router/dist/tools/search.d.ts +0 -66
  269. package/router/dist/tools/search.js +0 -1364
  270. package/router/dist/transform/engine.d.ts +0 -168
  271. package/router/dist/transform/engine.js +0 -2072
  272. package/router/dist/vault/vault-index.d.ts +0 -125
  273. package/router/dist/vault/vault-index.js +0 -416
  274. package/router/dist/vault/vault.d.ts +0 -138
  275. package/router/dist/vault/vault.js +0 -630
  276. package/runtime/interaction-contract.md +0 -16
  277. package/skills/discuss/references/checkpoints.md +0 -66
  278. package/skills/execute-work/references/execution-loop.md +0 -77
  279. package/skills/output-router/SKILL.md +0 -206
  280. package/skills/output-router/references/safety-policy.md +0 -40
  281. package/skills/setup-freeflow/references/output-router-setup.md +0 -110
  282. package/skills/track-work/references/working-record-schema.md +0 -378
  283. package/skills/workflow/references/workflow-loop.md +0 -92
  284. /package/skills/handoff/references/{templates.md → handoff-templates.md} +0 -0
  285. /package/skills/review-artifact/references/{reviewer-prompt.md → independent-artifact-reviewer-contract.md} +0 -0
@@ -3,17 +3,6 @@ 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
 
6
- import {
7
- DEFAULT_OBSERVED_ROUTING_CONFIG,
8
- DEFAULT_OUTPUT_ROUTER_ENABLED,
9
- DEFAULT_POST_TOOL_ROUTING,
10
- DEFAULT_ROUTER_THRESHOLDS,
11
- DEFAULT_SCRIPT_TRANSFORM_CONFIG,
12
- DEFAULT_STORAGE_POLICY,
13
- DEFAULT_VAULT_RETENTION,
14
- DEFAULT_VAULT_ROOT,
15
- normalizeFreeflowConfig,
16
- } from "../../../router/dist/index.js";
17
6
  import {
18
7
  handleModeCommand,
19
8
  readCapabilityState,
@@ -30,15 +19,22 @@ import {
30
19
  type SettingsCommitResult,
31
20
  type SettingsEntry,
32
21
  type SettingsSessionResult,
22
+ type SettingsWizard,
23
+ type SettingsWizardStep,
33
24
  } from "./settings-tui.js";
25
+ import { isPiFlowHost } from "../runtime/runtime-identity.js";
26
+ import type {
27
+ CognitiveRoutingCapabilityState,
28
+ CognitiveRoutingProfile,
29
+ CognitiveRoutingProfileName,
30
+ CognitiveRoutingThinkingLevel,
31
+ } from "../cognitive-routing/types.js";
34
32
 
35
- const POST_TOOL_ROUTING_VALUES = ["off", "safety-net", "strict"] as const;
36
- const STORAGE_POLICY_VALUES = ["hybrid-dedupe", "store-everything"] as const;
37
- const OBSERVED_PERSISTENCE_VALUES = ["none", "metadata-only", "exact"] as const;
38
- const SCRIPT_LANGUAGES = ["javascript", "python", "jq"] as const;
39
33
  const DEFAULT_FREEFLOW_ENABLED = true;
40
34
  const DEFAULT_INTERACTION_CONTRACT_ENABLED = true;
41
35
  const DEFAULT_SKILLS_ENABLED = true;
36
+ const DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED = false;
37
+ const DEFAULT_CONVERSATION_HISTORY_ENABLED = false;
42
38
  const MODE_VALUES = ["conversation", "workflow", "strict-workflow"];
43
39
  const MODE_LABELS = {
44
40
  default: "Use configured default",
@@ -58,6 +54,7 @@ type ConfigScope = "session" | "local" | "repository";
58
54
  type ConfigSource = "session" | "local" | "repository" | "builtin";
59
55
 
60
56
  type SettingKind = "boolean" | "enum" | "integer" | "string" | "list" | "json" | "group";
57
+ type SettingsValue = boolean | number | string | null | SettingsValue[] | { [key: string]: SettingsValue };
61
58
 
62
59
  type SettingsItem = {
63
60
  id: string;
@@ -71,8 +68,10 @@ type SettingsItem = {
71
68
  valueLabels?: Record<string, string>;
72
69
  valueDescriptions?: Record<string, string>;
73
70
  inactive?: boolean;
71
+ runtimeInactive?: boolean;
74
72
  displaySuffix?: string;
75
73
  children?: SettingsItem[];
74
+ wizard?: () => SettingsWizard;
76
75
  parse?: (text: string) => unknown;
77
76
  format?: (value: unknown) => string;
78
77
  configScope?: ConfigScope;
@@ -85,6 +84,18 @@ type SettingsItem = {
85
84
  transient?: boolean;
86
85
  };
87
86
 
87
+ type CognitiveRoutingSettingsController = {
88
+ state(): { effective: boolean; controlMode: string; activeProfile?: string };
89
+ setManualProfile(profile: "standard" | "reasoning", mechanism?: string): Promise<{ status: string; reason?: string }>;
90
+ setAutomaticControl(mechanism?: string): Promise<{ status: string; reason?: string }>;
91
+ };
92
+
93
+ type AfterChangeOptions = {
94
+ reconcileCognitiveRouting?: boolean;
95
+ };
96
+
97
+ type AfterChange = (changed: boolean, options?: AfterChangeOptions) => Promise<void> | void;
98
+
88
99
  type OpenSettingsOptions = {
89
100
  title: string;
90
101
  items: SettingsItem[];
@@ -106,13 +117,13 @@ function cloneJson<T>(value: T): T {
106
117
  }
107
118
  }
108
119
 
109
- function getPath(source: unknown, path: string[]): unknown {
110
- let current = source;
120
+ function getPath(source: unknown, path: string[]): SettingsValue | undefined {
121
+ let current: unknown = source;
111
122
  for (const key of path) {
112
123
  if (!isRecord(current)) return undefined;
113
- current = current[key];
124
+ current = current[key] as SettingsValue | undefined;
114
125
  }
115
- return current;
126
+ return current as SettingsValue | undefined;
116
127
  }
117
128
 
118
129
  function setPath(target: Record<string, unknown>, path: string[], value: unknown) {
@@ -170,318 +181,29 @@ function setConfigValue(config: Record<string, unknown>, item: SettingsItem, val
170
181
  setPath(config, item.path, value);
171
182
  }
172
183
 
173
- function parseStringList(text: string): string[] {
174
- return text
175
- .split(",")
176
- .map((item) => item.trim())
177
- .filter(Boolean);
178
- }
179
-
180
- function parsePositiveInteger(text: string): number {
181
- const value = Number(text.trim());
182
- if (!Number.isInteger(value) || value <= 0) {
183
- throw new Error("Expected a positive integer.");
184
- }
185
- return value;
186
- }
187
-
188
- function parseJsonObject(text: string): unknown {
189
- const trimmed = text.trim();
190
- if (!trimmed) return {};
191
- let parsed: unknown;
192
- try {
193
- parsed = JSON.parse(trimmed);
194
- } catch (error) {
195
- throw new Error(`Expected valid JSON: ${error instanceof Error ? error.message : String(error)}`);
196
- }
197
- if (!isRecord(parsed)) {
198
- throw new Error("Expected a JSON object.");
199
- }
200
- return parsed;
201
- }
202
-
203
- function formatList(value: unknown): string {
204
- return Array.isArray(value) ? value.join(", ") : "";
205
- }
206
-
207
- function formatJson(value: unknown): string {
208
- return JSON.stringify(isRecord(value) ? value : {});
209
- }
210
-
211
184
  function booleanValue(value: unknown): string {
212
185
  return value === true ? "enabled" : "disabled";
213
186
  }
214
187
 
215
- function normalizedSettingsConfig(rawConfig: Record<string, unknown>) {
216
- const rawRouter = isRecord(rawConfig.outputRouter) ? rawConfig.outputRouter : {};
217
- const normalized = normalizeFreeflowConfig({
218
- ...rawConfig,
219
- outputRouter: { ...rawRouter, enabled: true },
220
- });
221
- return normalized.config;
222
- }
223
-
224
- function rawValue(rawConfig: Record<string, unknown>, path: string[], fallback: unknown): unknown {
225
- const value = getPath(rawConfig, path);
226
- return value === undefined ? fallback : value;
227
- }
228
-
229
- function outputRouterItems(rawConfig: Record<string, unknown>, freeflowInactive = false): SettingsItem[] {
230
- const effective = normalizedSettingsConfig(rawConfig);
231
- const routerEnabled = normalizeFreeflowConfig(rawConfig).config.outputRouter.enabled;
232
- const router = effective.outputRouter;
233
- const scriptTransform = effective.scriptTransform;
234
- const observedRouting = effective.observedRouting;
235
- const inactive = freeflowInactive || !routerEnabled;
236
-
237
- return [
238
- {
239
- id: "outputRouter.enabled",
240
- label: "Output Router",
241
- description: "Master switch for Freeflow routed evidence tools and router runtime guidance.",
242
- path: ["outputRouter", "enabled"],
243
- kind: "boolean",
244
- value: routerEnabled,
245
- defaultValue: DEFAULT_OUTPUT_ROUTER_ENABLED,
246
- inactive: freeflowInactive,
247
- },
248
- {
249
- id: "outputRouter.postToolRouting",
250
- label: "Native safety net",
251
- description:
252
- "Post-process large/noisy native read/bash output. safety-net can replace oversized native output with vaulted routed evidence; strict is stronger.",
253
- path: ["outputRouter", "postToolRouting"],
254
- kind: "enum",
255
- value: router.postToolRouting,
256
- values: [...POST_TOOL_ROUTING_VALUES],
257
- valueDescriptions: {
258
- off: "Leave native tool output unchanged",
259
- "safety-net": "Route oversized or noisy native output",
260
- strict: "Apply stronger native output routing",
261
- },
262
- defaultValue: DEFAULT_POST_TOOL_ROUTING,
263
- inactive,
264
- },
265
- {
266
- id: "outputRouter.storagePolicy",
267
- label: "Storage policy",
268
- description: "Vault storage behavior for exactness-sensitive and noisy output.",
269
- path: ["outputRouter", "storagePolicy"],
270
- kind: "enum",
271
- value: router.storagePolicy,
272
- values: [...STORAGE_POLICY_VALUES],
273
- defaultValue: DEFAULT_STORAGE_POLICY,
274
- inactive,
275
- },
276
- {
277
- id: "outputRouter.thresholds.largeOutputBytes",
278
- label: "Large output bytes",
279
- description: "Byte threshold for treating command/tool output as large.",
280
- path: ["outputRouter", "thresholds", "largeOutputBytes"],
281
- kind: "integer",
282
- value: router.thresholds.largeOutputBytes,
283
- defaultValue: DEFAULT_ROUTER_THRESHOLDS.largeOutputBytes,
284
- parse: parsePositiveInteger,
285
- inactive,
286
- },
287
- {
288
- id: "outputRouter.thresholds.largeOutputLines",
289
- label: "Large output lines",
290
- description: "Line threshold for treating command/tool output as large.",
291
- path: ["outputRouter", "thresholds", "largeOutputLines"],
292
- kind: "integer",
293
- value: router.thresholds.largeOutputLines,
294
- defaultValue: DEFAULT_ROUTER_THRESHOLDS.largeOutputLines,
295
- parse: parsePositiveInteger,
296
- inactive,
297
- },
298
- {
299
- id: "outputRouter.vault.root",
300
- label: "Vault root",
301
- description: "Root directory for recoverable routed output.",
302
- path: ["outputRouter", "vault", "root"],
303
- kind: "string",
304
- value: router.vault.root,
305
- defaultValue: DEFAULT_VAULT_ROOT,
306
- inactive,
307
- },
308
- {
309
- id: "outputRouter.vault.retention.ttlDays",
310
- label: "Vault retention days",
311
- description: "TTL retention for vaulted output. Set a positive integer; default is 7 days.",
312
- path: ["outputRouter", "vault", "retention", "ttlDays"],
313
- kind: "integer",
314
- value:
315
- router.vault.retention?.strategy === "ttl" ? router.vault.retention.ttlDays : DEFAULT_VAULT_RETENTION.ttlDays,
316
- defaultValue: DEFAULT_VAULT_RETENTION.ttlDays,
317
- parse: parsePositiveInteger,
318
- inactive,
319
- },
320
- {
321
- id: "outputRouter.generatedPaths",
322
- label: "Generated paths",
323
- description:
324
- "Comma-separated globs for generated repo paths that should be treated as noisy or lower-priority evidence.",
325
- path: ["outputRouter", "hints", "generatedPathGlobs"],
326
- kind: "list",
327
- value: rawValue(
328
- rawConfig,
329
- ["outputRouter", "hints", "generatedPathGlobs"],
330
- rawValue(rawConfig, ["outputRouter", "generatedPaths"], router.hints?.generatedPathGlobs ?? []),
331
- ),
332
- format: formatList,
333
- parse: parseStringList,
334
- inactive,
335
- },
336
- {
337
- id: "outputRouter.noisyCommandHints",
338
- label: "Noisy command hints",
339
- description: "Comma-separated command patterns that are likely to produce noisy output.",
340
- path: ["outputRouter", "hints", "noisyCommandPatterns"],
341
- kind: "list",
342
- value: rawValue(
343
- rawConfig,
344
- ["outputRouter", "hints", "noisyCommandPatterns"],
345
- rawValue(rawConfig, ["outputRouter", "noisyCommandHints"], router.hints?.noisyCommandPatterns ?? []),
346
- ),
347
- format: formatList,
348
- parse: parseStringList,
349
- inactive,
350
- },
351
- {
352
- id: "outputRouter.scriptTransform.enabled",
353
- label: "Script transform",
354
- description:
355
- "Enable sandboxed script transforms/producers. Requires proof-backed adapters; no unsandboxed fallback.",
356
- path: ["outputRouter", "scriptTransform", "enabled"],
357
- kind: "boolean",
358
- value: scriptTransform.enabled,
359
- defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.enabled,
360
- inactive,
361
- },
362
- {
363
- id: "outputRouter.scriptTransform.languages",
364
- label: "Script languages",
365
- description: "Comma-separated sandbox languages to allow: javascript, python, jq.",
366
- path: ["outputRouter", "scriptTransform", "languages"],
367
- kind: "list",
368
- value: scriptTransform.languages,
369
- defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.languages,
370
- format: formatList,
371
- parse: (text) => parseStringList(text).filter((value) => (SCRIPT_LANGUAGES as readonly string[]).includes(value)),
372
- inactive,
373
- },
374
- {
375
- id: "outputRouter.scriptTransform.limits.timeoutMs",
376
- label: "Script timeout ms",
377
- description: "Maximum sandboxed script execution time.",
378
- path: ["outputRouter", "scriptTransform", "limits", "timeoutMs"],
379
- kind: "integer",
380
- value: scriptTransform.limits.timeoutMs,
381
- defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.timeoutMs,
382
- parse: parsePositiveInteger,
383
- inactive,
384
- },
385
- {
386
- id: "outputRouter.scriptTransform.limits.maxInputBytes",
387
- label: "Script max input bytes",
388
- description: "Maximum input size for sandboxed scripts.",
389
- path: ["outputRouter", "scriptTransform", "limits", "maxInputBytes"],
390
- kind: "integer",
391
- value: scriptTransform.limits.maxInputBytes,
392
- defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.maxInputBytes,
393
- parse: parsePositiveInteger,
394
- inactive,
395
- },
396
- {
397
- id: "outputRouter.scriptTransform.limits.maxOutputBytes",
398
- label: "Script max output bytes",
399
- description: "Maximum output size for sandboxed scripts.",
400
- path: ["outputRouter", "scriptTransform", "limits", "maxOutputBytes"],
401
- kind: "integer",
402
- value: scriptTransform.limits.maxOutputBytes,
403
- defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.maxOutputBytes,
404
- parse: parsePositiveInteger,
405
- inactive,
406
- },
407
- {
408
- id: "outputRouter.observedRouting.enabled",
409
- label: "Observed routing",
410
- description: "Route configured host tool results after they run, e.g. MCP/web/fetch/code-search output.",
411
- path: ["outputRouter", "observedRouting", "enabled"],
412
- kind: "boolean",
413
- value: observedRouting.enabled,
414
- defaultValue: DEFAULT_OBSERVED_ROUTING_CONFIG.enabled,
415
- inactive,
416
- },
417
- ...observedProducerItems("web", "Web search", observedRouting.web, inactive),
418
- ...observedProducerItems("fetch", "Fetch content", observedRouting.fetch, inactive),
419
- ...observedProducerItems("codeSearch", "Code search", observedRouting.codeSearch, inactive),
420
- {
421
- id: "outputRouter.observedRouting.mcp.servers",
422
- label: "MCP servers JSON",
423
- description: 'JSON object keyed by MCP server id. Example: {"github":{"enabled":true,"persistence":"exact"}}',
424
- path: ["outputRouter", "observedRouting", "mcp", "servers"],
425
- kind: "json",
426
- value: observedRouting.mcp?.servers ?? {},
427
- defaultValue: DEFAULT_OBSERVED_ROUTING_CONFIG.mcp.servers,
428
- format: formatJson,
429
- parse: parseJsonObject,
430
- inactive,
431
- },
432
- ];
433
- }
434
-
435
- function observedProducerItems(
436
- id: "web" | "fetch" | "codeSearch",
437
- label: string,
438
- value: any,
439
- inactive: boolean,
440
- ): SettingsItem[] {
441
- return [
442
- {
443
- id: `outputRouter.observedRouting.${id}.enabled`,
444
- label: `${label} observed`,
445
- description: `Enable observed routing for ${label.toLowerCase()} output.`,
446
- path: ["outputRouter", "observedRouting", id, "enabled"],
447
- kind: "boolean",
448
- value: value?.enabled === true,
449
- defaultValue: false,
450
- inactive,
451
- },
452
- {
453
- id: `outputRouter.observedRouting.${id}.persistence`,
454
- label: `${label} persistence`,
455
- description: "Persistence mode for this observed producer: none, metadata-only, or exact.",
456
- path: ["outputRouter", "observedRouting", id, "persistence"],
457
- kind: "enum",
458
- value: value?.persistence ?? "none",
459
- values: [...OBSERVED_PERSISTENCE_VALUES],
460
- valueDescriptions: {
461
- none: "Do not persist observed output",
462
- "metadata-only": "Persist metadata without raw output",
463
- exact: "Persist exact recoverable output",
464
- },
465
- defaultValue: value?.enabled === true ? undefined : "none",
466
- inactive,
467
- },
468
- ];
469
- }
470
-
471
- function configValueForChoice(item: SettingsItem, value: unknown): unknown {
188
+ function configValueForChoice(item: SettingsItem, value: unknown): SettingsValue | undefined {
472
189
  const key = String(value);
473
190
  if (item.configValues && Object.hasOwn(item.configValues, key)) {
474
- return item.configValues[key];
191
+ return item.configValues[key] as SettingsValue | undefined;
475
192
  }
476
- return value;
193
+ return value as SettingsValue | undefined;
477
194
  }
478
195
 
479
- function effectiveItemValue(item: SettingsItem): unknown {
480
- return item.effectiveValue !== undefined ? item.effectiveValue : item.value;
196
+ function effectiveItemValue(item: SettingsItem): SettingsValue | undefined {
197
+ return (item.effectiveValue === undefined ? item.value : item.effectiveValue) as SettingsValue | undefined;
481
198
  }
482
199
 
483
200
  function formatCoreValue(value: unknown): string {
484
- return typeof value === "boolean" ? booleanValue(value) : String(value ?? "");
201
+ if (typeof value === "boolean") return booleanValue(value);
202
+ if (isRecord(value) && typeof value.provider === "string" && typeof value.model === "string") {
203
+ const effort = typeof value.thinkingLevel === "string" ? ` · ${value.thinkingLevel}` : "";
204
+ return `${value.provider}/${value.model}${effort}`;
205
+ }
206
+ return String(value ?? "");
485
207
  }
486
208
 
487
209
  function coreDisplaySuffix(item: SettingsItem, inactive = false): string {
@@ -669,14 +391,19 @@ function resolveSettingsCoreView(
669
391
  const fallbackCore = {
670
392
  enabled: getPath(rawConfig, ["enabled"]) !== false,
671
393
  interactionContract: getPath(rawConfig, ["interactionContract"]) !== false,
394
+ contextVirtualization: getPath(rawConfig, ["contextVirtualization"]) === true,
395
+ conversationHistory: getPath(rawConfig, ["conversationHistory"]) === true,
672
396
  skills: { enabled: getPath(repositorySkills, ["enabled"]) !== false },
673
397
  defaultMode: validModeOrUndefined(rawConfig.defaultMode) ?? "workflow",
674
398
  };
675
399
  const fallbackSources = {
676
400
  enabled: typeof getPath(rawConfig, ["enabled"]) === "boolean" ? "repository" : "builtin",
677
401
  interactionContract: typeof getPath(rawConfig, ["interactionContract"]) === "boolean" ? "repository" : "builtin",
402
+ contextVirtualization:
403
+ typeof getPath(rawConfig, ["contextVirtualization"]) === "boolean" ? "repository" : "builtin",
404
+ conversationHistory: typeof getPath(rawConfig, ["conversationHistory"]) === "boolean" ? "repository" : "builtin",
678
405
  skillsEnabled: typeof getPath(repositorySkills, ["enabled"]) === "boolean" ? "repository" : "builtin",
679
- defaultMode: validModeOrUndefined(rawConfig.defaultMode) !== undefined ? "repository" : "builtin",
406
+ defaultMode: validModeOrUndefined(rawConfig.defaultMode) === undefined ? "builtin" : "repository",
680
407
  } as const;
681
408
  return {
682
409
  localConfig,
@@ -684,21 +411,19 @@ function resolveSettingsCoreView(
684
411
  sources: (layers?.sources ?? fallbackSources) as {
685
412
  enabled: ConfigSource;
686
413
  interactionContract: ConfigSource;
414
+ contextVirtualization: ConfigSource;
415
+ conversationHistory: ConfigSource;
687
416
  skillsEnabled: ConfigSource;
688
417
  defaultMode: ConfigSource;
689
418
  },
690
419
  };
691
420
  }
692
421
 
693
- function groupEnabled(items: SettingsItem[], id: string): boolean {
694
- return items.find((item) => item.id === id)?.value === true;
695
- }
696
-
697
422
  function createSessionBooleanItem(options: {
698
423
  id: string;
699
424
  label: string;
700
425
  description: string;
701
- key: "enabled" | "interactionContract" | "skillsEnabled";
426
+ key: "enabled" | "interactionContract" | "skillsEnabled" | "contextVirtualization" | "conversationHistory";
702
427
  inheritedValue: boolean;
703
428
  inheritedSource: ConfigSource;
704
429
  effectiveValue: boolean;
@@ -745,17 +470,22 @@ function createSessionBooleanItem(options: {
745
470
  function sessionFreeflowItems(
746
471
  state: Awaited<ReturnType<typeof readCapabilityState>>,
747
472
  modeState: Awaited<ReturnType<typeof readModeState>>,
473
+ cognitiveRoutingController?: CognitiveRoutingSettingsController,
748
474
  ): SettingsItem[] {
749
475
  const sessionOverrides = state.sessionOverrides as Record<string, boolean>;
750
476
  const configured = state.configuredCoreConfig;
751
477
  const configuredSources = state.configuredSources as {
752
478
  enabled: ConfigSource;
753
479
  interactionContract: ConfigSource;
480
+ contextVirtualization: ConfigSource;
481
+ conversationHistory: ConfigSource;
754
482
  skillsEnabled: ConfigSource;
755
483
  };
756
484
  const effectiveSources = state.configSources as {
757
485
  enabled: ConfigSource;
758
486
  interactionContract: ConfigSource;
487
+ contextVirtualization: ConfigSource;
488
+ conversationHistory: ConfigSource;
759
489
  skillsEnabled: ConfigSource;
760
490
  };
761
491
 
@@ -792,11 +522,35 @@ function sessionFreeflowItems(
792
522
  effectiveSource: effectiveSources.skillsEnabled,
793
523
  sessionOverrides,
794
524
  });
525
+ const contextVirtualizationItem = createSessionBooleanItem({
526
+ id: "freeflow.contextVirtualization",
527
+ label: "Context Virtualization",
528
+ description: "Temporary Context Virtualization override for this Pi session.",
529
+ key: "contextVirtualization",
530
+ inheritedValue: configured.contextVirtualization,
531
+ inheritedSource: configuredSources.contextVirtualization,
532
+ effectiveValue: state.contextVirtualization.enabled,
533
+ effectiveSource: effectiveSources.contextVirtualization,
534
+ sessionOverrides,
535
+ });
536
+ const conversationHistoryItem = createSessionBooleanItem({
537
+ id: "freeflow.conversationHistory",
538
+ label: "Conversation History",
539
+ description: "Temporary Conversation History override for this Pi session.",
540
+ key: "conversationHistory",
541
+ inheritedValue: configured.conversationHistory,
542
+ inheritedSource: configuredSources.conversationHistory,
543
+ effectiveValue: state.conversationHistory.enabled,
544
+ effectiveSource: effectiveSources.conversationHistory,
545
+ sessionOverrides,
546
+ });
795
547
 
796
548
  const freeflowInactive = !state.enabled;
797
549
  const skillsEnabled = state.skills.enabled;
798
550
  interactionItem.inactive = freeflowInactive;
799
551
  skillsItem.inactive = freeflowInactive;
552
+ contextVirtualizationItem.inactive = freeflowInactive || !skillsEnabled;
553
+ conversationHistoryItem.inactive = freeflowInactive || !skillsEnabled;
800
554
 
801
555
  const sessionMode = modeState.currentMode ?? "default";
802
556
  const sessionModeItem: SettingsItem = {
@@ -825,15 +579,48 @@ function sessionFreeflowItems(
825
579
  inheritedSource: modeState.defaultModeSource,
826
580
  };
827
581
 
582
+ const cognitiveRoutingState = cognitiveRoutingController?.state();
583
+ const cognitiveRoutingProfile =
584
+ cognitiveRoutingState?.controlMode === "manual-standard"
585
+ ? "standard"
586
+ : cognitiveRoutingState?.controlMode === "manual-reasoning"
587
+ ? "reasoning"
588
+ : "auto";
589
+ const cognitiveRoutingItem: SettingsItem | undefined = state.cognitiveRouting
590
+ ? {
591
+ id: "freeflow.cognitiveRouting.profile",
592
+ label: "Cognitive Routing",
593
+ description: "Hold a profile manually or release the hold for automatic model control.",
594
+ kind: "enum",
595
+ value: cognitiveRoutingProfile,
596
+ values: ["auto", "standard", "reasoning"],
597
+ valueLabels: {
598
+ auto: "automatic",
599
+ standard: "manual · standard",
600
+ reasoning: "manual · reasoning",
601
+ },
602
+ valueDescriptions: {
603
+ auto: "Release the manual hold without forcing a model transition.",
604
+ standard: "Hold the standard profile until /freeflow profile auto.",
605
+ reasoning: "Hold the reasoning profile until /freeflow profile auto.",
606
+ },
607
+ inactive: !state.cognitiveRouting.effective || cognitiveRoutingController === undefined,
608
+ runtimeInactive: state.cognitiveRouting.blockingReason?.code === "runtime_disabled",
609
+ displaySuffix: cognitiveRoutingState?.effective ? cognitiveRoutingProfile : "unavailable",
610
+ }
611
+ : undefined;
612
+
828
613
  return [
829
614
  freeflowItem,
830
615
  interactionItem,
831
616
  skillsItem,
832
617
  sessionModeItem,
618
+ ...(cognitiveRoutingItem ? [cognitiveRoutingItem] : []),
833
619
  {
834
620
  id: "freeflow.session.reset",
835
621
  label: "Reset session overrides",
836
- description: "Clear Freeflow, Interaction Contract, Skills, and mode overrides for this Pi session.",
622
+ description:
623
+ "Clear Freeflow, Interaction Contract, Skills, Context Virtualization, Conversation History, and mode overrides for this Pi session.",
837
624
  kind: "enum",
838
625
  value: "available",
839
626
  values: ["reset"],
@@ -842,15 +629,359 @@ function sessionFreeflowItems(
842
629
  format: () => "available",
843
630
  transient: true,
844
631
  },
632
+ {
633
+ id: "freeflow.context",
634
+ label: "Freeflow Context",
635
+ description: "Choose which context projection and conversation-history operations are available to the model.",
636
+ kind: "group",
637
+ value: contextVirtualizationItem.effectiveValue === true || conversationHistoryItem.effectiveValue === true,
638
+ displaySuffix: `${[contextVirtualizationItem, conversationHistoryItem].filter((item) => item.effectiveValue === true).length}/2 enabled`,
639
+ children: [contextVirtualizationItem, conversationHistoryItem],
640
+ },
845
641
  ];
846
642
  }
847
643
 
644
+ const COGNITIVE_ROUTING_THINKING_LEVELS: CognitiveRoutingThinkingLevel[] = [
645
+ "off",
646
+ "minimal",
647
+ "low",
648
+ "medium",
649
+ "high",
650
+ "xhigh",
651
+ "max",
652
+ ];
653
+
654
+ const COGNITIVE_ROUTING_EFFORT_DESCRIPTIONS: Record<CognitiveRoutingThinkingLevel, string> = {
655
+ off: "No reasoning",
656
+ minimal: "Very brief reasoning",
657
+ low: "Light reasoning",
658
+ medium: "Moderate reasoning",
659
+ high: "Deep reasoning",
660
+ xhigh: "Extra-high reasoning",
661
+ max: "Maximum reasoning",
662
+ };
663
+
664
+ type CognitiveRoutingModelOption = {
665
+ key: string;
666
+ provider: string;
667
+ model: string;
668
+ label: string;
669
+ description: string;
670
+ thinkingLevels: CognitiveRoutingThinkingLevel[];
671
+ };
672
+
673
+ function cognitiveRoutingModelKey(provider: string, model: string): string {
674
+ return `${provider}/${model}`;
675
+ }
676
+
677
+ function isCognitiveRoutingProfile(value: unknown): value is CognitiveRoutingProfile {
678
+ return (
679
+ isRecord(value) &&
680
+ typeof value.provider === "string" &&
681
+ typeof value.model === "string" &&
682
+ typeof value.thinkingLevel === "string" &&
683
+ COGNITIVE_ROUTING_THINKING_LEVELS.includes(value.thinkingLevel as CognitiveRoutingThinkingLevel)
684
+ );
685
+ }
686
+
687
+ function cognitiveRoutingThinkingLevels(model: any, registry: any): CognitiveRoutingThinkingLevel[] {
688
+ if (typeof registry?.clampThinkingLevel !== "function") {
689
+ return model?.reasoning === true ? [...COGNITIVE_ROUTING_THINKING_LEVELS] : ["off"];
690
+ }
691
+ return COGNITIVE_ROUTING_THINKING_LEVELS.filter((level) => {
692
+ try {
693
+ return registry.clampThinkingLevel(model, level) === level;
694
+ } catch {
695
+ return false;
696
+ }
697
+ });
698
+ }
699
+
700
+ function cognitiveRoutingModelOptions(ctx: any): CognitiveRoutingModelOption[] {
701
+ const registry = ctx?.modelRegistry;
702
+ if (typeof registry?.getAvailable !== "function") return [];
703
+ let models: any[];
704
+ try {
705
+ models = registry.getAvailable();
706
+ } catch {
707
+ return [];
708
+ }
709
+ if (!Array.isArray(models)) return [];
710
+
711
+ return models
712
+ .filter((model) => typeof model?.provider === "string" && typeof model?.id === "string")
713
+ .map((model) => {
714
+ const thinkingLevels = cognitiveRoutingThinkingLevels(model, registry);
715
+ const provider = model.provider as string;
716
+ const modelId = model.id as string;
717
+ const key = cognitiveRoutingModelKey(provider, modelId);
718
+ const displayName = typeof model.name === "string" && model.name !== modelId ? model.name : undefined;
719
+ return {
720
+ key,
721
+ provider,
722
+ model: modelId,
723
+ label: key,
724
+ description: displayName ? `${displayName} · ${thinkingLevels.join(", ")}` : thinkingLevels.join(", "),
725
+ thinkingLevels,
726
+ };
727
+ })
728
+ .filter((model) => model.thinkingLevels.length > 0)
729
+ .sort((a, b) => a.label.localeCompare(b.label));
730
+ }
731
+
732
+ function cognitiveRoutingProfileDisplay(value: unknown): string {
733
+ return isCognitiveRoutingProfile(value) ? formatCoreValue(value) : "not configured";
734
+ }
735
+
736
+ function cognitiveRoutingSettingsSource(source: unknown): ConfigSource {
737
+ if (source === "repository") return "repository";
738
+ if (source === "personal" || source === "local") return "local";
739
+ return "builtin";
740
+ }
741
+
742
+ function cognitiveRoutingProfileDisplaySuffix(source: unknown, scope: ConfigScope): string | undefined {
743
+ if (!source) return undefined;
744
+ if (source === "personal" && scope === "repository") return "(effective personal)";
745
+ return `(${source})`;
746
+ }
747
+
748
+ function cognitiveRoutingConfirmStep(summary: string): SettingsWizardStep {
749
+ return {
750
+ title: "Confirm preset",
751
+ choices: [
752
+ {
753
+ key: "__save__",
754
+ value: "save",
755
+ label: `Save ${summary}`,
756
+ description: "Write the complete profile atomically.",
757
+ },
758
+ { key: "__cancel__", value: "cancel", label: "Cancel", description: "Leave the previous preset unchanged." },
759
+ ],
760
+ selectedKey: "__save__",
761
+ };
762
+ }
763
+
764
+ function createCognitiveRoutingProfileWizard(
765
+ currentValue: unknown,
766
+ models: CognitiveRoutingModelOption[],
767
+ allowInherit: boolean,
768
+ ): SettingsWizard {
769
+ const currentProfile = isCognitiveRoutingProfile(currentValue) ? currentValue : undefined;
770
+ const currentModelKey = currentProfile
771
+ ? cognitiveRoutingModelKey(currentProfile.provider, currentProfile.model)
772
+ : undefined;
773
+ const firstChoices = [
774
+ ...(allowInherit
775
+ ? [
776
+ {
777
+ key: LOCAL_INHERIT,
778
+ value: LOCAL_INHERIT,
779
+ label: "Inherit repository preset",
780
+ description: "Remove the personal override without changing the repository preset.",
781
+ },
782
+ ]
783
+ : []),
784
+ ...models.map((model) => ({
785
+ key: model.key,
786
+ value: model,
787
+ label: model.label,
788
+ description: model.description,
789
+ })),
790
+ ];
791
+ if (firstChoices.length === 0) {
792
+ firstChoices.push({
793
+ key: "__cancel__",
794
+ value: "cancel",
795
+ label: "No authenticated models available",
796
+ description: "Authenticate a provider before configuring this preset.",
797
+ });
798
+ }
799
+
800
+ return {
801
+ firstStep: () => ({
802
+ title: "Choose model",
803
+ choices: firstChoices,
804
+ selectedKey:
805
+ currentValue === LOCAL_INHERIT
806
+ ? LOCAL_INHERIT
807
+ : firstChoices.some((choice) => choice.key === currentModelKey)
808
+ ? currentModelKey
809
+ : firstChoices[0]?.key,
810
+ }),
811
+ nextStep: (selectedValues) => {
812
+ if (selectedValues.length === 1) {
813
+ const selected = selectedValues[0];
814
+ if (selected === LOCAL_INHERIT) {
815
+ return cognitiveRoutingConfirmStep("inherit repository preset");
816
+ }
817
+ const model = selected as CognitiveRoutingModelOption;
818
+ if (!model?.key || model.thinkingLevels.length === 0) return undefined;
819
+ const currentEffort =
820
+ currentProfile && currentModelKey === model.key ? currentProfile.thinkingLevel : undefined;
821
+ return {
822
+ title: `Choose effort · ${model.label}`,
823
+ choices: model.thinkingLevels.map((level) => ({
824
+ key: level,
825
+ value: level,
826
+ label: level,
827
+ description: COGNITIVE_ROUTING_EFFORT_DESCRIPTIONS[level],
828
+ })),
829
+ selectedKey:
830
+ currentEffort && model.thinkingLevels.includes(currentEffort) ? currentEffort : model.thinkingLevels[0],
831
+ };
832
+ }
833
+ if (selectedValues.length === 2) {
834
+ const model = selectedValues[0] as CognitiveRoutingModelOption;
835
+ const effort = selectedValues[1] as CognitiveRoutingThinkingLevel;
836
+ return cognitiveRoutingConfirmStep(`${model.label} · ${effort}`);
837
+ }
838
+ return undefined;
839
+ },
840
+ valueFromSelections: (selectedValues) => {
841
+ if (selectedValues[0] === LOCAL_INHERIT) return undefined;
842
+ const model = selectedValues[0] as CognitiveRoutingModelOption;
843
+ return {
844
+ provider: model.provider,
845
+ model: model.model,
846
+ thinkingLevel: selectedValues[1] as CognitiveRoutingThinkingLevel,
847
+ } satisfies CognitiveRoutingProfile;
848
+ },
849
+ };
850
+ }
851
+
852
+ function cognitiveRoutingProfileItem(options: {
853
+ name: CognitiveRoutingProfileName;
854
+ scope: ConfigScope;
855
+ rawConfig: Record<string, unknown>;
856
+ localConfig: Record<string, unknown>;
857
+ capabilityState: CognitiveRoutingCapabilityState | undefined;
858
+ ctx: any;
859
+ }): SettingsItem {
860
+ const path = ["cognitiveRouting", "profiles", options.name];
861
+ const repositoryProfile = getPath(options.rawConfig, path);
862
+ const localProfile = getPath(options.localConfig, path);
863
+ const effectiveProfile = options.capabilityState?.profiles?.[options.name] ?? repositoryProfile;
864
+ const localValue = isCognitiveRoutingProfile(localProfile) ? localProfile : LOCAL_INHERIT;
865
+ const value = options.scope === "local" ? localValue : repositoryProfile;
866
+ const source =
867
+ options.capabilityState?.profileSources?.[options.name] ??
868
+ (isCognitiveRoutingProfile(repositoryProfile) ? "repository" : undefined);
869
+ const models = cognitiveRoutingModelOptions(options.ctx);
870
+ const inactive = options.scope === "local" && models.length === 0 && localValue !== LOCAL_INHERIT;
871
+
872
+ return {
873
+ id: `freeflow.cognitiveRouting.${options.name}`,
874
+ label: `${options.name.charAt(0).toUpperCase()}${options.name.slice(1)} preset`,
875
+ description:
876
+ "Choose an authenticated available model and one effort supported by that model. Confirming writes the complete preset; cancel leaves it unchanged.",
877
+ path,
878
+ kind: "string",
879
+ value,
880
+ format: cognitiveRoutingProfileDisplay,
881
+ configScope: options.scope,
882
+ effectiveValue: effectiveProfile,
883
+ effectiveSource: cognitiveRoutingSettingsSource(source),
884
+ inheritedValue: repositoryProfile,
885
+ inheritedSource: "repository",
886
+ inactive,
887
+ displaySuffix: cognitiveRoutingProfileDisplaySuffix(source, options.scope),
888
+ wizard: () => createCognitiveRoutingProfileWizard(value, models, options.scope === "local"),
889
+ };
890
+ }
891
+
892
+ type CognitiveRoutingSessionStartSetting = "control" | "profile";
893
+
894
+ function cognitiveRoutingSessionStartItem(options: {
895
+ setting: CognitiveRoutingSessionStartSetting;
896
+ scope: ConfigScope;
897
+ rawConfig: Record<string, unknown>;
898
+ localConfig: Record<string, unknown>;
899
+ capabilityState: CognitiveRoutingCapabilityState | undefined;
900
+ }): SettingsItem {
901
+ const path = ["cognitiveRouting", "sessionStart", options.setting];
902
+ const defaultValue = options.setting === "control" ? "automatic" : "standard";
903
+ const values = options.setting === "control" ? ["automatic", "manual"] : ["standard", "reasoning"];
904
+ const labels = Object.fromEntries(values.map((value) => [value, value]));
905
+ const descriptions =
906
+ options.setting === "control"
907
+ ? {
908
+ automatic: "Let Cognitive Routing manage profile transitions for the new session.",
909
+ manual: "Hold the selected profile for the new session until the user changes it.",
910
+ }
911
+ : {
912
+ standard: "Start a new session with the configured Standard preset.",
913
+ reasoning: "Start a new session with the configured Reasoning preset.",
914
+ };
915
+ const repositoryValue = getPath(options.rawConfig, path);
916
+ const localValue = getPath(options.localConfig, path);
917
+ const isValidValue = (value: unknown): value is string => values.includes(value as string);
918
+ const repositorySetting = isValidValue(repositoryValue) ? repositoryValue : undefined;
919
+ const localSetting = isValidValue(localValue) ? localValue : undefined;
920
+ const inheritedValue = repositorySetting ?? defaultValue;
921
+ const inheritedSource: ConfigSource = repositorySetting ? "repository" : "builtin";
922
+ const configuredSource =
923
+ options.capabilityState?.sessionStartSources?.[options.setting] ?? (repositorySetting ? "repository" : undefined);
924
+ const effectiveValue = options.capabilityState?.sessionStart?.[options.setting] ?? inheritedValue;
925
+ const effectiveSource = cognitiveRoutingSettingsSource(configuredSource);
926
+ const label = options.setting === "control" ? "Session start control" : "Session start profile";
927
+ const description =
928
+ options.setting === "control"
929
+ ? "Choose automatic or manual control for new sessions only; changing this does not alter the active session."
930
+ : "Choose the initial profile for new sessions only; changing this does not alter the active session.";
931
+
932
+ const item: SettingsItem =
933
+ options.scope === "local"
934
+ ? {
935
+ id: `freeflow.cognitiveRouting.sessionStart.${options.setting}`,
936
+ label,
937
+ description: `${description} Choose inherit to use the repository value; use /freeflow settings repo to edit shared defaults.`,
938
+ path,
939
+ kind: "enum",
940
+ value: localSetting ?? LOCAL_INHERIT,
941
+ values: [LOCAL_INHERIT, ...values],
942
+ valueLabels: { inherit: "Inherit repository", ...labels },
943
+ valueDescriptions: {
944
+ inherit: `Use ${inheritedValue} from ${inheritedSource}.`,
945
+ ...descriptions,
946
+ },
947
+ format: (value) => String(value),
948
+ configScope: "local",
949
+ configValues: { inherit: undefined },
950
+ effectiveValue,
951
+ effectiveSource,
952
+ inheritedValue,
953
+ inheritedSource,
954
+ }
955
+ : {
956
+ id: `freeflow.cognitiveRouting.sessionStart.${options.setting}`,
957
+ label,
958
+ description: `${description} This edits shared .freeflow/config.json.`,
959
+ path,
960
+ kind: "enum",
961
+ value: inheritedValue,
962
+ values,
963
+ valueLabels: labels,
964
+ valueDescriptions: descriptions,
965
+ format: (value) => String(value),
966
+ defaultValue,
967
+ configScope: "repository",
968
+ effectiveValue,
969
+ effectiveSource,
970
+ localOverrideValue: localSetting,
971
+ };
972
+ item.displaySuffix = coreDisplaySuffix(item);
973
+ return item;
974
+ }
975
+
848
976
  function freeflowItems(
849
977
  rawConfig: Record<string, unknown>,
850
978
  modeState?: Awaited<ReturnType<typeof readModeState>>,
851
979
  options: {
852
980
  scope?: ConfigScope;
853
981
  layers?: Awaited<ReturnType<typeof readFreeflowConfigLayers>>;
982
+ cognitiveRouting?: CognitiveRoutingCapabilityState;
983
+ ctx?: any;
984
+ hostInfo?: unknown;
854
985
  } = {},
855
986
  ): SettingsItem[] {
856
987
  const scope = options.scope ?? "repository";
@@ -887,12 +1018,36 @@ function freeflowItems(
887
1018
  localConfig,
888
1019
  id: "freeflow.skills.enabled",
889
1020
  label: "Skills",
890
- description: "Expose Freeflow skills and load Workflow once on the first turn.",
1021
+ description: "Expose Freeflow skills and discover Workflow before consequential or mutating work.",
891
1022
  path: ["skills", "enabled"],
892
1023
  effectiveValue: core.skills.enabled,
893
1024
  effectiveSource: sources.skillsEnabled,
894
1025
  defaultValue: DEFAULT_SKILLS_ENABLED,
895
1026
  });
1027
+ const contextVirtualizationItem = createScopedBooleanItem({
1028
+ scope,
1029
+ rawConfig,
1030
+ localConfig,
1031
+ id: "freeflow.contextVirtualization",
1032
+ label: "Context Virtualization",
1033
+ description: "Let the model archive consumed tool results from future context while preserving session history.",
1034
+ path: ["contextVirtualization"],
1035
+ effectiveValue: core.contextVirtualization,
1036
+ effectiveSource: sources.contextVirtualization,
1037
+ defaultValue: DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED,
1038
+ });
1039
+ const conversationHistoryItem = createScopedBooleanItem({
1040
+ scope,
1041
+ rawConfig,
1042
+ localConfig,
1043
+ id: "freeflow.conversationHistory",
1044
+ label: "Conversation History",
1045
+ description: "Let the model search and retrieve hidden conversation history on the active branch.",
1046
+ path: ["conversationHistory"],
1047
+ effectiveValue: core.conversationHistory,
1048
+ effectiveSource: sources.conversationHistory,
1049
+ defaultValue: DEFAULT_CONVERSATION_HISTORY_ENABLED,
1050
+ });
896
1051
 
897
1052
  const repositoryModeValue = validModeOrUndefined(rawConfig.defaultMode);
898
1053
  const repositoryDefaultMode = repositoryModeValue ?? "workflow";
@@ -911,6 +1066,8 @@ function freeflowItems(
911
1066
  const skillsEnabled = core.skills.enabled;
912
1067
  interactionItem.inactive = freeflowInactive;
913
1068
  skillsItem.inactive = freeflowInactive;
1069
+ contextVirtualizationItem.inactive = freeflowInactive || !skillsEnabled;
1070
+ conversationHistoryItem.inactive = freeflowInactive || !skillsEnabled;
914
1071
  defaultModeItem.inactive = freeflowInactive;
915
1072
  defaultModeItem.displaySuffix = coreDisplaySuffix(defaultModeItem, freeflowInactive || !skillsEnabled);
916
1073
  const sessionMode = modeState?.currentMode ?? "default";
@@ -939,8 +1096,72 @@ function freeflowItems(
939
1096
  ),
940
1097
  };
941
1098
 
942
- const routerItems = outputRouterItems(rawConfig, freeflowInactive);
943
- const routerEnabled = groupEnabled(routerItems, "outputRouter.enabled");
1099
+ const cognitiveRoutingState = options.cognitiveRouting;
1100
+ const cognitiveRoutingRuntimeDisabled = !isPiFlowHost(options.hostInfo);
1101
+ const cognitiveRoutingGroup = (() => {
1102
+ const cognitiveRoutingEnabledItem = createScopedBooleanItem({
1103
+ scope,
1104
+ rawConfig,
1105
+ localConfig,
1106
+ id: "freeflow.cognitiveRouting.enabled",
1107
+ label: "Enabled",
1108
+ description: cognitiveRoutingRuntimeDisabled
1109
+ ? "Configured for PiFlow, but disabled in normal Pi."
1110
+ : "Allow Cognitive Routing to own the session model lease and switch the configured profiles.",
1111
+ path: ["cognitiveRouting", "enabled"],
1112
+ effectiveValue: cognitiveRoutingState?.enabled ?? false,
1113
+ effectiveSource: cognitiveRoutingSettingsSource(cognitiveRoutingState?.enabledSource),
1114
+ defaultValue: false,
1115
+ });
1116
+ cognitiveRoutingEnabledItem.inactive = freeflowInactive || !skillsEnabled || cognitiveRoutingRuntimeDisabled;
1117
+ cognitiveRoutingEnabledItem.runtimeInactive = cognitiveRoutingRuntimeDisabled;
1118
+
1119
+ const cognitiveRoutingProfiles = ["standard", "reasoning"].map((name) =>
1120
+ cognitiveRoutingProfileItem({
1121
+ name: name as CognitiveRoutingProfileName,
1122
+ scope,
1123
+ rawConfig,
1124
+ localConfig,
1125
+ capabilityState: cognitiveRoutingState,
1126
+ ctx: options.ctx,
1127
+ }),
1128
+ );
1129
+ const cognitiveRoutingSessionStart = (["control", "profile"] as CognitiveRoutingSessionStartSetting[]).map(
1130
+ (setting) =>
1131
+ cognitiveRoutingSessionStartItem({
1132
+ setting,
1133
+ scope,
1134
+ rawConfig,
1135
+ localConfig,
1136
+ capabilityState: cognitiveRoutingState,
1137
+ }),
1138
+ );
1139
+ for (const item of [...cognitiveRoutingProfiles, ...cognitiveRoutingSessionStart]) {
1140
+ item.inactive ||= freeflowInactive || !skillsEnabled || cognitiveRoutingRuntimeDisabled;
1141
+ item.runtimeInactive = cognitiveRoutingRuntimeDisabled;
1142
+ }
1143
+ const cognitiveRoutingStatus = cognitiveRoutingRuntimeDisabled
1144
+ ? "disabled · PiFlow only"
1145
+ : cognitiveRoutingState
1146
+ ? cognitiveRoutingState.effective
1147
+ ? "active"
1148
+ : cognitiveRoutingState.blockingReason?.code === "profile_missing"
1149
+ ? "not configured"
1150
+ : (cognitiveRoutingState.blockingReason?.code ?? "inactive")
1151
+ : "unavailable";
1152
+ return {
1153
+ id: "freeflow.cognitiveRouting",
1154
+ label: "Cognitive Routing",
1155
+ description: cognitiveRoutingRuntimeDisabled
1156
+ ? "Cognitive Routing configuration is visible for inspection but can only run in PiFlow."
1157
+ : "Configure the automatic standard/reasoning profiles and choose whether Freeflow may manage model state for this repository.",
1158
+ kind: "group" as const,
1159
+ value: cognitiveRoutingRuntimeDisabled ? false : (cognitiveRoutingState?.enabled ?? false),
1160
+ inactive: freeflowInactive || !skillsEnabled,
1161
+ displaySuffix: cognitiveRoutingStatus,
1162
+ children: [cognitiveRoutingEnabledItem, ...cognitiveRoutingProfiles, ...cognitiveRoutingSessionStart],
1163
+ } satisfies SettingsItem;
1164
+ })();
944
1165
 
945
1166
  return [
946
1167
  freeflowItem,
@@ -948,155 +1169,25 @@ function freeflowItems(
948
1169
  skillsItem,
949
1170
  sessionModeItem,
950
1171
  defaultModeItem,
1172
+ ...(cognitiveRoutingGroup ? [cognitiveRoutingGroup] : []),
951
1173
  {
952
- id: "outputRouter.group",
953
- label: "Output Router",
954
- description:
955
- "Shared repository settings for routed evidence tools, native output safety net, vault storage, script transforms, and observed tool routing.",
1174
+ id: "freeflow.context",
1175
+ label: "Freeflow Context",
1176
+ description: "Choose which context projection and conversation-history operations are available to the model.",
956
1177
  kind: "group",
957
- value: routerEnabled,
958
- inactive: freeflowInactive,
959
- displaySuffix: "(repository)",
960
- children: routerItems,
1178
+ value: contextVirtualizationItem.effectiveValue === true || conversationHistoryItem.effectiveValue === true,
1179
+ displaySuffix: `${[contextVirtualizationItem, conversationHistoryItem].filter((item) => item.effectiveValue === true).length}/2 enabled`,
1180
+ children: [contextVirtualizationItem, conversationHistoryItem],
961
1181
  },
962
1182
  ];
963
1183
  }
964
1184
 
965
- function migrateLegacyRouterConfig(config: Record<string, unknown>) {
966
- const outputRouter = isRecord(config.outputRouter) ? config.outputRouter : undefined;
967
- if (!outputRouter) return;
968
- if (config.scriptTransform !== undefined && outputRouter.scriptTransform === undefined) {
969
- outputRouter.scriptTransform = config.scriptTransform;
970
- delete config.scriptTransform;
971
- }
972
- if (config.observedRouting !== undefined && outputRouter.observedRouting === undefined) {
973
- outputRouter.observedRouting = config.observedRouting;
974
- delete config.observedRouting;
975
- }
976
-
977
- const thresholds = isRecord(outputRouter.thresholds) ? outputRouter.thresholds : {};
978
- if (outputRouter.largeOutputBytes !== undefined && thresholds.largeOutputBytes === undefined) {
979
- thresholds.largeOutputBytes = outputRouter.largeOutputBytes;
980
- outputRouter.thresholds = thresholds;
981
- delete outputRouter.largeOutputBytes;
982
- }
983
- if (outputRouter.largeOutputLines !== undefined && thresholds.largeOutputLines === undefined) {
984
- thresholds.largeOutputLines = outputRouter.largeOutputLines;
985
- outputRouter.thresholds = thresholds;
986
- delete outputRouter.largeOutputLines;
987
- }
988
-
989
- const vault = isRecord(outputRouter.vault) ? outputRouter.vault : {};
990
- if (outputRouter.vaultRoot !== undefined && vault.root === undefined) {
991
- vault.root = outputRouter.vaultRoot;
992
- outputRouter.vault = vault;
993
- delete outputRouter.vaultRoot;
994
- }
995
- if (outputRouter.vaultRetentionDays !== undefined && !isRecord(vault.retention)) {
996
- vault.retention = {
997
- strategy: "ttl",
998
- ttlDays: outputRouter.vaultRetentionDays,
999
- };
1000
- outputRouter.vault = vault;
1001
- delete outputRouter.vaultRetentionDays;
1002
- }
1003
-
1004
- const hints = isRecord(outputRouter.hints) ? outputRouter.hints : {};
1005
- if (outputRouter.generatedPaths !== undefined && hints.generatedPathGlobs === undefined) {
1006
- hints.generatedPathGlobs = outputRouter.generatedPaths;
1007
- outputRouter.hints = hints;
1008
- delete outputRouter.generatedPaths;
1009
- }
1010
- if (outputRouter.noisyCommandHints !== undefined && hints.noisyCommandPatterns === undefined) {
1011
- hints.noisyCommandPatterns = outputRouter.noisyCommandHints;
1012
- outputRouter.hints = hints;
1013
- delete outputRouter.noisyCommandHints;
1014
- }
1015
- }
1016
-
1017
1185
  function pruneKnownDefaults(config: Record<string, unknown>) {
1018
1186
  const defaultPaths: Array<{ path: string[]; value: unknown }> = [
1019
1187
  { path: ["enabled"], value: DEFAULT_FREEFLOW_ENABLED },
1020
- {
1021
- path: ["interactionContract"],
1022
- value: DEFAULT_INTERACTION_CONTRACT_ENABLED,
1023
- },
1188
+ { path: ["interactionContract"], value: DEFAULT_INTERACTION_CONTRACT_ENABLED },
1024
1189
  { path: ["skills", "enabled"], value: DEFAULT_SKILLS_ENABLED },
1025
- { path: ["outputRouter", "enabled"], value: DEFAULT_OUTPUT_ROUTER_ENABLED },
1026
- {
1027
- path: ["outputRouter", "postToolRouting"],
1028
- value: DEFAULT_POST_TOOL_ROUTING,
1029
- },
1030
- { path: ["outputRouter", "storagePolicy"], value: DEFAULT_STORAGE_POLICY },
1031
- {
1032
- path: ["outputRouter", "thresholds", "largeOutputBytes"],
1033
- value: DEFAULT_ROUTER_THRESHOLDS.largeOutputBytes,
1034
- },
1035
- {
1036
- path: ["outputRouter", "thresholds", "largeOutputLines"],
1037
- value: DEFAULT_ROUTER_THRESHOLDS.largeOutputLines,
1038
- },
1039
- { path: ["outputRouter", "vault", "root"], value: DEFAULT_VAULT_ROOT },
1040
- {
1041
- path: ["outputRouter", "vault", "retention", "ttlDays"],
1042
- value: DEFAULT_VAULT_RETENTION.ttlDays,
1043
- },
1044
- {
1045
- path: ["outputRouter", "scriptTransform", "enabled"],
1046
- value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.enabled,
1047
- },
1048
- {
1049
- path: ["outputRouter", "scriptTransform", "sandbox"],
1050
- value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.sandbox,
1051
- },
1052
- {
1053
- path: ["outputRouter", "scriptTransform", "languages"],
1054
- value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.languages,
1055
- },
1056
- {
1057
- path: ["outputRouter", "scriptTransform", "network"],
1058
- value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.network,
1059
- },
1060
- {
1061
- path: ["outputRouter", "scriptTransform", "rawScriptPersistence"],
1062
- value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.rawScriptPersistence,
1063
- },
1064
- {
1065
- path: ["outputRouter", "scriptTransform", "limits", "timeoutMs"],
1066
- value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.timeoutMs,
1067
- },
1068
- {
1069
- path: ["outputRouter", "scriptTransform", "limits", "maxInputBytes"],
1070
- value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.maxInputBytes,
1071
- },
1072
- {
1073
- path: ["outputRouter", "scriptTransform", "limits", "maxOutputBytes"],
1074
- value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.maxOutputBytes,
1075
- },
1076
- {
1077
- path: ["outputRouter", "observedRouting", "enabled"],
1078
- value: DEFAULT_OBSERVED_ROUTING_CONFIG.enabled,
1079
- },
1080
- {
1081
- path: ["outputRouter", "observedRouting", "onRoutingFailure"],
1082
- value: DEFAULT_OBSERVED_ROUTING_CONFIG.onRoutingFailure,
1083
- },
1084
- {
1085
- path: ["outputRouter", "observedRouting", "mcp", "servers"],
1086
- value: DEFAULT_OBSERVED_ROUTING_CONFIG.mcp.servers,
1087
- },
1088
- {
1089
- path: ["outputRouter", "observedRouting", "web", "enabled"],
1090
- value: DEFAULT_OBSERVED_ROUTING_CONFIG.web.enabled,
1091
- },
1092
- {
1093
- path: ["outputRouter", "observedRouting", "fetch", "enabled"],
1094
- value: DEFAULT_OBSERVED_ROUTING_CONFIG.fetch.enabled,
1095
- },
1096
- {
1097
- path: ["outputRouter", "observedRouting", "codeSearch", "enabled"],
1098
- value: DEFAULT_OBSERVED_ROUTING_CONFIG.codeSearch.enabled,
1099
- },
1190
+ { path: ["conversationHistory"], value: DEFAULT_CONVERSATION_HISTORY_ENABLED },
1100
1191
  ];
1101
1192
 
1102
1193
  for (const item of defaultPaths) {
@@ -1104,19 +1195,6 @@ function pruneKnownDefaults(config: Record<string, unknown>) {
1104
1195
  deletePath(config, item.path);
1105
1196
  }
1106
1197
  }
1107
-
1108
- for (const producer of ["web", "fetch", "codeSearch"]) {
1109
- const enabledPath = ["outputRouter", "observedRouting", producer, "enabled"];
1110
- const persistencePath = ["outputRouter", "observedRouting", producer, "persistence"];
1111
- if (getPath(config, enabledPath) !== true && getPath(config, persistencePath) === "none") {
1112
- deletePath(config, persistencePath);
1113
- }
1114
- }
1115
-
1116
- const servers = getPath(config, ["outputRouter", "observedRouting", "mcp", "servers"]);
1117
- if (isRecord(servers) && Object.keys(servers).length === 0) {
1118
- deletePath(config, ["outputRouter", "observedRouting", "mcp", "servers"]);
1119
- }
1120
1198
  }
1121
1199
 
1122
1200
  async function ensureLocalConfigIgnored(cwd: string) {
@@ -1190,15 +1268,20 @@ async function updateConfig(
1190
1268
  const current = await readFreeflowConfig(cwd);
1191
1269
  const next = cloneJson(current) as Record<string, unknown>;
1192
1270
  setConfigValue(next, item, value);
1193
- migrateLegacyRouterConfig(next);
1194
1271
  pruneKnownDefaults(next);
1195
1272
  await mkdir(join(cwd, ".freeflow"), { recursive: true });
1196
1273
  await writeFile(join(cwd, ".freeflow/config.json"), `${JSON.stringify(next, null, 2)}\n`, "utf8");
1197
1274
  }
1198
1275
 
1276
+ function isCognitiveRoutingProfileItem(item: SettingsItem): boolean {
1277
+ return item.id === "freeflow.cognitiveRouting.standard" || item.id === "freeflow.cognitiveRouting.reasoning";
1278
+ }
1279
+
1199
1280
  function valueForDisplay(item: SettingsItem): string {
1200
1281
  let value: string;
1201
- if (item.configScope === "local" || item.configScope === "session") {
1282
+ if (isCognitiveRoutingProfileItem(item) && item.format) {
1283
+ value = item.format(effectiveItemValue(item));
1284
+ } else if (item.configScope === "local" || item.configScope === "session") {
1202
1285
  value = formatCoreValue(effectiveItemValue(item));
1203
1286
  } else if (item.kind === "boolean") {
1204
1287
  value = booleanValue(item.value);
@@ -1235,10 +1318,14 @@ function refreshSettingsDerivedState(items: SettingsItem[]) {
1235
1318
  const freeflowInactive = freeflowItem ? effectiveItemValue(freeflowItem) !== true : false;
1236
1319
  const skillsItem = findSettingsItem(items, "freeflow.skills.enabled");
1237
1320
  const skillsEnabled = skillsItem ? effectiveItemValue(skillsItem) === true : true;
1238
- const routerEnabled = findSettingsItem(items, "outputRouter.enabled")?.value === true;
1239
1321
  const sessionModeItem = findSettingsItem(items, "freeflow.sessionMode");
1240
1322
  const defaultModeItem = findSettingsItem(items, "freeflow.defaultMode");
1241
- const routerGroup = findSettingsItem(items, "outputRouter.group");
1323
+ const cognitiveRoutingGroup = findSettingsItem(items, "freeflow.cognitiveRouting");
1324
+ const cognitiveRoutingEnabledItem = findSettingsItem(items, "freeflow.cognitiveRouting.enabled");
1325
+ const cognitiveRoutingProfiles = [
1326
+ findSettingsItem(items, "freeflow.cognitiveRouting.standard"),
1327
+ findSettingsItem(items, "freeflow.cognitiveRouting.reasoning"),
1328
+ ];
1242
1329
 
1243
1330
  if (sessionModeItem) {
1244
1331
  const defaultMode = String(
@@ -1258,29 +1345,59 @@ function refreshSettingsDerivedState(items: SettingsItem[]) {
1258
1345
  };
1259
1346
  }
1260
1347
 
1261
- if (routerGroup) {
1262
- routerGroup.value = routerEnabled;
1263
- routerGroup.inactive = freeflowInactive;
1348
+ if (cognitiveRoutingGroup && cognitiveRoutingEnabledItem) {
1349
+ const enabled = effectiveItemValue(cognitiveRoutingEnabledItem) === true;
1350
+ const profilesConfigured = cognitiveRoutingProfiles.every((item) =>
1351
+ item ? isCognitiveRoutingProfile(effectiveItemValue(item)) : false,
1352
+ );
1353
+ cognitiveRoutingGroup.value = enabled;
1354
+ cognitiveRoutingGroup.displaySuffix = enabled
1355
+ ? profilesConfigured
1356
+ ? cognitiveRoutingGroup.displaySuffix === "active"
1357
+ ? "active"
1358
+ : "configured"
1359
+ : "not configured"
1360
+ : "disabled";
1361
+ cognitiveRoutingGroup.inactive = freeflowInactive || !skillsEnabled;
1362
+ }
1363
+
1364
+ const contextGroup = findSettingsItem(items, "freeflow.context");
1365
+ if (contextGroup?.children?.length) {
1366
+ const enabledCount = contextGroup.children.filter((item) => effectiveItemValue(item) === true).length;
1367
+ contextGroup.value = enabledCount > 0;
1368
+ contextGroup.displaySuffix = `${enabledCount}/${contextGroup.children.length} enabled`;
1264
1369
  }
1265
1370
 
1266
1371
  walkSettingsItems(items, (candidate) => {
1267
1372
  if (candidate.id === "freeflow.session.reset") {
1268
1373
  candidate.inactive = false;
1269
1374
  } else if (candidate.configScope) {
1270
- const inactive = candidate.id === "freeflow.enabled" ? false : freeflowInactive;
1375
+ const inactive =
1376
+ candidate.id === "freeflow.enabled"
1377
+ ? false
1378
+ : candidate.runtimeInactive === true ||
1379
+ freeflowInactive ||
1380
+ (!skillsEnabled &&
1381
+ [
1382
+ "freeflow.cognitiveRouting",
1383
+ "freeflow.cognitiveRouting.enabled",
1384
+ "freeflow.cognitiveRouting.standard",
1385
+ "freeflow.cognitiveRouting.reasoning",
1386
+ "freeflow.cognitiveRouting.sessionStart.control",
1387
+ "freeflow.cognitiveRouting.sessionStart.profile",
1388
+ "freeflow.contextVirtualization",
1389
+ "freeflow.conversationHistory",
1390
+ ].includes(candidate.id));
1271
1391
  const displayInactive = candidate.id === "freeflow.defaultMode" ? freeflowInactive || !skillsEnabled : inactive;
1272
1392
  candidate.inactive = inactive;
1273
1393
  candidate.displaySuffix = coreDisplaySuffix(candidate, displayInactive);
1274
1394
  } else if (candidate.id === "freeflow.sessionMode") {
1275
- candidate.inactive = freeflowInactive || !skillsEnabled;
1276
- } else if (candidate.id === "outputRouter.group") {
1277
- candidate.inactive = freeflowInactive;
1278
- } else if (candidate.id === "outputRouter.enabled") {
1279
- candidate.inactive = freeflowInactive;
1280
- } else if (candidate.id.startsWith("outputRouter.")) {
1281
- candidate.inactive = freeflowInactive || !routerEnabled;
1395
+ candidate.inactive = candidate.runtimeInactive === true || freeflowInactive || !skillsEnabled;
1282
1396
  } else {
1283
- candidate.inactive = freeflowInactive;
1397
+ candidate.inactive =
1398
+ candidate.runtimeInactive === true ||
1399
+ freeflowInactive ||
1400
+ (!skillsEnabled && candidate.id === "freeflow.cognitiveRouting");
1284
1401
  }
1285
1402
  });
1286
1403
  }
@@ -1327,6 +1444,7 @@ function settingsEntries(
1327
1444
  currentChoiceKey: () => String(item.value),
1328
1445
  choices: settingsChoices(item),
1329
1446
  children: item.children ? () => settingsEntries(item.children!, rootItems, onChange) : undefined,
1447
+ wizard: item.wizard ? () => item.wizard!() : undefined,
1330
1448
  edit:
1331
1449
  !item.children && !["boolean", "enum", "group"].includes(item.kind)
1332
1450
  ? {
@@ -1385,32 +1503,30 @@ async function openSettings(options: OpenSettingsOptions): Promise<SettingsSessi
1385
1503
  return component?.sessionResult() ?? { changed: false, configChanged: false, failed: false };
1386
1504
  }
1387
1505
 
1388
- function outputRouterStatusText(rawConfig: Record<string, unknown>): string {
1389
- const settingsConfig = normalizedSettingsConfig(rawConfig);
1390
- const actual = normalizeFreeflowConfig(rawConfig).config;
1391
- const freeflowEnabled = rawConfig.enabled !== false;
1392
- const router = actual.outputRouter;
1393
- const configured = settingsConfig;
1394
- const generatedPaths = router.hints?.generatedPathGlobs?.length ?? 0;
1395
- const noisyHints = router.hints?.noisyCommandPatterns?.length ?? 0;
1396
- return [
1397
- `Output Router: ${freeflowEnabled && router.enabled ? "enabled" : "disabled"}${freeflowEnabled ? "" : " (inactive: Freeflow off)"}`,
1398
- `script transform: ${freeflowEnabled && configured.scriptTransform.enabled ? "enabled" : "disabled"}${freeflowEnabled && router.enabled ? "" : " (inactive)"}`,
1399
- `observed routing: ${freeflowEnabled && configured.observedRouting.enabled ? "enabled" : "disabled"}${freeflowEnabled && router.enabled ? "" : " (inactive)"}`,
1400
- `native safety net: ${freeflowEnabled ? router.postToolRouting : "off"}`,
1401
- `storage: ${router.storagePolicy}`,
1402
- `generated paths: ${generatedPaths}`,
1403
- `noisy command hints: ${noisyHints}`,
1404
- ].join("; ");
1405
- }
1406
-
1407
- function freeflowStatusText(state: Awaited<ReturnType<typeof readCapabilityState>>): string {
1506
+ function freeflowStatusText(
1507
+ state: Awaited<ReturnType<typeof readCapabilityState>>,
1508
+ cognitiveRoutingController?: CognitiveRoutingSettingsController,
1509
+ ): string {
1408
1510
  if (!state.configured) {
1409
1511
  return state.configExists
1410
1512
  ? `Freeflow: inactive (invalid config: ${state.parseError ?? "unknown parse error"}); run /setup-freeflow or fix .freeflow/config.json`
1411
1513
  : "Freeflow: inactive (repo not set up); run /setup-freeflow";
1412
1514
  }
1413
1515
  const sessionSuffix = (source: ConfigSource) => (source === "session" ? " (session override)" : "");
1516
+ const routingState = cognitiveRoutingController?.state();
1517
+ const cognitiveRouting = state.cognitiveRouting;
1518
+ const cognitiveRoutingStatus = cognitiveRouting
1519
+ ? cognitiveRouting.blockingReason?.code === "runtime_disabled"
1520
+ ? "disabled (PiFlow only)"
1521
+ : cognitiveRouting.effective
1522
+ ? routingState?.effective
1523
+ ? `active (${routingState.activeProfile ?? "unknown"}, ${routingState.controlMode})`
1524
+ : "effective (inactive)"
1525
+ : cognitiveRouting.enabled
1526
+ ? `blocked (${cognitiveRouting.blockingReason.code})`
1527
+ : "disabled"
1528
+ : undefined;
1529
+ const contextEnabled = state.contextVirtualization?.effective || state.conversationHistory?.effective;
1414
1530
  return [
1415
1531
  `Freeflow: ${state.enabled ? "enabled" : "disabled"}${sessionSuffix(state.configSources.enabled as ConfigSource)}`,
1416
1532
  `interaction contract: ${state.interactionContract.effective ? "enabled" : "disabled"}${sessionSuffix(
@@ -1419,7 +1535,8 @@ function freeflowStatusText(state: Awaited<ReturnType<typeof readCapabilityState
1419
1535
  `skills: ${state.skills.effective ? "enabled" : "disabled (workflow modes inactive)"}${sessionSuffix(
1420
1536
  state.configSources.skillsEnabled as ConfigSource,
1421
1537
  )}`,
1422
- `output router: ${state.outputRouter.enabled ? "enabled" : "disabled"}`,
1538
+ `context: ${contextEnabled ? "enabled" : "disabled"} (virtualization ${state.contextVirtualization?.effective ? "enabled" : "disabled"}, history ${state.conversationHistory?.effective ? "enabled" : "disabled"})`,
1539
+ ...(cognitiveRoutingStatus ? [`cognitive routing: ${cognitiveRoutingStatus}`] : []),
1423
1540
  ].join("; ");
1424
1541
  }
1425
1542
 
@@ -1428,6 +1545,12 @@ const NON_TUI_SETTINGS_GUIDANCE =
1428
1545
  const NON_TUI_MODE_GUIDANCE =
1429
1546
  "Freeflow mode selector requires Pi TUI mode. Use /freeflow mode conversation, /freeflow mode workflow, /freeflow mode strict-workflow, or /freeflow mode reset.";
1430
1547
 
1548
+ function ensureSettingsIdle(ctx: any): boolean {
1549
+ if (typeof ctx?.isIdle !== "function" || ctx.isIdle()) return true;
1550
+ ctx.ui?.notify?.("Freeflow settings and profile changes are available only while Pi is idle.", "warning");
1551
+ return false;
1552
+ }
1553
+
1431
1554
  function nonTuiGuidance(ctx: any, message: string) {
1432
1555
  if (ctx?.hasUI === false) throw new Error(message);
1433
1556
  ctx?.ui?.notify?.(message, "warning");
@@ -1437,13 +1560,14 @@ function nonTuiGuidance(ctx: any, message: string) {
1437
1560
  async function finalizeSettingsSession(
1438
1561
  session: SettingsSessionResult,
1439
1562
  ctx: any,
1440
- afterChange: (changed: boolean) => Promise<void> | void,
1563
+ afterChange: AfterChange,
1441
1564
  savedMessage: string,
1442
1565
  reloadWarning = "Run /reload for Freeflow changes to fully apply.",
1566
+ afterChangeOptions: AfterChangeOptions = {},
1443
1567
  ) {
1444
1568
  if (!session.configChanged) return false;
1445
1569
 
1446
- await afterChange(true);
1570
+ await afterChange(true, afterChangeOptions);
1447
1571
  if (session.failed) return false;
1448
1572
 
1449
1573
  ctx.ui.notify(savedMessage, "info");
@@ -1456,11 +1580,7 @@ async function finalizeSettingsSession(
1456
1580
  return true;
1457
1581
  }
1458
1582
 
1459
- async function finalizeSessionSettings(
1460
- session: SettingsSessionResult,
1461
- ctx: any,
1462
- afterChange: (changed: boolean) => Promise<void> | void,
1463
- ) {
1583
+ async function finalizeSessionSettings(session: SettingsSessionResult, ctx: any, afterChange: AfterChange) {
1464
1584
  if (!session.changed) return false;
1465
1585
 
1466
1586
  await afterChange(true);
@@ -1480,35 +1600,12 @@ async function finalizeSessionSettings(
1480
1600
  return true;
1481
1601
  }
1482
1602
 
1483
- function actionIsMutation(action: string): boolean {
1484
- return action === "" || action === "settings" || ["enable", "on", "true", "disable", "off", "false"].includes(action);
1485
- }
1486
-
1487
- async function maybeBlockLayerMutation(action: string, ctx: any, layerName: string) {
1488
- if (!actionIsMutation(action)) return false;
1489
- const state = await readCapabilityState(ctx.cwd);
1490
- if (!state.configured) {
1491
- ctx.ui.notify(
1492
- `Freeflow is installed but this repo is not set up. Run /setup-freeflow before configuring ${layerName}.`,
1493
- "warning",
1494
- );
1495
- return true;
1496
- }
1497
- if (!state.enabled) {
1498
- ctx.ui.notify(
1499
- `Freeflow is disabled for this repo. Use /freeflow enable before configuring ${layerName}.`,
1500
- "warning",
1501
- );
1502
- return true;
1503
- }
1504
- return false;
1505
- }
1506
-
1507
1603
  export async function handleFreeflowCommand(
1508
1604
  args: string | undefined,
1509
1605
  ctx: any,
1510
- afterChange: (changed: boolean) => Promise<void> | void,
1606
+ afterChange: AfterChange,
1511
1607
  pi: any,
1608
+ cognitiveRoutingController?: CognitiveRoutingSettingsController,
1512
1609
  ) {
1513
1610
  const input = (args ?? "settings").trim().toLowerCase() || "settings";
1514
1611
  const [action, ...rest] = input.split(/\s+/);
@@ -1526,19 +1623,19 @@ export async function handleFreeflowCommand(
1526
1623
 
1527
1624
  const [layers, state, modeState] = await Promise.all([
1528
1625
  readFreeflowConfigLayers(ctx.cwd),
1529
- readCapabilityState(ctx.cwd),
1626
+ readCapabilityState(ctx.cwd, ctx, pi?.host),
1530
1627
  readModeState(ctx.cwd),
1531
1628
  ]);
1532
1629
  const configState = layers.repository;
1533
1630
  const raw = configState.valid ? configState.parsed : {};
1534
1631
 
1535
1632
  if (action === "status") {
1536
- ctx.ui.notify(freeflowStatusText(state), "info");
1633
+ ctx.ui.notify(freeflowStatusText(state, cognitiveRoutingController), "info");
1537
1634
  return { changed: false, reloaded: false };
1538
1635
  }
1539
1636
 
1540
1637
  if (!configState.valid) {
1541
- ctx.ui.notify(freeflowStatusText(state), "warning");
1638
+ ctx.ui.notify(freeflowStatusText(state, cognitiveRoutingController), "warning");
1542
1639
  return { changed: false, reloaded: false, error: "not_configured" };
1543
1640
  }
1544
1641
  if (layers.local.exists && !layers.local.valid) {
@@ -1550,6 +1647,7 @@ export async function handleFreeflowCommand(
1550
1647
  }
1551
1648
 
1552
1649
  if (action === "mode") {
1650
+ if (!ensureSettingsIdle(ctx)) return { changed: false, reloaded: false, error: "busy" };
1553
1651
  if (actionValue) {
1554
1652
  const result = await handleModeCommand(actionValue, ctx, pi);
1555
1653
  return { changed: result.changed, reloaded: false, error: result.error };
@@ -1566,6 +1664,9 @@ export async function handleFreeflowCommand(
1566
1664
  const item = freeflowItems(raw, modeState, {
1567
1665
  scope: "local",
1568
1666
  layers,
1667
+ cognitiveRouting: state.cognitiveRouting as CognitiveRoutingCapabilityState,
1668
+ ctx,
1669
+ hostInfo: pi?.host,
1569
1670
  }).find((candidate) => candidate.id === "freeflow.sessionMode")!;
1570
1671
  const session = await openSettings({
1571
1672
  title: "Freeflow Mode",
@@ -1581,10 +1682,14 @@ export async function handleFreeflowCommand(
1581
1682
  }
1582
1683
 
1583
1684
  if (["enable", "on", "true", "disable", "off", "false"].includes(action)) {
1685
+ if (!ensureSettingsIdle(ctx)) return { changed: false, reloaded: false, error: "busy" };
1584
1686
  const enabled = ["enable", "on", "true"].includes(action);
1585
1687
  const item = freeflowItems(raw, modeState, {
1586
1688
  scope: "repository",
1587
1689
  layers,
1690
+ cognitiveRouting: state.cognitiveRouting as CognitiveRoutingCapabilityState,
1691
+ ctx,
1692
+ hostInfo: pi?.host,
1588
1693
  }).find((candidate) => candidate.id === "freeflow.enabled")!;
1589
1694
  await updateConfig(ctx.cwd, item, enabled, "repository");
1590
1695
  await afterChange(true);
@@ -1618,10 +1723,19 @@ export async function handleFreeflowCommand(
1618
1723
  return { changed: false, reloaded: false, error: "invalid_scope" };
1619
1724
  }
1620
1725
 
1726
+ if (!ensureSettingsIdle(ctx)) return { changed: false, reloaded: false, error: "busy" };
1727
+
1728
+ let reconcileCognitiveRouting = settingsScope === "session";
1621
1729
  const items =
1622
1730
  settingsScope === "session"
1623
- ? sessionFreeflowItems(state, modeState)
1624
- : freeflowItems(raw, modeState, { scope: settingsScope, layers });
1731
+ ? sessionFreeflowItems(state, modeState, cognitiveRoutingController)
1732
+ : freeflowItems(raw, modeState, {
1733
+ scope: settingsScope,
1734
+ layers,
1735
+ cognitiveRouting: state.cognitiveRouting as CognitiveRoutingCapabilityState,
1736
+ ctx,
1737
+ hostInfo: pi?.host,
1738
+ });
1625
1739
  const session = await openSettings({
1626
1740
  title:
1627
1741
  settingsScope === "session"
@@ -1632,10 +1746,34 @@ export async function handleFreeflowCommand(
1632
1746
  items,
1633
1747
  ctx,
1634
1748
  onChange: async (item, value) => {
1749
+ if (!ensureSettingsIdle(ctx)) return { changed: false, reloadRequired: false };
1750
+ if (!isPiFlowHost(pi?.host) && item.id.startsWith("freeflow.cognitiveRouting.")) {
1751
+ ctx.ui.notify("Cognitive Routing is available only in PiFlow.", "warning");
1752
+ return { changed: false, reloadRequired: false };
1753
+ }
1635
1754
  if (item.id === "freeflow.sessionMode") {
1636
1755
  const result = await handleModeCommand(String(value), ctx, pi);
1637
1756
  return { changed: result.changed, reloadRequired: false };
1638
1757
  }
1758
+ if (item.id === "freeflow.cognitiveRouting.profile") {
1759
+ if (!cognitiveRoutingController) {
1760
+ ctx.ui.notify("Cognitive Routing is unavailable for this session.", "warning");
1761
+ return { changed: false, reloadRequired: false };
1762
+ }
1763
+ const result =
1764
+ value === "auto"
1765
+ ? await cognitiveRoutingController.setAutomaticControl("profile-settings")
1766
+ : await cognitiveRoutingController.setManualProfile(value as "standard" | "reasoning", "profile-settings");
1767
+ if ((result.status !== "automatic" && result.status !== "active") || result.reason) {
1768
+ ctx.ui.notify(
1769
+ `Cognitive Routing settings could not be applied: ${result.reason ?? result.status}.`,
1770
+ "warning",
1771
+ );
1772
+ return { changed: false, reloadRequired: false };
1773
+ }
1774
+ await afterChange(false);
1775
+ return { changed: true, reloadRequired: false };
1776
+ }
1639
1777
  if (item.id === "freeflow.session.reset") {
1640
1778
  const result = await resetSessionOverrides(ctx, pi);
1641
1779
  return { changed: result.changed, reloadRequired: result.reloadRequired };
@@ -1645,6 +1783,8 @@ export async function handleFreeflowCommand(
1645
1783
  "freeflow.enabled": "enabled",
1646
1784
  "freeflow.interactionContract": "interactionContract",
1647
1785
  "freeflow.skills.enabled": "skillsEnabled",
1786
+ "freeflow.contextVirtualization": "contextVirtualization",
1787
+ "freeflow.conversationHistory": "conversationHistory",
1648
1788
  } as const;
1649
1789
  const key = keyById[item.id as keyof typeof keyById];
1650
1790
  const override = value === LOCAL_INHERIT ? null : value === "true";
@@ -1652,6 +1792,9 @@ export async function handleFreeflowCommand(
1652
1792
  return { changed: result.changed, reloadRequired: result.reloadRequired === true };
1653
1793
  }
1654
1794
  await updateConfig(ctx.cwd, item, value, item.configScope ?? "repository");
1795
+ if (!item.id.startsWith("freeflow.cognitiveRouting.sessionStart.")) {
1796
+ reconcileCognitiveRouting = true;
1797
+ }
1655
1798
  return { changed: true, reloadRequired: true };
1656
1799
  },
1657
1800
  });
@@ -1667,64 +1810,8 @@ export async function handleFreeflowCommand(
1667
1810
  ctx,
1668
1811
  afterChange,
1669
1812
  `Freeflow ${savedTarget} saved. Reloading Freeflow runtime...`,
1670
- );
1671
- return { changed: session.changed, reloaded, error: session.failed ? "write_failed" : undefined };
1672
- }
1673
-
1674
- export async function handleOutputRouterCommand(
1675
- args: string | undefined,
1676
- ctx: any,
1677
- afterChange: (changed: boolean) => Promise<void> | void,
1678
- ) {
1679
- const action = (args ?? "").trim().toLowerCase();
1680
- const raw = await readFreeflowConfig(ctx.cwd);
1681
-
1682
- if (await maybeBlockLayerMutation(action, ctx, "Output Router")) {
1683
- return { changed: false, reloaded: false, error: "freeflow_inactive" };
1684
- }
1685
-
1686
- if (action === "status") {
1687
- const state = await readCapabilityState(ctx.cwd);
1688
- const prefix = !state.configured || !state.enabled ? `${freeflowStatusText(state)}; ` : "";
1689
- ctx.ui.notify(`${prefix}${outputRouterStatusText(raw)}`, !state.configured || !state.enabled ? "warning" : "info");
1690
- return { changed: false, reloaded: false };
1691
- }
1692
-
1693
- if (["enable", "on", "true", "disable", "off", "false"].includes(action)) {
1694
- const enabled = ["enable", "on", "true"].includes(action);
1695
- const item = outputRouterItems(raw).find((candidate) => candidate.id === "outputRouter.enabled")!;
1696
- await updateConfig(ctx.cwd, item, enabled);
1697
- await afterChange(true);
1698
- ctx.ui.notify(`Output Router ${enabled ? "enabled" : "disabled"}. Reloading Freeflow runtime...`, "info");
1699
- if (typeof ctx.reload === "function") {
1700
- await ctx.reload();
1701
- return { changed: true, reloaded: true };
1702
- }
1703
- ctx.ui.notify("Run /reload for Output Router changes to fully apply.", "warning");
1704
- return { changed: true, reloaded: false };
1705
- }
1706
-
1707
- if (action && action !== "settings") {
1708
- ctx.ui.notify("Usage: /output-router, /output-router settings, or /output-router status", "warning");
1709
- return { changed: false, reloaded: false, error: "invalid_action" };
1710
- }
1711
-
1712
- const session = await openSettings({
1713
- title: "Output Router Settings",
1714
- items: outputRouterItems(raw),
1715
- ctx,
1716
- onChange: async (item, value) => {
1717
- await updateConfig(ctx.cwd, item, value);
1718
- return { changed: true, reloadRequired: true };
1719
- },
1720
- });
1721
-
1722
- const reloaded = await finalizeSettingsSession(
1723
- session,
1724
- ctx,
1725
- afterChange,
1726
- "Output Router settings saved. Reloading Freeflow runtime...",
1727
- "Run /reload for Output Router changes to fully apply.",
1813
+ "Run /reload for Freeflow changes to fully apply.",
1814
+ { reconcileCognitiveRouting },
1728
1815
  );
1729
1816
  return { changed: session.changed, reloaded, error: session.failed ? "write_failed" : undefined };
1730
1817
  }