@hassangameryt/freeflow 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/.codex-plugin/plugin.json +4 -5
  4. package/README.md +141 -163
  5. package/capabilities/cognitive-routing/SKILL.md +387 -0
  6. package/capabilities/context-virtualization/SKILL.md +103 -0
  7. package/capabilities/conversation-history/SKILL.md +53 -0
  8. package/command-surface.json +6 -37
  9. package/hooks/freeflow-runtime-context.mjs +50 -541
  10. package/hooks/hooks.json +1 -26
  11. package/hooks/tests/check-runtime-context-hook.sh +114 -308
  12. package/package.json +27 -17
  13. package/pi-extension/dist/cognitive-routing/commands.js +96 -0
  14. package/pi-extension/dist/cognitive-routing/config.js +232 -0
  15. package/pi-extension/dist/cognitive-routing/controller.js +1292 -0
  16. package/pi-extension/dist/cognitive-routing/history.js +451 -0
  17. package/pi-extension/dist/cognitive-routing/host.js +188 -0
  18. package/pi-extension/dist/cognitive-routing/pi-routing-session.js +414 -0
  19. package/pi-extension/dist/cognitive-routing/pi-session-control.js +320 -0
  20. package/pi-extension/dist/cognitive-routing/runtime.js +43 -0
  21. package/pi-extension/dist/cognitive-routing/tool.js +212 -0
  22. package/pi-extension/dist/cognitive-routing/types.js +6 -0
  23. package/pi-extension/dist/context-virtualization/commands.js +89 -0
  24. package/pi-extension/dist/context-virtualization/projector.js +42 -0
  25. package/pi-extension/dist/context-virtualization/runtime.js +356 -0
  26. package/pi-extension/dist/context-virtualization/state.js +57 -0
  27. package/pi-extension/dist/context-virtualization/types.js +10 -0
  28. package/pi-extension/dist/conversation-history/passages.js +66 -0
  29. package/pi-extension/dist/conversation-history/ranking.js +105 -0
  30. package/pi-extension/dist/conversation-history/runtime.js +226 -0
  31. package/pi-extension/dist/conversation-history/search.js +56 -0
  32. package/pi-extension/dist/conversation-history/types.js +1 -0
  33. package/pi-extension/dist/freeflow-context/resolver.js +120 -0
  34. package/pi-extension/dist/freeflow-context/runtime.js +80 -0
  35. package/pi-extension/dist/freeflow-context/source-projector.js +95 -0
  36. package/pi-extension/dist/freeflow-context/tool.js +308 -0
  37. package/pi-extension/dist/freeflow-context/types.js +12 -0
  38. package/pi-extension/dist/freeflow-context/visibility.js +28 -0
  39. package/pi-extension/dist/index.js +564 -129
  40. package/pi-extension/dist/runtime/runtime-context.js +357 -470
  41. package/pi-extension/dist/runtime/runtime-identity.js +5 -0
  42. package/pi-extension/dist/settings/settings-tui.js +78 -0
  43. package/pi-extension/dist/settings/settings-ui.js +572 -826
  44. package/pi-extension/dist/ui/text-component.js +57 -0
  45. package/pi-extension/src/cognitive-routing/commands.ts +129 -0
  46. package/pi-extension/src/cognitive-routing/config.ts +303 -0
  47. package/pi-extension/src/cognitive-routing/controller.ts +1628 -0
  48. package/pi-extension/src/cognitive-routing/history.ts +650 -0
  49. package/pi-extension/src/cognitive-routing/host.ts +266 -0
  50. package/pi-extension/src/cognitive-routing/pi-routing-session.ts +538 -0
  51. package/pi-extension/src/cognitive-routing/pi-session-control.ts +444 -0
  52. package/pi-extension/src/cognitive-routing/runtime.ts +57 -0
  53. package/pi-extension/src/cognitive-routing/tool.ts +273 -0
  54. package/pi-extension/src/cognitive-routing/types.ts +89 -0
  55. package/pi-extension/src/context-virtualization/commands.ts +111 -0
  56. package/pi-extension/src/context-virtualization/projector.ts +62 -0
  57. package/pi-extension/src/context-virtualization/runtime.ts +406 -0
  58. package/pi-extension/src/context-virtualization/state.ts +79 -0
  59. package/pi-extension/src/context-virtualization/types.ts +92 -0
  60. package/pi-extension/src/conversation-history/passages.ts +81 -0
  61. package/pi-extension/src/conversation-history/ranking.ts +155 -0
  62. package/pi-extension/src/conversation-history/runtime.ts +250 -0
  63. package/pi-extension/src/conversation-history/search.ts +79 -0
  64. package/pi-extension/src/conversation-history/types.ts +31 -0
  65. package/pi-extension/src/freeflow-context/resolver.ts +139 -0
  66. package/pi-extension/src/freeflow-context/runtime.ts +100 -0
  67. package/pi-extension/src/freeflow-context/source-projector.ts +106 -0
  68. package/pi-extension/src/freeflow-context/tool.ts +360 -0
  69. package/pi-extension/src/freeflow-context/types.ts +70 -0
  70. package/pi-extension/src/freeflow-context/visibility.ts +53 -0
  71. package/pi-extension/src/index.ts +597 -135
  72. package/pi-extension/src/runtime/runtime-context.ts +373 -491
  73. package/pi-extension/src/runtime/runtime-identity.ts +8 -0
  74. package/pi-extension/src/settings/settings-tui.ts +93 -0
  75. package/pi-extension/src/settings/settings-ui.ts +679 -910
  76. package/pi-extension/src/ui/text-component.ts +66 -0
  77. package/pi-extension/tests/cognitive-routing/commands.test.js +134 -0
  78. package/pi-extension/tests/cognitive-routing/controller.test.js +1128 -0
  79. package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +1142 -0
  80. package/pi-extension/tests/cognitive-routing/history.test.js +274 -0
  81. package/pi-extension/tests/cognitive-routing/host-fixture.js +4 -0
  82. package/pi-extension/tests/cognitive-routing/host-identity.test.js +63 -0
  83. package/pi-extension/tests/cognitive-routing/pi-normal-lifecycle.test.js +434 -0
  84. package/pi-extension/tests/cognitive-routing/pi-session-control.test.js +174 -0
  85. package/pi-extension/tests/cognitive-routing/pi-session-history.test.js +91 -0
  86. package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +279 -0
  87. package/pi-extension/tests/cognitive-routing/tool.test.js +230 -0
  88. package/pi-extension/tests/context-virtualization/extension.test.js +749 -0
  89. package/pi-extension/tests/context-virtualization/projector.test.js +110 -0
  90. package/pi-extension/tests/context-virtualization/runtime.test.js +216 -0
  91. package/pi-extension/tests/conversation-history/runtime.test.js +98 -0
  92. package/pi-extension/tests/conversation-history/search.test.js +97 -0
  93. package/pi-extension/tests/integration/mode-free-core.test.js +218 -0
  94. package/pi-extension/tests/integration/pi-extension.test.js +532 -3701
  95. package/pi-extension/tests/integration/prompt-architecture.test.js +350 -0
  96. package/pi-extension/tests/integration/unaffected-regressions.test.js +411 -0
  97. package/runtime/prompts/cognitive-routing.md +10 -0
  98. package/runtime/prompts/context-virtualization.md +3 -0
  99. package/runtime/prompts/conversation-history.md +3 -0
  100. package/runtime/prompts/core.md +84 -0
  101. package/runtime/prompts/interaction-contract.md +11 -0
  102. package/skills/action-selection/SKILL.md +140 -0
  103. package/skills/action-selection/references/trajectory-stalls.md +71 -0
  104. package/skills/bypass/SKILL.md +109 -38
  105. package/skills/commit-work/SKILL.md +94 -55
  106. package/skills/commit-work/references/git-state-edges.md +94 -0
  107. package/skills/commit-work/references/simple-push.md +92 -0
  108. package/skills/commit-work/references/staging-decisions.md +79 -46
  109. package/skills/decision-gate/SKILL.md +80 -22
  110. package/skills/design-for-depth/SKILL.md +206 -60
  111. package/skills/design-for-depth/references/design-pressure-signals.md +108 -240
  112. package/skills/design-for-depth/references/interface-design-loop.md +77 -97
  113. package/skills/design-for-depth/references/module-and-dependency-design.md +147 -0
  114. package/skills/design-for-depth/references/software-design-philosophy.md +128 -174
  115. package/skills/design-for-depth/references/state-and-failure-boundaries.md +136 -0
  116. package/skills/diagnose-failure/SKILL.md +200 -48
  117. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +18 -6
  118. package/skills/diagnose-failure/references/{flaky-and-performance.md → flaky-and-performance-diagnosis.md} +13 -5
  119. package/skills/discuss/SKILL.md +119 -65
  120. package/skills/discuss/references/discussion-continuity.md +123 -0
  121. package/skills/evaluate-skill/SKILL.md +166 -52
  122. package/skills/evaluate-skill/references/{definition-schema.md → evaluation-definition-schema.md} +43 -3
  123. package/skills/evaluate-skill/references/evaluation-design.md +56 -30
  124. package/skills/evaluate-skill/references/execution-and-evidence.md +8 -3
  125. package/skills/evaluate-skill/references/review-and-revision.md +62 -21
  126. package/skills/evaluate-skill/scripts/lib/definitions.mjs +114 -3
  127. package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +156 -1
  128. package/skills/evaluate-skill/scripts/lib/grade.mjs +9 -1
  129. package/skills/evaluate-skill/scripts/lib/pi.mjs +147 -32
  130. package/skills/evaluate-skill/scripts/lib/rpc.mjs +4 -4
  131. package/skills/evaluate-skill/scripts/lib/runner.mjs +36 -14
  132. package/skills/evaluate-skill/scripts/lib/sandbox.mjs +92 -0
  133. package/skills/evaluate-skill/scripts/pi-guard.mjs +1 -1
  134. package/skills/evaluate-skill/scripts/pi-observer.mjs +66 -0
  135. package/skills/evaluate-skill/scripts/skill-eval.mjs +6 -5
  136. package/skills/execute-work/SKILL.md +99 -57
  137. package/skills/execute-work/references/code-practices.md +4 -4
  138. package/skills/execute-work/references/execute-work-edges.md +62 -0
  139. package/skills/finish-branch/SKILL.md +2 -2
  140. package/skills/finish-branch/references/integration-options.md +1 -1
  141. package/skills/handoff/SKILL.md +3 -3
  142. package/skills/launch-work/SKILL.md +1 -1
  143. package/skills/launch-work/references/launch-readiness.md +2 -2
  144. package/skills/migration-work/SKILL.md +2 -2
  145. package/skills/migration-work/references/migration-lifecycle.md +1 -1
  146. package/skills/release-work/SKILL.md +1 -1
  147. package/skills/release-work/references/release-evidence.md +1 -1
  148. package/skills/review-artifact/SKILL.md +129 -66
  149. package/skills/review-artifact/references/adjudicate-artifact-review.md +101 -0
  150. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +159 -0
  151. package/skills/review-work/SKILL.md +113 -68
  152. package/skills/review-work/references/adjudicate-work-review.md +97 -0
  153. package/skills/review-work/references/independent-work-reviewer-contract.md +139 -0
  154. package/skills/review-work/references/security-risk-lens.md +18 -16
  155. package/skills/setup-freeflow/SKILL.md +16 -17
  156. package/skills/setup-freeflow/references/activation-contract.md +24 -30
  157. package/skills/setup-freeflow/references/host-setup.md +6 -7
  158. package/skills/simplify-code/SKILL.md +97 -39
  159. package/skills/simplify-code/references/simplification-patterns.md +1 -1
  160. package/skills/tdd/SKILL.md +100 -53
  161. package/skills/tdd/references/test-design.md +3 -3
  162. package/skills/track-work/SKILL.md +195 -58
  163. package/skills/track-work/references/working-record-format.md +515 -0
  164. package/skills/track-work/scripts/lib/document.mjs +713 -0
  165. package/skills/track-work/scripts/lib/format.mjs +166 -0
  166. package/skills/track-work/scripts/lib/store.mjs +233 -0
  167. package/skills/track-work/scripts/lib/transitions.mjs +790 -0
  168. package/skills/track-work/scripts/lib/views.mjs +40 -0
  169. package/skills/track-work/scripts/working-record.mjs +316 -0
  170. package/skills/verify-work/SKILL.md +57 -19
  171. package/skills/verify-work/references/browser-runtime-evidence.md +2 -2
  172. package/skills/verify-work/references/integration-evidence.md +1 -7
  173. package/skills/verify-work/references/performance-evidence.md +2 -2
  174. package/skills/workflow/SKILL.md +137 -55
  175. package/skills/workflow/references/domain-skill-composition.md +26 -40
  176. package/skills/write-plan/SKILL.md +1 -1
  177. package/skills/write-skill/SKILL.md +218 -61
  178. package/skills/write-skill/references/activation-boundaries.md +63 -21
  179. package/skills/write-skill/references/agent-first-instructions.md +61 -27
  180. package/skills/write-skill/references/progressive-disclosure.md +97 -33
  181. package/skills/write-skill/references/skill-dependency-graphs.md +190 -0
  182. package/capabilities/output-router/SKILL.md +0 -206
  183. package/capabilities/output-router/references/safety-policy.md +0 -40
  184. package/capabilities/output-router/references/setup.md +0 -110
  185. package/pi-extension/dist/output-router/host-producer-identification.js +0 -232
  186. package/pi-extension/dist/output-router/native-safety-net.js +0 -216
  187. package/pi-extension/dist/output-router/observed-tool-routing.js +0 -122
  188. package/pi-extension/dist/output-router/renderers.js +0 -1023
  189. package/pi-extension/dist/output-router/router-tools.js +0 -554
  190. package/pi-extension/dist/output-router/schemas.js +0 -383
  191. package/pi-extension/dist/output-router/status.js +0 -744
  192. package/pi-extension/dist/output-router/utils.js +0 -502
  193. package/pi-extension/src/output-router/host-producer-identification.ts +0 -256
  194. package/pi-extension/src/output-router/native-safety-net.ts +0 -248
  195. package/pi-extension/src/output-router/observed-tool-routing.ts +0 -134
  196. package/pi-extension/src/output-router/renderers.ts +0 -1108
  197. package/pi-extension/src/output-router/router-tools.ts +0 -579
  198. package/pi-extension/src/output-router/schemas.ts +0 -387
  199. package/pi-extension/src/output-router/status.ts +0 -791
  200. package/pi-extension/src/output-router/utils.ts +0 -554
  201. package/pi-extension/tests/output-router/observed-producers.test.js +0 -132
  202. package/pi-extension/tests/output-router/search.test.js +0 -722
  203. package/router/dist/benchmarks/benchmark-harness.d.ts +0 -35
  204. package/router/dist/benchmarks/benchmark-harness.js +0 -92
  205. package/router/dist/benchmarks/benchmarks.d.ts +0 -109
  206. package/router/dist/benchmarks/benchmarks.js +0 -979
  207. package/router/dist/benchmarks/codex-qa-benchmarks.d.ts +0 -95
  208. package/router/dist/benchmarks/codex-qa-benchmarks.js +0 -500
  209. package/router/dist/benchmarks/command-benchmarks.d.ts +0 -143
  210. package/router/dist/benchmarks/command-benchmarks.js +0 -698
  211. package/router/dist/benchmarks/context-mode-normalized-benchmarks.d.ts +0 -82
  212. package/router/dist/benchmarks/context-mode-normalized-benchmarks.js +0 -773
  213. package/router/dist/benchmarks/context-mode-real-deep-benchmark.d.ts +0 -132
  214. package/router/dist/benchmarks/context-mode-real-deep-benchmark.js +0 -1380
  215. package/router/dist/benchmarks/context-saving-baseline.d.ts +0 -60
  216. package/router/dist/benchmarks/context-saving-baseline.js +0 -470
  217. package/router/dist/benchmarks/index-benchmarks.d.ts +0 -103
  218. package/router/dist/benchmarks/index-benchmarks.js +0 -862
  219. package/router/dist/benchmarks/storage-policy-benchmarks.d.ts +0 -101
  220. package/router/dist/benchmarks/storage-policy-benchmarks.js +0 -618
  221. package/router/dist/benchmarks/vault-index-benchmarks.d.ts +0 -56
  222. package/router/dist/benchmarks/vault-index-benchmarks.js +0 -322
  223. package/router/dist/config/config.d.ts +0 -103
  224. package/router/dist/config/config.js +0 -632
  225. package/router/dist/config/router-contract.d.ts +0 -10
  226. package/router/dist/config/router-contract.js +0 -54
  227. package/router/dist/config/schema.d.ts +0 -27
  228. package/router/dist/config/schema.js +0 -648
  229. package/router/dist/config/types.d.ts +0 -573
  230. package/router/dist/config/types.js +0 -41
  231. package/router/dist/evidence/bounded-evidence.d.ts +0 -40
  232. package/router/dist/evidence/bounded-evidence.js +0 -323
  233. package/router/dist/evidence/evidence-range-selector.d.ts +0 -24
  234. package/router/dist/evidence/evidence-range-selector.js +0 -232
  235. package/router/dist/evidence/evidence-search.d.ts +0 -27
  236. package/router/dist/evidence/evidence-search.js +0 -490
  237. package/router/dist/evidence/evidence.d.ts +0 -38
  238. package/router/dist/evidence/evidence.js +0 -200
  239. package/router/dist/evidence/failure-contracts.d.ts +0 -37
  240. package/router/dist/evidence/failure-contracts.js +0 -166
  241. package/router/dist/evidence/line-ranges.d.ts +0 -24
  242. package/router/dist/evidence/line-ranges.js +0 -22
  243. package/router/dist/experiments/local-index.d.ts +0 -64
  244. package/router/dist/experiments/local-index.js +0 -581
  245. package/router/dist/index.d.ts +0 -30
  246. package/router/dist/index.js +0 -32
  247. package/router/dist/local/local-traversal.d.ts +0 -18
  248. package/router/dist/local/local-traversal.js +0 -334
  249. package/router/dist/processing/engine.d.ts +0 -132
  250. package/router/dist/processing/engine.js +0 -533
  251. package/router/dist/processing/reducers.d.ts +0 -280
  252. package/router/dist/processing/reducers.js +0 -1673
  253. package/router/dist/processing/renderers.d.ts +0 -31
  254. package/router/dist/processing/renderers.js +0 -195
  255. package/router/dist/processing/scripts.d.ts +0 -91
  256. package/router/dist/processing/scripts.js +0 -386
  257. package/router/dist/repo/repo-traversal.d.ts +0 -18
  258. package/router/dist/repo/repo-traversal.js +0 -245
  259. package/router/dist/routing/observed-reducers.d.ts +0 -41
  260. package/router/dist/routing/observed-reducers.js +0 -661
  261. package/router/dist/routing/observed-routing.d.ts +0 -26
  262. package/router/dist/routing/observed-routing.js +0 -378
  263. package/router/dist/routing/parsers.d.ts +0 -16
  264. package/router/dist/routing/parsers.js +0 -521
  265. package/router/dist/routing/run-filters.d.ts +0 -60
  266. package/router/dist/routing/run-filters.js +0 -238
  267. package/router/dist/routing/run-reducers.d.ts +0 -49
  268. package/router/dist/routing/run-reducers.js +0 -122
  269. package/router/dist/sandbox/adapter-roots.d.ts +0 -46
  270. package/router/dist/sandbox/adapter-roots.js +0 -66
  271. package/router/dist/sandbox/eryx-python-adapter.d.ts +0 -13
  272. package/router/dist/sandbox/eryx-python-adapter.js +0 -868
  273. package/router/dist/sandbox/jq-wasm-adapter.d.ts +0 -9
  274. package/router/dist/sandbox/jq-wasm-adapter.js +0 -429
  275. package/router/dist/sandbox/quickjs-wasi-adapter.d.ts +0 -13
  276. package/router/dist/sandbox/quickjs-wasi-adapter.js +0 -384
  277. package/router/dist/sandbox/script-sandbox.d.ts +0 -134
  278. package/router/dist/sandbox/script-sandbox.js +0 -337
  279. package/router/dist/setup/script-transform-adapters.d.ts +0 -32
  280. package/router/dist/setup/script-transform-adapters.js +0 -278
  281. package/router/dist/tools/batch.d.ts +0 -33
  282. package/router/dist/tools/batch.js +0 -586
  283. package/router/dist/tools/run.d.ts +0 -62
  284. package/router/dist/tools/run.js +0 -1304
  285. package/router/dist/tools/search.d.ts +0 -66
  286. package/router/dist/tools/search.js +0 -1364
  287. package/router/dist/transform/engine.d.ts +0 -168
  288. package/router/dist/transform/engine.js +0 -2072
  289. package/router/dist/vault/vault-index.d.ts +0 -125
  290. package/router/dist/vault/vault-index.js +0 -416
  291. package/router/dist/vault/vault.d.ts +0 -138
  292. package/router/dist/vault/vault.js +0 -630
  293. package/runtime/interaction-contract.md +0 -16
  294. package/skills/discuss/references/checkpoints.md +0 -66
  295. package/skills/execute-work/references/execution-loop.md +0 -77
  296. package/skills/mode-contract/SKILL.md +0 -104
  297. package/skills/review-artifact/references/reviewer-prompt.md +0 -148
  298. package/skills/review-work/references/reviewer-prompt.md +0 -134
  299. package/skills/track-work/references/working-record-schema.md +0 -378
  300. package/skills/workflow/references/workflow-loop.md +0 -92
  301. /package/skills/handoff/references/{templates.md → handoff-templates.md} +0 -0
@@ -2,19 +2,17 @@ import { readFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
 
5
- import { normalizeFreeflowConfig, normalizeLocalFreeflowConfig } from "../../../router/dist/index.js";
6
-
7
- export const VALID_MODES = new Set(["conversation", "workflow", "strict-workflow"]);
5
+ import { resolveCognitiveRoutingState } from "../cognitive-routing/runtime.js";
6
+ import { supportsCognitiveRoutingModelRegistry } from "../cognitive-routing/host.js";
8
7
 
9
8
  export const WORKFLOW_COMMANDS = [
10
9
  { command: "discuss", skill: "discuss" },
11
- { command: "discover", skill: "discuss" },
10
+ { command: "action-selection", skill: "action-selection" },
12
11
  { command: "track-work", skill: "track-work" },
13
12
  { command: "write-spec", skill: "write-spec" },
14
13
  { command: "review-artifact", skill: "review-artifact" },
15
14
  { command: "write-plan", skill: "write-plan" },
16
15
  { command: "execute-work", skill: "execute-work" },
17
- { command: "execute-plan", skill: "execute-work" },
18
16
  { command: "simplify-code", skill: "simplify-code" },
19
17
  { command: "migration-work", skill: "migration-work" },
20
18
  { command: "diagnose-failure", skill: "diagnose-failure" },
@@ -31,6 +29,7 @@ export const WORKFLOW_COMMANDS = [
31
29
  export const CONTRIBUTOR_COMMANDS = ["setup-freeflow", "write-skill", "evaluate-skill"];
32
30
 
33
31
  export const FREEFLOW_MODEL_SKILL_NAMES = [
32
+ "action-selection",
34
33
  "bypass",
35
34
  "commit-work",
36
35
  "decision-gate",
@@ -42,7 +41,6 @@ export const FREEFLOW_MODEL_SKILL_NAMES = [
42
41
  "execute-work",
43
42
  "finish-branch",
44
43
  "handoff",
45
- "mode-contract",
46
44
  "release-work",
47
45
  "review-artifact",
48
46
  "review-work",
@@ -58,63 +56,114 @@ export const FREEFLOW_MODEL_SKILL_NAMES = [
58
56
  "write-spec",
59
57
  ];
60
58
 
59
+ export const FREEFLOW_CAPABILITY_SKILL_NAMES = ["cognitive-routing", "context-virtualization", "conversation-history"];
60
+
61
61
  export function freeflowSkillPath(skillName) {
62
62
  return fileURLToPath(new URL(`../../../skills/${skillName}/SKILL.md`, import.meta.url));
63
63
  }
64
64
 
65
- export function freeflowModelSkillPaths() {
66
- return FREEFLOW_MODEL_SKILL_NAMES.map((skillName) => freeflowSkillPath(skillName));
65
+ export function freeflowCapabilitySkillPath(skillName) {
66
+ return fileURLToPath(new URL(`../../../capabilities/${skillName}/SKILL.md`, import.meta.url));
67
+ }
68
+
69
+ export function freeflowModelSkillPaths(capabilityState = undefined) {
70
+ const paths = FREEFLOW_MODEL_SKILL_NAMES.map((skillName) => freeflowSkillPath(skillName));
71
+ const capabilityStates = {
72
+ "cognitive-routing": capabilityState?.cognitiveRouting,
73
+ "context-virtualization": capabilityState?.contextVirtualization,
74
+ "conversation-history": capabilityState?.conversationHistory,
75
+ };
76
+ for (const skillName of FREEFLOW_CAPABILITY_SKILL_NAMES) {
77
+ if (capabilityStates[skillName]?.effective === true) {
78
+ paths.push(freeflowCapabilitySkillPath(skillName));
79
+ }
80
+ }
81
+ return paths;
67
82
  }
68
83
 
69
- type SessionCoreKey = "enabled" | "interactionContract" | "skillsEnabled";
84
+ type SessionCoreKey = "enabled" | "contextVirtualization" | "conversationHistory";
70
85
  type SessionCoreOverrides = Partial<Record<SessionCoreKey, boolean>>;
71
86
 
72
- const MODE_STATE_ENTRY = "freeflow-mode";
73
87
  const SESSION_OVERRIDES_ENTRY = "freeflow-session-overrides";
74
- const SESSION_CORE_KEYS = new Set<SessionCoreKey>(["enabled", "interactionContract", "skillsEnabled"]);
75
- const RESET_MODE_ARGS = new Set(["reset"]);
88
+ const SESSION_CORE_KEYS = new Set<SessionCoreKey>(["enabled", "contextVirtualization", "conversationHistory"]);
76
89
 
77
- export const FREEFLOW_STATUS_TOOL_NAME = "freeflow_status";
78
- export const OUTPUT_ROUTER_TOOL_NAMES = ["freeflow_search", "freeflow_run", "freeflow_batch"];
90
+ export const COGNITIVE_ROUTING_SWITCH_TOOL_NAME = "freeflow_switch_profile";
91
+ export const FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE = "freeflow-runtime-state";
92
+ export const COGNITIVE_ROUTING_RUNTIME_STATE_MESSAGE_TYPE = "freeflow-cognitive-routing-runtime-state";
79
93
  export const WORKFLOW_BOOTSTRAP_MESSAGE_TYPE = "freeflow-workflow-bootstrap";
94
+ export const COGNITIVE_ROUTING_BOOTSTRAP_MESSAGE_TYPE = "freeflow-cognitive-routing-bootstrap";
95
+ export const FREEFLOW_BOOTSTRAP_MESSAGE_TYPE = "freeflow-bootstrap";
80
96
 
81
97
  let runtimeContextCache = null;
82
- let currentModeOverride = null;
83
98
  let currentSessionOverrides: SessionCoreOverrides = {};
84
- let lastRouterConfigWarningKey = null;
99
+
100
+ export function isPromptAvailable(value: unknown): value is string {
101
+ return typeof value === "string" && value.trim().length > 0;
102
+ }
103
+
104
+ export function hasUsableMandatoryPrompts(freeflowContext: any): boolean {
105
+ return (
106
+ isPromptAvailable(freeflowContext?.corePrompt) && isPromptAvailable(freeflowContext?.interactionContractPrompt)
107
+ );
108
+ }
109
+
110
+ async function readPromptFile(url: URL): Promise<string | null> {
111
+ try {
112
+ const prompt = await readFile(url, "utf8");
113
+ return isPromptAvailable(prompt) ? prompt.trim() : null;
114
+ } catch {
115
+ return null;
116
+ }
117
+ }
118
+
85
119
  async function loadRuntimeContext(capabilityState = undefined) {
86
- const interactionContractEnabled = capabilityState?.interactionContract?.effective === true;
87
- const skillsEnabled = capabilityState?.skills?.effective === true;
88
- const outputRouterEnabled = capabilityState?.outputRouter?.enabled === true;
89
- const [interactionContract, workflowSkill, outputRouterSkill] = await Promise.all([
90
- interactionContractEnabled
91
- ? readFile(new URL("../../../runtime/interaction-contract.md", import.meta.url), "utf8")
120
+ const freeflowEnabled = capabilityState?.enabled === true;
121
+ const contextVirtualizationEnabled = capabilityState?.contextVirtualization?.effective === true;
122
+ const conversationHistoryEnabled = capabilityState?.conversationHistory?.effective === true;
123
+ const cognitiveRoutingEnabled = capabilityState?.cognitiveRouting?.effective === true;
124
+ const [
125
+ corePrompt,
126
+ interactionContractPrompt,
127
+ cognitiveRoutingPrompt,
128
+ contextVirtualizationPrompt,
129
+ conversationHistoryPrompt,
130
+ ] = await Promise.all([
131
+ freeflowEnabled
132
+ ? readPromptFile(new URL("../../../runtime/prompts/core.md", import.meta.url))
92
133
  : Promise.resolve(null),
93
- skillsEnabled
94
- ? readFile(new URL("../../../skills/workflow/SKILL.md", import.meta.url), "utf8")
134
+ freeflowEnabled
135
+ ? readPromptFile(new URL("../../../runtime/prompts/interaction-contract.md", import.meta.url))
95
136
  : Promise.resolve(null),
96
- outputRouterEnabled
97
- ? readFile(new URL("../../../capabilities/output-router/SKILL.md", import.meta.url), "utf8")
137
+ cognitiveRoutingEnabled
138
+ ? readPromptFile(new URL("../../../runtime/prompts/cognitive-routing.md", import.meta.url))
139
+ : Promise.resolve(null),
140
+ contextVirtualizationEnabled
141
+ ? readPromptFile(new URL("../../../runtime/prompts/context-virtualization.md", import.meta.url))
142
+ : Promise.resolve(null),
143
+ conversationHistoryEnabled
144
+ ? readPromptFile(new URL("../../../runtime/prompts/conversation-history.md", import.meta.url))
98
145
  : Promise.resolve(null),
99
146
  ]);
100
147
 
101
- return { interactionContract, workflowSkill, outputRouterSkill };
148
+ return {
149
+ corePrompt,
150
+ interactionContractPrompt,
151
+ cognitiveRoutingPrompt,
152
+ contextVirtualizationPrompt,
153
+ conversationHistoryPrompt,
154
+ };
102
155
  }
103
156
 
104
157
  function runtimeContextCacheSatisfies(capabilityState) {
105
- if (!runtimeContextCache) {
106
- return false;
107
- }
108
- if (capabilityState?.interactionContract?.effective === true && !runtimeContextCache.interactionContract) {
109
- return false;
110
- }
111
- if (capabilityState?.skills?.effective === true && !runtimeContextCache.workflowSkill) {
112
- return false;
113
- }
114
- if (capabilityState?.outputRouter?.enabled === true && !runtimeContextCache.outputRouterSkill) {
115
- return false;
116
- }
117
- return true;
158
+ if (!runtimeContextCache) return false;
159
+ const expected = {
160
+ corePrompt: capabilityState?.enabled === true,
161
+ interactionContractPrompt: capabilityState?.enabled === true,
162
+ cognitiveRoutingPrompt: capabilityState?.cognitiveRouting?.effective === true,
163
+ contextVirtualizationPrompt: capabilityState?.contextVirtualization?.effective === true,
164
+ conversationHistoryPrompt: capabilityState?.conversationHistory?.effective === true,
165
+ };
166
+ return Object.entries(expected).every(([key, required]) => !required || isPromptAvailable(runtimeContextCache[key]));
118
167
  }
119
168
 
120
169
  export async function refreshRuntimeContext(capabilityState = undefined) {
@@ -129,44 +178,34 @@ export async function getRuntimeContext(capabilityState = undefined) {
129
178
  return refreshRuntimeContext(capabilityState);
130
179
  }
131
180
 
132
- function activeSessionEntries(sessionManager) {
133
- try {
134
- if (typeof sessionManager?.buildContextEntries === "function") {
135
- const entries = sessionManager.buildContextEntries();
136
- if (Array.isArray(entries)) {
137
- return entries;
138
- }
139
- }
140
- if (typeof sessionManager?.getEntries === "function") {
141
- const entries = sessionManager.getEntries();
142
- if (Array.isArray(entries)) {
143
- return entries;
144
- }
145
- }
146
- } catch {
147
- // A missing session snapshot should reload useful context rather than suppress it.
148
- }
149
- return [];
150
- }
181
+ const BOOTSTRAP_COMPONENTS = Object.freeze({
182
+ workflow: "workflow",
183
+ cognitiveRouting: "cognitive-routing",
184
+ });
151
185
 
152
- export function workflowBootstrapMessage(freeflowContext, capabilityState, sessionManager) {
153
- if (capabilityState?.skills?.effective !== true || !freeflowContext?.workflowSkill) {
154
- return undefined;
186
+ function bootstrapEntryComponents(entry) {
187
+ const isCustomMessage = entry?.role === "custom" || entry?.type === "custom_message" || entry?.type === "custom";
188
+ if (!isCustomMessage) return [];
189
+ if (entry.customType === FREEFLOW_BOOTSTRAP_MESSAGE_TYPE) {
190
+ if (Array.isArray(entry.details?.components)) return entry.details.components;
191
+ return [
192
+ ...(entry.content?.includes("<!-- freeflow-bootstrap:workflow -->") ? [BOOTSTRAP_COMPONENTS.workflow] : []),
193
+ ...(entry.content?.includes("<!-- freeflow-bootstrap:cognitive-routing -->")
194
+ ? [BOOTSTRAP_COMPONENTS.cognitiveRouting]
195
+ : []),
196
+ ];
155
197
  }
156
-
157
- const alreadyLoaded = activeSessionEntries(sessionManager).some(
158
- (entry) => entry?.type === "custom_message" && entry?.customType === WORKFLOW_BOOTSTRAP_MESSAGE_TYPE,
159
- );
160
- if (alreadyLoaded) {
161
- return undefined;
198
+ if (entry.customType === WORKFLOW_BOOTSTRAP_MESSAGE_TYPE) {
199
+ return [BOOTSTRAP_COMPONENTS.workflow];
162
200
  }
201
+ if (entry.customType === COGNITIVE_ROUTING_BOOTSTRAP_MESSAGE_TYPE) {
202
+ return [BOOTSTRAP_COMPONENTS.cognitiveRouting];
203
+ }
204
+ return [];
205
+ }
163
206
 
164
- return {
165
- customType: WORKFLOW_BOOTSTRAP_MESSAGE_TYPE,
166
- content: `# Freeflow Workflow Bootstrap\n\n${freeflowContext.workflowSkill.trim()}`,
167
- display: false,
168
- details: { skill: "workflow", source: "first-turn-bootstrap" },
169
- };
207
+ export function filterBootstrapMessage(message) {
208
+ return bootstrapEntryComponents(message).length === 0 ? message : undefined;
170
209
  }
171
210
 
172
211
  function isRecord(value) {
@@ -174,30 +213,16 @@ function isRecord(value) {
174
213
  }
175
214
 
176
215
  function validateCoreConfigFields(value) {
177
- if (value.defaultMode !== undefined && !VALID_MODES.has(value.defaultMode)) {
178
- return `invalid defaultMode: ${JSON.stringify(value.defaultMode)}`;
179
- }
180
-
181
216
  if (value.enabled !== undefined && typeof value.enabled !== "boolean") {
182
217
  return "enabled must be a boolean";
183
218
  }
184
219
 
185
- if (value.interactionContract !== undefined && typeof value.interactionContract !== "boolean") {
186
- return "interactionContract must be a boolean";
220
+ if (value.contextVirtualization !== undefined && typeof value.contextVirtualization !== "boolean") {
221
+ return "contextVirtualization must be a boolean";
187
222
  }
188
223
 
189
- if (value.skills !== undefined) {
190
- if (!isRecord(value.skills)) {
191
- return "skills must be an object";
192
- }
193
- for (const key of Object.keys(value.skills)) {
194
- if (key !== "enabled") {
195
- return `unsupported skills config key: ${key}`;
196
- }
197
- }
198
- if (value.skills.enabled !== undefined && typeof value.skills.enabled !== "boolean") {
199
- return "skills.enabled must be a boolean";
200
- }
224
+ if (value.conversationHistory !== undefined && typeof value.conversationHistory !== "boolean") {
225
+ return "conversationHistory must be a boolean";
201
226
  }
202
227
 
203
228
  return null;
@@ -208,14 +233,15 @@ function validateFreeflowConfigShape(value) {
208
233
  return "config must be a JSON object";
209
234
  }
210
235
 
236
+ // Keep retired capability keys shape-tolerated so existing repositories remain activated; runtime behavior ignores them.
211
237
  const allowedKeys = new Set([
212
238
  "enabled",
213
- "defaultMode",
214
- "interactionContract",
215
- "skills",
216
239
  "outputRouter",
217
240
  "observedRouting",
218
241
  "scriptTransform",
242
+ "cognitiveRouting",
243
+ "contextVirtualization",
244
+ "conversationHistory",
219
245
  ]);
220
246
  for (const key of Object.keys(value)) {
221
247
  if (!allowedKeys.has(key)) {
@@ -240,7 +266,13 @@ function validateFreeflowLocalConfigShape(value) {
240
266
  return "local config must be a JSON object";
241
267
  }
242
268
 
243
- const allowedKeys = new Set(["enabled", "defaultMode", "interactionContract", "skills", "processing"]);
269
+ const allowedKeys = new Set([
270
+ "enabled",
271
+ "processing",
272
+ "cognitiveRouting",
273
+ "contextVirtualization",
274
+ "conversationHistory",
275
+ ]);
244
276
  for (const key of Object.keys(value)) {
245
277
  if (!allowedKeys.has(key)) {
246
278
  return `unsupported top-level local config key: ${key}`;
@@ -317,24 +349,19 @@ function resolveLayeredValue(repository, local, key, fallback) {
317
349
 
318
350
  function resolveCoreConfig(repository, local) {
319
351
  const enabled = resolveLayeredValue(repository, local, "enabled", true);
320
- const interactionContract = resolveLayeredValue(repository, local, "interactionContract", true);
321
- const defaultMode = resolveLayeredValue(repository, local, "defaultMode", "workflow");
322
- const repositorySkills = isRecord(repository.skills) ? repository.skills : {};
323
- const localSkills = isRecord(local.skills) ? local.skills : {};
324
- const skillsEnabled = resolveLayeredValue(repositorySkills, localSkills, "enabled", true);
352
+ const contextVirtualization = resolveLayeredValue(repository, local, "contextVirtualization", false);
353
+ const conversationHistory = resolveLayeredValue(repository, local, "conversationHistory", false);
325
354
 
326
355
  return {
327
356
  config: {
328
357
  enabled: enabled.value,
329
- interactionContract: interactionContract.value,
330
- skills: { enabled: skillsEnabled.value },
331
- defaultMode: defaultMode.value,
358
+ contextVirtualization: contextVirtualization.value,
359
+ conversationHistory: conversationHistory.value,
332
360
  },
333
361
  sources: {
334
362
  enabled: enabled.source,
335
- interactionContract: interactionContract.source,
336
- skillsEnabled: skillsEnabled.source,
337
- defaultMode: defaultMode.source,
363
+ contextVirtualization: contextVirtualization.source,
364
+ conversationHistory: conversationHistory.source,
338
365
  },
339
366
  };
340
367
  }
@@ -354,22 +381,21 @@ function resolveSessionCoreConfig(layers) {
354
381
  const configured = layers.coreConfig;
355
382
  const sources = layers.sources;
356
383
  const enabled = currentSessionOverrides.enabled;
357
- const interactionContract = currentSessionOverrides.interactionContract;
358
- const skillsEnabled = currentSessionOverrides.skillsEnabled;
384
+ const contextVirtualization = currentSessionOverrides.contextVirtualization;
385
+ const conversationHistory = currentSessionOverrides.conversationHistory;
359
386
 
360
387
  return {
361
388
  config: {
362
389
  enabled: typeof enabled === "boolean" ? enabled : configured.enabled,
363
- interactionContract:
364
- typeof interactionContract === "boolean" ? interactionContract : configured.interactionContract,
365
- skills: { enabled: typeof skillsEnabled === "boolean" ? skillsEnabled : configured.skills.enabled },
366
- defaultMode: configured.defaultMode,
390
+ contextVirtualization:
391
+ typeof contextVirtualization === "boolean" ? contextVirtualization : configured.contextVirtualization,
392
+ conversationHistory:
393
+ typeof conversationHistory === "boolean" ? conversationHistory : configured.conversationHistory,
367
394
  },
368
395
  sources: {
369
396
  enabled: typeof enabled === "boolean" ? "session" : sources.enabled,
370
- interactionContract: typeof interactionContract === "boolean" ? "session" : sources.interactionContract,
371
- skillsEnabled: typeof skillsEnabled === "boolean" ? "session" : sources.skillsEnabled,
372
- defaultMode: sources.defaultMode,
397
+ contextVirtualization: typeof contextVirtualization === "boolean" ? "session" : sources.contextVirtualization,
398
+ conversationHistory: typeof conversationHistory === "boolean" ? "session" : sources.conversationHistory,
373
399
  },
374
400
  };
375
401
  }
@@ -400,15 +426,32 @@ export async function readFreeflowConfigLayers(cwd) {
400
426
  };
401
427
  }
402
428
 
403
- export async function readCapabilityState(cwd) {
429
+ export async function readCapabilityState(cwd, host = undefined, extensionHost = undefined) {
404
430
  const layers = await readFreeflowConfigLayers(cwd);
405
- const parsed = layers.repository.valid ? layers.repository.parsed : {};
406
- const normalized = normalizeFreeflowConfig(parsed);
407
431
  const effectiveCore = resolveSessionCoreConfig(layers);
408
432
  const enabled = layers.configured && effectiveCore.config.enabled;
409
- const interactionContractConfigEnabled = effectiveCore.config.interactionContract;
410
- const skillsConfigEnabled = effectiveCore.config.skills.enabled;
411
- const outputRouterConfigEnabled = normalized.config.outputRouter.enabled;
433
+ const contextVirtualizationConfigEnabled = effectiveCore.config.contextVirtualization;
434
+ const conversationHistoryConfigEnabled = effectiveCore.config.conversationHistory;
435
+ const hostSupportsCognitiveRouting = supportsCognitiveRoutingModelRegistry(host);
436
+ const configuredCognitiveRouting = await resolveCognitiveRoutingState(
437
+ layers.repository.parsed,
438
+ layers.local.parsed,
439
+ hostSupportsCognitiveRouting ? host : undefined,
440
+ );
441
+ const disabledReason = enabled ? undefined : { code: "disabled" as const, message: "Freeflow is disabled" };
442
+ const childCapability = (configuredEnabled) => ({
443
+ enabled: configuredEnabled,
444
+ effective: enabled && configuredEnabled,
445
+ ...(disabledReason ? { blockingReason: disabledReason } : {}),
446
+ });
447
+ const cognitiveRouting = enabled
448
+ ? configuredCognitiveRouting
449
+ : {
450
+ ...configuredCognitiveRouting,
451
+ enabled: false,
452
+ effective: false,
453
+ blockingReason: disabledReason,
454
+ };
412
455
  return {
413
456
  configured: layers.configured,
414
457
  repositoryConfigured: layers.repositoryConfigured,
@@ -424,121 +467,34 @@ export async function readCapabilityState(cwd) {
424
467
  configuredSources: layers.sources,
425
468
  sessionOverrides: { ...currentSessionOverrides },
426
469
  configSources: effectiveCore.sources,
427
- defaultMode: effectiveCore.config.defaultMode,
428
470
  enabled,
429
- interactionContract: {
430
- enabled: interactionContractConfigEnabled,
431
- effective: enabled && interactionContractConfigEnabled,
432
- },
433
- skills: {
434
- enabled: skillsConfigEnabled,
435
- effective: enabled && skillsConfigEnabled,
436
- },
437
- outputRouter: {
438
- enabled: enabled && outputRouterConfigEnabled,
439
- configEnabled: outputRouterConfigEnabled,
440
- },
471
+ contextVirtualization: childCapability(contextVirtualizationConfigEnabled),
472
+ conversationHistory: childCapability(conversationHistoryConfigEnabled),
473
+ hostSupportsCognitiveRouting,
474
+ cognitiveRouting,
441
475
  };
442
476
  }
443
477
 
444
478
  export const readRuntimeState = readCapabilityState;
445
479
 
446
- export async function readOutputRouterConfig(cwd) {
447
- const layers = await readFreeflowConfigLayers(cwd);
448
- const parsed = layers.repository.valid ? layers.repository.parsed : {};
449
- const localParsed = layers.local.valid ? layers.local.parsed : {};
450
- const normalized = normalizeFreeflowConfig(parsed);
451
- const local = normalizeLocalFreeflowConfig(localParsed);
452
- const freeflowEnabled = layers.configured && layers.coreConfig.enabled;
453
- const effectiveConfig = freeflowEnabled
454
- ? normalized.config
455
- : {
456
- ...normalized.config,
457
- outputRouter: { ...normalized.config.outputRouter, enabled: false },
458
- observedRouting: {
459
- ...normalized.config.observedRouting,
460
- enabled: false,
461
- },
462
- scriptTransform: {
463
- ...normalized.config.scriptTransform,
464
- enabled: false,
465
- },
466
- };
467
- const configWarnings = [...normalized.warnings];
468
- if (layers.repository.exists && !layers.repository.valid) {
469
- configWarnings.unshift(
470
- `.freeflow/config.json could not be parsed; Freeflow runtime is inactive. ${layers.repository.parseError ?? ""}`.trim(),
471
- );
472
- }
473
- if (layers.local.exists && !layers.local.valid) {
474
- configWarnings.unshift(
475
- `.freeflow/local.json could not be parsed; Freeflow runtime is inactive. ${layers.local.parseError ?? ""}`.trim(),
476
- );
477
- }
478
- return {
479
- config: effectiveConfig.outputRouter,
480
- freeflowConfig: effectiveConfig,
481
- localConfig: local.config,
482
- warnings: [...configWarnings, ...local.warnings],
483
- };
484
- }
485
-
486
- export function notifyRouterConfigWarnings(ctx, routerConfigResult) {
487
- if (!routerConfigResult.warnings.length) {
488
- return;
489
- }
490
-
491
- const key = routerConfigResult.warnings.join("\n");
492
- if (key === lastRouterConfigWarningKey) {
493
- return;
494
- }
495
-
496
- lastRouterConfigWarningKey = key;
497
- ctx.ui.notify(`Freeflow config warning: ${routerConfigResult.warnings.join(" ")}`, "warning");
498
- }
499
-
500
- export function restoreModeOverride(ctx) {
501
- currentModeOverride = null;
480
+ export function restoreSessionOverrides(ctx) {
502
481
  currentSessionOverrides = {};
503
482
  const entries = ctx.sessionManager?.getBranch?.() ?? ctx.sessionManager?.getEntries?.() ?? [];
504
483
 
505
484
  for (const entry of entries) {
506
- if (entry.type !== "custom") continue;
507
- if (entry.customType === MODE_STATE_ENTRY) {
508
- const mode = entry.data?.currentMode;
509
- currentModeOverride = VALID_MODES.has(mode) ? mode : null;
510
- } else if (entry.customType === SESSION_OVERRIDES_ENTRY) {
485
+ if (entry.type === "custom" && entry.customType === SESSION_OVERRIDES_ENTRY) {
511
486
  currentSessionOverrides = normalizeSessionOverrides(entry.data?.overrides);
512
487
  }
513
488
  }
514
489
  }
515
490
 
516
- export async function readModeState(cwd) {
517
- const layers = await readFreeflowConfigLayers(cwd);
518
- const effectiveCore = resolveSessionCoreConfig(layers);
519
- const repositoryDefaultMode = VALID_MODES.has(layers.repository.parsed.defaultMode)
520
- ? layers.repository.parsed.defaultMode
521
- : "workflow";
522
- const personalDefaultMode = VALID_MODES.has(layers.local.parsed.defaultMode) ? layers.local.parsed.defaultMode : null;
523
- const sessionMode = VALID_MODES.has(currentModeOverride) ? currentModeOverride : null;
524
- const resolvedMode = sessionMode ?? effectiveCore.config.defaultMode;
525
- const active = layers.configured && effectiveCore.config.enabled && effectiveCore.config.skills.enabled;
526
-
527
- return {
528
- repositoryDefaultMode,
529
- repositoryDefaultModeSource: VALID_MODES.has(layers.repository.parsed.defaultMode) ? "repository" : "builtin",
530
- personalDefaultMode,
531
- defaultMode: effectiveCore.config.defaultMode,
532
- defaultModeSource: effectiveCore.sources.defaultMode,
533
- currentMode: sessionMode,
534
- sessionMode,
535
- resolvedMode,
536
- active,
537
- effectiveMode: active ? resolvedMode : null,
538
- };
539
- }
540
-
541
- export function setModeStatus(ctx, modeState, capabilityState = undefined) {
491
+ export function setFreeflowStatus(
492
+ ctx,
493
+ capabilityState = undefined,
494
+ cognitiveRoutingRuntime = undefined,
495
+ freeflowContext = undefined,
496
+ options: { cognitiveRoutingStartupPending?: boolean; startupSelectionSuppressed?: boolean } = {},
497
+ ) {
542
498
  if (capabilityState && !capabilityState.configured) {
543
499
  ctx.ui.setStatus("freeflow", capabilityState.configExists ? "freeflow: config error" : "freeflow: setup needed");
544
500
  return;
@@ -548,36 +504,59 @@ export function setModeStatus(ctx, modeState, capabilityState = undefined) {
548
504
  ctx.ui.setStatus("freeflow", `freeflow: off${source}`);
549
505
  return;
550
506
  }
507
+ if (capabilityState?.enabled === true && !hasUsableMandatoryPrompts(freeflowContext)) {
508
+ ctx.ui.setStatus("freeflow", "freeflow: unavailable");
509
+ return;
510
+ }
551
511
 
552
512
  const active: string[] = [];
553
- if (capabilityState?.interactionContract.effective) {
554
- active.push("interaction");
555
- } else if (capabilityState?.configSources?.interactionContract === "session") {
556
- active.push("interaction off (session)");
557
- }
558
- if (capabilityState?.skills.effective) {
559
- active.push(`${modeState.effectiveMode}${modeState.currentMode ? " (session)" : ""}`);
560
- } else if (capabilityState?.configSources?.skillsEnabled === "session") {
561
- active.push("skills off (session)");
513
+ const cognitiveRouting = capabilityState?.cognitiveRouting;
514
+ const cognitiveRoutingInactive =
515
+ cognitiveRouting?.enabled === true && cognitiveRoutingRuntime?.runtimeStatus === "inactive";
516
+ const cognitiveRoutingBlocked =
517
+ cognitiveRouting?.enabled === true &&
518
+ (cognitiveRouting?.blockingReason?.code === "runtime_blocked" ||
519
+ cognitiveRoutingRuntime?.runtimeStatus === "blocked");
520
+ const cognitiveRoutingActive = cognitiveRouting?.effective === true && cognitiveRoutingRuntime?.effective === true;
521
+ const startupSelectionSuppressesCognitiveRouting = options.startupSelectionSuppressed === true;
522
+ if (cognitiveRoutingInactive) {
523
+ active.push("cognitive inactive");
524
+ } else if (cognitiveRoutingBlocked) {
525
+ active.push(`cognitive blocked · ${cognitiveRoutingRuntime?.runtimeReason ?? "runtime_blocked"}`);
526
+ } else if (cognitiveRoutingActive) {
527
+ const profile = cognitiveRoutingRuntime.activeProfile;
528
+ const control =
529
+ cognitiveRoutingRuntime.controlMode === "manual-standard" ||
530
+ cognitiveRoutingRuntime.controlMode === "manual-reasoning"
531
+ ? "manual hold"
532
+ : "automatic";
533
+ active.push(`${profile} · ${control}`);
534
+ } else if (cognitiveRouting?.enabled === true) {
535
+ if (cognitiveRouting.blockingReason?.code === "runtime_disabled") {
536
+ active.push("cognitive blocked · runtime_disabled");
537
+ } else if (
538
+ cognitiveRouting.effective === true &&
539
+ cognitiveRoutingRuntime === undefined &&
540
+ options.cognitiveRoutingStartupPending === true &&
541
+ !startupSelectionSuppressesCognitiveRouting
542
+ ) {
543
+ const startupProfile =
544
+ cognitiveRouting.sessionStart?.control === "manual"
545
+ ? (cognitiveRouting.sessionStart.profile ?? "reasoning")
546
+ : "reasoning";
547
+ active.push(`${startupProfile} · pending`);
548
+ } else {
549
+ const reason =
550
+ cognitiveRouting.effective === true
551
+ ? (cognitiveRoutingRuntime?.blockingReason?.code ?? "runtime_inactive")
552
+ : (cognitiveRouting.blockingReason?.code ?? "unavailable");
553
+ active.push(`cognitive blocked · ${reason}`);
554
+ }
562
555
  }
563
- if (capabilityState?.outputRouter.enabled) {
564
- active.push("router");
556
+ if (capabilityState?.contextVirtualization?.effective || capabilityState?.conversationHistory?.effective) {
557
+ active.push("context");
565
558
  }
566
- ctx.ui.setStatus("freeflow", `freeflow: ${active.length > 0 ? active.join(" · ") : "idle"}`);
567
- }
568
-
569
- function modeSourceLabel(source) {
570
- if (source === "local") return "personal override";
571
- if (source === "repository") return "repository default";
572
- return "built-in default";
573
- }
574
-
575
- function describeModeState(modeState) {
576
- const configuredDefault = `configured default ${modeState.defaultMode} (${modeSourceLabel(modeState.defaultModeSource)})`;
577
- if (modeState.sessionMode) {
578
- return `session ${modeState.sessionMode}; ${configuredDefault}`;
579
- }
580
- return configuredDefault;
559
+ ctx.ui.setStatus("freeflow", `freeflow: ${active.length > 0 ? active.join(" · ") : "active"}`);
581
560
  }
582
561
 
583
562
  export function skillPrompt(skill, args) {
@@ -585,175 +564,170 @@ export function skillPrompt(skill, args) {
585
564
  return trimmed ? `/skill:${skill}\n\n${trimmed}` : `/skill:${skill}`;
586
565
  }
587
566
 
588
- function outputRouterModeGuidance(mode, skillsEnabled = true) {
589
- if (!skillsEnabled) {
590
- return "Freeflow Skills are disabled: no Freeflow workflow mode is active; apply only output-router evidence guidance.";
591
- }
592
- if (mode === "conversation") {
593
- return "conversation mode: keep routed-tool guidance soft; answer questions directly.";
594
- }
595
- if (mode === "strict-workflow") {
596
- return "strict-workflow mode: strongest guidance; prefer exact, recoverable routed evidence for risky work.";
597
- }
598
- return "workflow mode: prefer routed tools for exploration and likely-large command output.";
599
- }
600
-
601
- function outputRouterContext(modeState, freeflowContext, routerConfigResult, capabilityState) {
602
- const safetyNetText =
603
- routerConfigResult.config.postToolRouting === "off"
604
- ? ""
605
- : "\n\nOutput-router config note: large native read/bash outputs may be vaulted and replaced with labeled routed output. Use freeflow_search with the output id to recover exact content.";
606
-
607
- return `## Loaded Output Router Skill
567
+ type CognitiveRoutingRuntimeSnapshot = {
568
+ effective?: boolean;
569
+ runtimeStatus?: "active" | "inactive" | "blocked";
570
+ runtimeReason?: unknown;
571
+ activeProfile?: unknown;
572
+ controlMode?: unknown;
573
+ };
608
574
 
609
- Mode guidance: ${outputRouterModeGuidance(modeState.effectiveMode, capabilityState.skills.effective)}${safetyNetText}
575
+ type FreeflowContextMessage = {
576
+ customType?: unknown;
577
+ [key: string]: unknown;
578
+ };
610
579
 
611
- \`\`\`md
612
- ${freeflowContext.outputRouterSkill.trim()}
613
- \`\`\``;
580
+ function publicCognitiveRoutingControl(controlMode) {
581
+ if (controlMode === "automatic") return "automatic";
582
+ if (controlMode === "manual-standard" || controlMode === "manual-reasoning") return "manual";
583
+ return "unavailable";
614
584
  }
615
585
 
616
- function capabilityContext(capabilityState) {
617
- const sessionSuffix = (source) => (source === "session" ? " (session override)" : "");
618
- const interactionContract = `${capabilityState.interactionContract.effective ? "enabled" : "disabled"}${sessionSuffix(
619
- capabilityState.configSources.interactionContract,
620
- )}`;
621
- const skills = `${capabilityState.skills.effective ? "enabled" : "disabled"}${sessionSuffix(
622
- capabilityState.configSources.skillsEnabled,
623
- )}`;
624
- const outputRouter = capabilityState.outputRouter.enabled ? "enabled" : "disabled";
625
- return `## Freeflow Capabilities
626
-
627
- - Interaction contract: ${interactionContract}. Configure with \`/freeflow settings\` or temporarily with \`/freeflow settings session\`; inspect with \`/freeflow status\`.
628
- - Skills: ${skills}. Configure with \`/freeflow settings\` or temporarily with \`/freeflow settings session\`; inspect with \`/freeflow status\`.
629
- - Output router: ${outputRouter}. Configure with \`/freeflow settings\` or \`/output-router\`; inspect with \`/output-router status\`.
630
-
631
- Disabled capabilities are named only for status/config awareness. Capability-specific instructions and tools are active only while that capability is enabled.`;
586
+ function publicCognitiveRoutingProfile(activeProfile, effective) {
587
+ if (effective !== true) return "unavailable";
588
+ return activeProfile === "standard" || activeProfile === "reasoning" ? activeProfile : "unavailable";
632
589
  }
633
590
 
634
- function hasModelFacingCapability(capabilityState) {
635
- return (
636
- capabilityState.interactionContract.effective ||
637
- capabilityState.skills.effective ||
638
- capabilityState.outputRouter.enabled
639
- );
591
+ function publicCapabilityStatus(capability) {
592
+ if (capability?.effective === true) return "active";
593
+ if (!capability) return "unavailable";
594
+ const blockingCode = capability.blockingReason?.code;
595
+ if (blockingCode && blockingCode !== "disabled") return "unavailable";
596
+ return "inactive";
640
597
  }
641
598
 
642
- function modeOverlayContext(mode) {
643
- if (mode === "conversation") {
644
- return `
645
-
646
- ## Conversation Mode Boundary
647
-
648
- Conversation mode is active and read-only.
649
-
650
- Do not call write, edit, or mutating tools. Do not create, delete, commit, push, or change repository, system, external, or durable state.
651
-
652
- A mutation request does not switch mode, and an execution skill does not override this boundary. Explain that conversation mode is read-only and ask the user to switch to workflow or strict-workflow.`;
653
- }
654
-
655
- if (mode === "strict-workflow") {
656
- return `
657
-
658
- ## Strict Workflow Overlay
659
-
660
- Strict Workflow is active. Use the adaptive Workflow, but increase decision, evidence, verification, and checkpoint pressure at high-risk or hard-to-reverse boundaries.
661
-
662
- For work affecting security, privacy, billing, data loss, migrations, public interfaces, compatibility, deployment, or architecture:
663
-
664
- - stop for any user-owned choice or source conflict;
665
- - inspect the relevant risk surface before crossing the boundary;
666
- - select only artifacts, checkpoints, and independent review that materially reduce risk;
667
- - verify at the affected boundary before claiming success.
668
-
669
- Do not manufacture ceremony for low-risk, reversible work. Strict Workflow does not authorize mutation, bypass safety, or make every implementation detail a user decision.`;
670
- }
671
-
672
- return "";
599
+ function publicCognitiveRoutingStatus(capability, runtime) {
600
+ if (runtime?.runtimeStatus === "inactive") return "inactive";
601
+ return publicCapabilityStatus(capability);
673
602
  }
674
603
 
675
- function activeModeContext(modeState) {
676
- return `## Freeflow Mode State
677
-
678
- Repository default mode: \`${modeState.repositoryDefaultMode}\` (${modeState.repositoryDefaultModeSource}).
679
- Personal default override: \`${modeState.personalDefaultMode ?? "none"}\`.
680
- Configured default mode: \`${modeState.defaultMode}\` (${modeSourceLabel(modeState.defaultModeSource)}).
681
- Session mode override: \`${modeState.sessionMode ?? "none"}\`.
682
- Resolved mode: \`${modeState.resolvedMode}\`.
683
- Effective Freeflow mode: \`${modeState.effectiveMode}\`.
684
-
685
- Mode behavior:
686
- - \`conversation\`: answer, discuss, critique, and inspect read-only; require a mode change before mutating state.
687
- - \`workflow\`: use the adaptive workflow for consequential or mutating work.
688
- - \`strict-workflow\`: use the same workflow with stronger decision, evidence, and checkpoint pressure at high-risk or hard-to-reverse boundaries.
604
+ export function freeflowRuntimeStateMessage(
605
+ capabilityState,
606
+ cognitiveRoutingRuntime: CognitiveRoutingRuntimeSnapshot | undefined = undefined,
607
+ freeflowContext = undefined,
608
+ ) {
609
+ const cognitiveRoutingEffective = capabilityState?.cognitiveRouting?.effective === true;
610
+ const profile = publicCognitiveRoutingProfile(
611
+ cognitiveRoutingRuntime?.activeProfile,
612
+ cognitiveRoutingEffective && cognitiveRoutingRuntime?.effective === true,
613
+ );
614
+ const control =
615
+ profile === "unavailable" ? "unavailable" : publicCognitiveRoutingControl(cognitiveRoutingRuntime?.controlMode);
616
+ const mandatoryPromptAvailable = capabilityState?.enabled !== true || hasUsableMandatoryPrompts(freeflowContext);
617
+ const freeflowStatus = capabilityState?.configured
618
+ ? capabilityState.enabled
619
+ ? mandatoryPromptAvailable
620
+ ? "active"
621
+ : "unavailable"
622
+ : "inactive"
623
+ : capabilityState?.configExists
624
+ ? "config error"
625
+ : "setup needed";
689
626
 
690
- Task type, risk classification, and direct skill calls do not change mode. Recommend another mode when useful; the user decides.
691
- Do not announce mode on every reply. Mention it when the user asks, configuration is discussed, or it changes the next action.${modeOverlayContext(modeState.effectiveMode)}`;
627
+ return {
628
+ role: "custom",
629
+ customType: FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE,
630
+ content: [
631
+ "# Freeflow Runtime State",
632
+ "",
633
+ "This is extension-generated runtime state. Use it to interpret the stable Freeflow guidance.",
634
+ "",
635
+ `Freeflow: ${freeflowStatus}`,
636
+ "",
637
+ "Capabilities:",
638
+ `- Context Virtualization: ${publicCapabilityStatus(capabilityState?.contextVirtualization)}`,
639
+ `- Conversation History: ${publicCapabilityStatus(capabilityState?.conversationHistory)}`,
640
+ `- Cognitive Routing: ${publicCognitiveRoutingStatus(capabilityState?.cognitiveRouting, cognitiveRoutingRuntime)}`,
641
+ "",
642
+ "Cognitive Routing:",
643
+ `- Control: \`${control}\``,
644
+ `- Profile: \`${profile}\``,
645
+ ].join("\n"),
646
+ display: false,
647
+ details: { source: "provider-request-runtime-state" },
648
+ };
692
649
  }
693
650
 
694
- function inactiveModeContext(modeState) {
695
- return `## Freeflow Mode State
696
-
697
- Repository default mode: \`${modeState.repositoryDefaultMode}\` (${modeState.repositoryDefaultModeSource}).
698
- Personal default override: \`${modeState.personalDefaultMode ?? "none"}\`.
699
- Configured default mode: \`${modeState.defaultMode}\` (${modeSourceLabel(modeState.defaultModeSource)}).
700
- Session mode override: \`${modeState.sessionMode ?? "none"}\`.
701
- Resolved mode: \`${modeState.resolvedMode}\` (inactive because Skills are disabled).
702
- Effective Freeflow mode: \`none\`.
703
-
704
- Freeflow workflow modes are dormant until Skills are enabled with \`/freeflow settings\`.`;
651
+ export function withoutFreeflowRuntimeState(messages: readonly FreeflowContextMessage[] | undefined) {
652
+ return (Array.isArray(messages) ? messages : []).filter(
653
+ (message) =>
654
+ message?.customType !== FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE &&
655
+ message?.customType !== COGNITIVE_ROUTING_RUNTIME_STATE_MESSAGE_TYPE,
656
+ );
705
657
  }
706
658
 
707
- function controlPlaneContext(modeState, capabilityState) {
708
- return `# Freeflow Control Plane
709
-
710
- Freeflow is enabled for this repo, but no model-facing capabilities are enabled.
711
- These lines are status/config awareness only; do not apply Freeflow workflow or output-router behavior.
712
-
713
- ${inactiveModeContext(modeState)}
714
-
715
- ${capabilityContext(capabilityState)}
659
+ function lastUserMessageIndex(messages: readonly FreeflowContextMessage[]): number {
660
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
661
+ if (messages[index]?.role === "user") return index;
662
+ }
663
+ return -1;
664
+ }
716
665
 
717
- Use \`/freeflow settings\` to enable the Interaction Contract, Skills, or Output Router. The read-only \`freeflow_status\` diagnostic may be available so the model can answer setup/status questions.`;
666
+ function insertRuntimeStateBeforeLatestUser(
667
+ messages: readonly FreeflowContextMessage[],
668
+ runtimeState: FreeflowContextMessage,
669
+ ): FreeflowContextMessage[] {
670
+ const latestUserIndex = lastUserMessageIndex(messages);
671
+ if (latestUserIndex < 0) return [...messages, runtimeState];
672
+ return [...messages.slice(0, latestUserIndex), runtimeState, ...messages.slice(latestUserIndex)];
718
673
  }
719
674
 
720
- export function runtimeContext(modeState, freeflowContext, routerConfigResult, capabilityState) {
721
- if (!capabilityState.configured) {
675
+ export function withFreeflowRuntimeState(
676
+ messages: readonly FreeflowContextMessage[] | undefined,
677
+ capabilityState,
678
+ cognitiveRoutingRuntime: CognitiveRoutingRuntimeSnapshot | undefined = undefined,
679
+ freeflowContext = undefined,
680
+ options: { force?: boolean } = {},
681
+ ) {
682
+ const source = Array.isArray(messages) ? messages : [];
683
+ const runtimeState = freeflowRuntimeStateMessage(capabilityState, cognitiveRoutingRuntime, freeflowContext);
684
+ const runtimeStateMessages = source.filter(
685
+ (message) =>
686
+ message?.customType === FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE ||
687
+ message?.customType === COGNITIVE_ROUTING_RUNTIME_STATE_MESSAGE_TYPE,
688
+ );
689
+ const withoutRuntimeState = withoutFreeflowRuntimeState(source);
690
+ const expectedRuntimeStateIndex = lastUserMessageIndex(withoutRuntimeState);
691
+ const runtimeStateIndex = source.findIndex((message) => message?.customType === FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE);
692
+ const unchanged =
693
+ options.force !== true &&
694
+ runtimeStateMessages.length === 1 &&
695
+ runtimeStateMessages[0]?.customType === FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE &&
696
+ runtimeStateMessages[0]?.content === runtimeState.content &&
697
+ runtimeStateIndex === (expectedRuntimeStateIndex < 0 ? withoutRuntimeState.length : expectedRuntimeStateIndex);
698
+ if (unchanged) return source;
699
+ return insertRuntimeStateBeforeLatestUser(withoutRuntimeState, runtimeState);
700
+ }
701
+
702
+ export function runtimeContext(freeflowContext, capabilityState) {
703
+ if (
704
+ capabilityState?.configured !== true ||
705
+ capabilityState?.enabled !== true ||
706
+ !hasUsableMandatoryPrompts(freeflowContext)
707
+ ) {
722
708
  return "";
723
709
  }
724
710
 
725
- if (!capabilityState.enabled) {
726
- return `# Freeflow Disabled
727
-
728
- Freeflow is disabled by \`.freeflow/config.json\` for this repo. Do not apply the Freeflow Interaction Contract, workflow, or output-router behavior unless the user re-enables Freeflow with \`/freeflow enable\` or \`/freeflow settings\`.
729
-
730
- These instructions are context-loading only. They do not override user instructions, repo instructions, or host safety and approval policy.`;
711
+ const blocks = [freeflowContext.corePrompt.trim(), freeflowContext.interactionContractPrompt.trim()];
712
+ if (
713
+ capabilityState.cognitiveRouting?.effective === true &&
714
+ isPromptAvailable(freeflowContext.cognitiveRoutingPrompt)
715
+ ) {
716
+ blocks.push(freeflowContext.cognitiveRoutingPrompt.trim());
731
717
  }
732
-
733
- if (!hasModelFacingCapability(capabilityState)) {
734
- return controlPlaneContext(modeState, capabilityState);
718
+ if (
719
+ capabilityState.contextVirtualization?.effective === true &&
720
+ isPromptAvailable(freeflowContext.contextVirtualizationPrompt)
721
+ ) {
722
+ blocks.push(freeflowContext.contextVirtualizationPrompt.trim());
735
723
  }
736
-
737
- const modeText = capabilityState.skills.effective ? activeModeContext(modeState) : inactiveModeContext(modeState);
738
- const interactionContractText = capabilityState.interactionContract.effective
739
- ? `\n\n${freeflowContext.interactionContract.trim()}`
740
- : "";
741
- const routerText =
742
- capabilityState.outputRouter.enabled && routerConfigResult.config.enabled
743
- ? `\n\n${outputRouterContext(modeState, freeflowContext, routerConfigResult, capabilityState)}`
744
- : "";
745
-
746
- return `# Freeflow Runtime Context
747
-
748
- Freeflow Pi extension loaded this before the agent turn.
749
- Runtime delivery: confirmed for this Pi \`before_agent_start\` invocation.
750
- These instructions are context-loading only. They do not override user instructions, repo instructions, or host safety and approval policy.
751
-
752
- ${modeText}
753
-
754
- ${capabilityContext(capabilityState)}${interactionContractText}${routerText}
755
-
756
- This Pi extension loads enabled runtime context before every agent turn and routes commands only; it does not enforce policy, block tools, grant permissions, or create repo-local hooks.`;
724
+ if (
725
+ capabilityState.conversationHistory?.effective === true &&
726
+ isPromptAvailable(freeflowContext.conversationHistoryPrompt)
727
+ ) {
728
+ blocks.push(freeflowContext.conversationHistoryPrompt.trim());
729
+ }
730
+ return blocks.filter(Boolean).join("\n\n");
757
731
  }
758
732
 
759
733
  export async function setSessionCoreOverride(key: SessionCoreKey, value: boolean | null, ctx, pi) {
@@ -779,117 +753,25 @@ export async function setSessionCoreOverride(key: SessionCoreKey, value: boolean
779
753
  pi?.appendEntry?.(SESSION_OVERRIDES_ENTRY, { overrides: { ...currentSessionOverrides } });
780
754
  return {
781
755
  changed: true,
782
- reloadRequired: key === "enabled" || key === "skillsEnabled",
756
+ reloadRequired: key === "enabled",
783
757
  sessionOverrides: { ...currentSessionOverrides },
784
- capabilityState: await readCapabilityState(ctx.cwd),
758
+ capabilityState: await readCapabilityState(ctx.cwd, ctx, pi?.host),
785
759
  };
786
760
  }
787
761
 
788
762
  export async function resetSessionOverrides(ctx, pi) {
789
763
  const hadCoreOverrides = Object.keys(currentSessionOverrides).length > 0;
790
- const reloadRequired =
791
- Object.hasOwn(currentSessionOverrides, "enabled") || Object.hasOwn(currentSessionOverrides, "skillsEnabled");
792
- const hadModeOverride = VALID_MODES.has(currentModeOverride);
764
+ const reloadRequired = Object.hasOwn(currentSessionOverrides, "enabled");
793
765
 
794
766
  if (hadCoreOverrides) {
795
767
  currentSessionOverrides = {};
796
768
  pi?.appendEntry?.(SESSION_OVERRIDES_ENTRY, { overrides: {} });
797
769
  }
798
- if (hadModeOverride) {
799
- await setSessionMode(null, ctx, pi);
800
- }
801
770
 
802
771
  return {
803
- changed: hadCoreOverrides || hadModeOverride,
772
+ changed: hadCoreOverrides,
804
773
  reloadRequired,
805
774
  sessionOverrides: { ...currentSessionOverrides },
806
- modeState: await readModeState(ctx.cwd),
775
+ capabilityState: await readCapabilityState(ctx.cwd, ctx, pi?.host),
807
776
  };
808
777
  }
809
-
810
- export async function setSessionMode(mode, ctx, pi) {
811
- const nextMode = mode === "default" || mode === "reset" || mode === null ? null : mode;
812
- if (nextMode !== null && !VALID_MODES.has(nextMode)) {
813
- throw new Error(`Invalid Freeflow mode: ${String(mode)}`);
814
- }
815
- currentModeOverride = nextMode;
816
- pi?.appendEntry?.(MODE_STATE_ENTRY, { currentMode: nextMode });
817
- return readModeState(ctx.cwd);
818
- }
819
-
820
- export async function handleModeCommand(args, ctx, pi) {
821
- const arg = args?.trim();
822
- const capabilityState = await readCapabilityState(ctx.cwd);
823
- const inactiveModeState = await readModeState(ctx.cwd);
824
-
825
- if (!capabilityState.configured) {
826
- setModeStatus(ctx, inactiveModeState, capabilityState);
827
- ctx.ui.notify(
828
- "Freeflow is installed but this repo is not set up. Run /setup-freeflow before changing mode.",
829
- "warning",
830
- );
831
- return { changed: false, error: "not_configured" };
832
- }
833
- if (!capabilityState.enabled) {
834
- setModeStatus(ctx, inactiveModeState, capabilityState);
835
- ctx.ui.notify(
836
- "Freeflow is disabled for this repo. Use /freeflow enable or /freeflow settings to re-enable it.",
837
- "warning",
838
- );
839
- return { changed: false, error: "freeflow_disabled" };
840
- }
841
- if (!capabilityState.skills.effective) {
842
- setModeStatus(ctx, inactiveModeState, capabilityState);
843
- ctx.ui.notify(
844
- `Freeflow modes are inactive because Skills are disabled. Configured default mode: ${inactiveModeState.defaultMode} (${modeSourceLabel(inactiveModeState.defaultModeSource)}). Enable Skills in /freeflow settings before changing mode.`,
845
- "warning",
846
- );
847
- return { changed: false, error: "skills_disabled" };
848
- }
849
-
850
- if (VALID_MODES.has(arg)) {
851
- if (inactiveModeState.effectiveMode === arg) {
852
- setModeStatus(ctx, inactiveModeState, capabilityState);
853
- const message = inactiveModeState.sessionMode
854
- ? `Freeflow is already in ${arg} mode for this Pi session. No session entry was added. Configured default remains ${inactiveModeState.defaultMode} (${modeSourceLabel(inactiveModeState.defaultModeSource)}).`
855
- : `Freeflow is already in ${arg} mode from the configured default (${modeSourceLabel(inactiveModeState.defaultModeSource)}). No session override was created.`;
856
- ctx.ui.notify(message, "info");
857
- return { changed: false, modeState: inactiveModeState };
858
- }
859
-
860
- const modeState = await setSessionMode(arg, ctx, pi);
861
- setModeStatus(ctx, modeState, capabilityState);
862
- ctx.ui.notify(
863
- `Freeflow mode is now ${modeState.effectiveMode} for this Pi session. Stored in Pi session history; .freeflow/local.json and .freeflow/config.json were not changed. Configured default remains ${modeState.defaultMode} (${modeSourceLabel(modeState.defaultModeSource)}).`,
864
- "info",
865
- );
866
- return { changed: true, modeState };
867
- }
868
-
869
- if (RESET_MODE_ARGS.has(arg) || arg === "default") {
870
- if (!inactiveModeState.sessionMode) {
871
- setModeStatus(ctx, inactiveModeState, capabilityState);
872
- ctx.ui.notify(
873
- `Freeflow is already using the configured default: ${inactiveModeState.defaultMode} (${modeSourceLabel(inactiveModeState.defaultModeSource)}). No session override is active.`,
874
- "info",
875
- );
876
- return { changed: false, modeState: inactiveModeState };
877
- }
878
-
879
- const modeState = await setSessionMode(null, ctx, pi);
880
- setModeStatus(ctx, modeState, capabilityState);
881
- ctx.ui.notify(
882
- `Freeflow mode reset to configured default: ${modeState.defaultMode} (${modeSourceLabel(modeState.defaultModeSource)}). Session override cleared; .freeflow/local.json and .freeflow/config.json were not changed.`,
883
- "info",
884
- );
885
- return { changed: true, modeState };
886
- }
887
-
888
- const modeState = await readModeState(ctx.cwd);
889
- setModeStatus(ctx, modeState, capabilityState);
890
- ctx.ui.notify(
891
- `Freeflow mode is ${modeState.effectiveMode} (${describeModeState(modeState)}). Use /freeflow mode conversation, /freeflow mode workflow, /freeflow mode strict-workflow, or /freeflow mode reset.`,
892
- "info",
893
- );
894
- return { changed: false, modeState, error: arg ? "invalid_mode" : undefined };
895
- }