@hassangameryt/freeflow 0.6.0 → 0.7.1

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 (136) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +3 -2
  3. package/.codex-plugin/plugin.json +5 -6
  4. package/.cursor-plugin/plugin.json +25 -0
  5. package/README.md +66 -56
  6. package/capabilities/cognitive-routing/SKILL.md +288 -130
  7. package/com.github.copilot/hooks/hooks.json +11 -0
  8. package/command-surface.json +0 -24
  9. package/gemini-extension.json +5 -0
  10. package/hooks/adapters/claude-session-start.mjs +16 -0
  11. package/hooks/adapters/codex-session-start.mjs +16 -0
  12. package/hooks/adapters/copilot-session-start.mjs +13 -0
  13. package/hooks/adapters/cursor-session-start.mjs +13 -0
  14. package/hooks/adapters/gemini-session-start.mjs +15 -0
  15. package/hooks/claude/hooks.json +18 -0
  16. package/hooks/codex/hooks.json +18 -0
  17. package/hooks/cursor/hooks.json +9 -0
  18. package/hooks/freeflow-runtime-context.mjs +3 -650
  19. package/hooks/hooks.json +4 -30
  20. package/hooks/shared/runtime-context.mjs +210 -0
  21. package/hooks/tests/check-host-adapters.test.mjs +82 -0
  22. package/hooks/tests/check-runtime-context-hook.sh +140 -320
  23. package/package.json +20 -4
  24. package/pi-extension/dist/cognitive-routing/commands.js +3 -3
  25. package/pi-extension/dist/cognitive-routing/config.js +16 -20
  26. package/pi-extension/dist/cognitive-routing/controller.js +478 -48
  27. package/pi-extension/dist/cognitive-routing/history.js +12 -0
  28. package/pi-extension/dist/cognitive-routing/host.js +74 -12
  29. package/pi-extension/dist/cognitive-routing/pi-routing-session.js +414 -0
  30. package/pi-extension/dist/cognitive-routing/pi-session-control.js +320 -0
  31. package/pi-extension/dist/cognitive-routing/tool.js +1 -0
  32. package/pi-extension/dist/cognitive-routing/types.js +1 -1
  33. package/pi-extension/dist/index.js +236 -219
  34. package/pi-extension/dist/runtime/runtime-context.js +126 -286
  35. package/pi-extension/dist/settings/settings-ui.js +52 -336
  36. package/pi-extension/src/cognitive-routing/commands.ts +15 -5
  37. package/pi-extension/src/cognitive-routing/config.ts +16 -20
  38. package/pi-extension/src/cognitive-routing/controller.ts +561 -51
  39. package/pi-extension/src/cognitive-routing/history.ts +12 -0
  40. package/pi-extension/src/cognitive-routing/host.ts +110 -16
  41. package/pi-extension/src/cognitive-routing/pi-routing-session.ts +538 -0
  42. package/pi-extension/src/cognitive-routing/pi-session-control.ts +444 -0
  43. package/pi-extension/src/cognitive-routing/tool.ts +1 -0
  44. package/pi-extension/src/cognitive-routing/types.ts +1 -1
  45. package/pi-extension/src/index.ts +249 -239
  46. package/pi-extension/src/runtime/runtime-context.ts +129 -305
  47. package/pi-extension/src/settings/settings-ui.ts +53 -371
  48. package/pi-extension/tests/cognitive-routing/commands.test.js +1 -1
  49. package/pi-extension/tests/cognitive-routing/controller.test.js +69 -40
  50. package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +128 -57
  51. package/pi-extension/tests/cognitive-routing/pi-normal-lifecycle.test.js +434 -0
  52. package/pi-extension/tests/cognitive-routing/pi-session-control.test.js +174 -0
  53. package/pi-extension/tests/cognitive-routing/pi-session-history.test.js +91 -0
  54. package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +47 -4
  55. package/pi-extension/tests/integration/mode-free-core.test.js +218 -0
  56. package/pi-extension/tests/integration/pi-extension.test.js +430 -2478
  57. package/pi-extension/tests/integration/prompt-architecture.test.js +124 -79
  58. package/pi-extension/tests/integration/unaffected-regressions.test.js +411 -0
  59. package/plugin.json +33 -0
  60. package/runtime/prompts/cognitive-routing.md +5 -4
  61. package/runtime/prompts/conversation-history.md +1 -1
  62. package/runtime/prompts/core.md +77 -6
  63. package/runtime/prompts/interaction-contract.md +6 -2
  64. package/skills/action-selection/SKILL.md +93 -44
  65. package/skills/action-selection/references/trajectory-stalls.md +71 -0
  66. package/skills/bypass/SKILL.md +109 -39
  67. package/skills/commit-work/SKILL.md +94 -55
  68. package/skills/commit-work/references/git-state-edges.md +94 -0
  69. package/skills/commit-work/references/simple-push.md +92 -0
  70. package/skills/commit-work/references/staging-decisions.md +79 -46
  71. package/skills/decision-gate/SKILL.md +80 -22
  72. package/skills/design-for-depth/SKILL.md +208 -43
  73. package/skills/design-for-depth/references/design-pressure-signals.md +108 -240
  74. package/skills/design-for-depth/references/interface-design-loop.md +77 -97
  75. package/skills/design-for-depth/references/module-and-dependency-design.md +147 -0
  76. package/skills/design-for-depth/references/software-design-philosophy.md +127 -173
  77. package/skills/design-for-depth/references/state-and-failure-boundaries.md +136 -0
  78. package/skills/diagnose-failure/SKILL.md +198 -52
  79. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +19 -5
  80. package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +13 -5
  81. package/skills/discuss/SKILL.md +118 -76
  82. package/skills/discuss/references/discussion-continuity.md +84 -32
  83. package/skills/evaluate-skill/SKILL.md +166 -52
  84. package/skills/evaluate-skill/references/evaluation-definition-schema.md +2 -2
  85. package/skills/evaluate-skill/references/evaluation-design.md +56 -30
  86. package/skills/evaluate-skill/references/execution-and-evidence.md +1 -1
  87. package/skills/evaluate-skill/references/review-and-revision.md +62 -21
  88. package/skills/execute-work/SKILL.md +98 -59
  89. package/skills/execute-work/references/code-practices.md +4 -4
  90. package/skills/execute-work/references/execute-work-edges.md +42 -22
  91. package/skills/review-artifact/SKILL.md +132 -71
  92. package/skills/review-artifact/references/adjudicate-artifact-review.md +101 -0
  93. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +52 -41
  94. package/skills/review-work/SKILL.md +115 -72
  95. package/skills/review-work/references/adjudicate-work-review.md +97 -0
  96. package/skills/review-work/references/independent-work-reviewer-contract.md +40 -35
  97. package/skills/review-work/references/security-risk-lens.md +17 -15
  98. package/skills/setup-freeflow/SKILL.md +14 -17
  99. package/skills/setup-freeflow/references/activation-contract.md +24 -30
  100. package/skills/setup-freeflow/references/host-setup.md +6 -7
  101. package/skills/simplify-code/SKILL.md +97 -39
  102. package/skills/simplify-code/references/simplification-patterns.md +1 -1
  103. package/skills/tdd/SKILL.md +108 -41
  104. package/skills/tdd/references/test-design.md +1 -1
  105. package/skills/track-work/SKILL.md +133 -300
  106. package/skills/track-work/references/working-record-format.md +515 -0
  107. package/skills/track-work/scripts/lib/document.mjs +713 -0
  108. package/skills/track-work/scripts/lib/format.mjs +166 -0
  109. package/skills/track-work/scripts/lib/store.mjs +233 -0
  110. package/skills/track-work/scripts/lib/transitions.mjs +790 -0
  111. package/skills/track-work/scripts/lib/views.mjs +27 -430
  112. package/skills/track-work/scripts/working-record.mjs +292 -98
  113. package/skills/verify-work/SKILL.md +46 -20
  114. package/skills/verify-work/references/browser-runtime-evidence.md +1 -1
  115. package/skills/verify-work/references/integration-evidence.md +1 -7
  116. package/skills/verify-work/references/performance-evidence.md +1 -1
  117. package/skills/workflow/SKILL.md +110 -80
  118. package/skills/workflow/references/domain-skill-composition.md +24 -18
  119. package/skills/write-skill/SKILL.md +218 -62
  120. package/skills/write-skill/references/activation-boundaries.md +63 -21
  121. package/skills/write-skill/references/agent-first-instructions.md +61 -27
  122. package/skills/write-skill/references/progressive-disclosure.md +91 -41
  123. package/skills/write-skill/references/skill-dependency-graphs.md +190 -0
  124. package/runtime/prompts/skills.md +0 -65
  125. package/skills/mode-contract/SKILL.md +0 -104
  126. package/skills/track-work/scripts/lib/codec.mjs +0 -570
  127. package/skills/track-work/scripts/lib/commands.mjs +0 -568
  128. package/skills/track-work/scripts/lib/contract.mjs +0 -303
  129. package/skills/track-work/scripts/lib/model.mjs +0 -506
  130. package/skills/track-work/scripts/lib/operations.mjs +0 -965
  131. package/skills/track-work/scripts/lib/record-store.mjs +0 -264
  132. package/skills/track-work/scripts/lib/result.mjs +0 -37
  133. package/skills/track-work/scripts/lib/rewrites.mjs +0 -740
  134. package/skills/track-work/scripts/lib/schema.mjs +0 -277
  135. package/skills/track-work/scripts/lib/source-inventory.mjs +0 -162
  136. package/skills/track-work/scripts/lib/workspace.mjs +0 -134
@@ -1,8 +1,11 @@
1
+ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
+
1
3
  import {
2
4
  COGNITIVE_ROUTING_CONTROL_ENTRY,
5
+ COGNITIVE_ROUTING_INACTIVE_ENTRY,
3
6
  COGNITIVE_ROUTING_INTENT_ENTRY,
4
- CognitiveRoutingController,
5
7
  } from "./cognitive-routing/controller.js";
8
+ import { PiRoutingSession, type PiRoutingSessionSnapshot } from "./cognitive-routing/pi-routing-session.js";
6
9
  import {
7
10
  cognitiveRoutingProfileCompletions,
8
11
  handleCognitiveRoutingProfileCommand,
@@ -23,21 +26,18 @@ import {
23
26
  freeflowModelSkillPaths,
24
27
  freeflowSkillPath,
25
28
  getRuntimeContext,
29
+ hasUsableMandatoryPrompts,
30
+ isPromptAvailable,
26
31
  readCapabilityState,
27
- readModeState,
28
32
  refreshRuntimeContext,
29
- restoreModeOverride,
33
+ restoreSessionOverrides,
30
34
  runtimeContext,
31
35
  filterBootstrapMessage,
32
- setModeStatus,
36
+ setFreeflowStatus,
33
37
  skillPrompt,
34
38
  withFreeflowRuntimeState,
35
39
  } from "./runtime/runtime-context.js";
36
40
 
37
- function startupSelectionSuppressesCognitiveRouting(ctx: any): boolean {
38
- return ctx?.modelStateProvenance?.explicitModel === true || ctx?.modelStateProvenance?.explicitThinking === true;
39
- }
40
-
41
41
  function unavailableCapability(capability: any, code: string, message: string): any {
42
42
  return {
43
43
  ...capability,
@@ -48,15 +48,12 @@ function unavailableCapability(capability: any, code: string, message: string):
48
48
 
49
49
  function modelFacingCapabilityState(
50
50
  capabilityState: any,
51
- ctx: any,
52
51
  freeflowContext: any,
53
- cognitiveRoutingController: CognitiveRoutingController | undefined,
52
+ routingSnapshot: PiRoutingSessionSnapshot,
54
53
  cognitiveRoutingActivationFailed = false,
55
54
  ): any {
56
55
  const surfaceState = {
57
56
  ...capabilityState,
58
- interactionContract: { ...capabilityState?.interactionContract },
59
- skills: { ...capabilityState?.skills },
60
57
  contextVirtualization: { ...capabilityState?.contextVirtualization },
61
58
  conversationHistory: { ...capabilityState?.conversationHistory },
62
59
  cognitiveRouting: { ...capabilityState?.cognitiveRouting },
@@ -69,53 +66,59 @@ function modelFacingCapabilityState(
69
66
  }
70
67
  };
71
68
 
72
- if (!freeflowContext?.corePrompt) {
73
- for (const key of [
74
- "interactionContract",
75
- "skills",
76
- "contextVirtualization",
77
- "conversationHistory",
78
- "cognitiveRouting",
79
- ]) {
80
- markUnavailable(key, "Freeflow core prompt is unavailable.");
69
+ if (!hasUsableMandatoryPrompts(freeflowContext)) {
70
+ for (const key of ["contextVirtualization", "conversationHistory", "cognitiveRouting"]) {
71
+ markUnavailable(key, "Mandatory Freeflow prompt is unavailable.");
81
72
  }
82
73
  return surfaceState;
83
74
  }
84
75
 
85
- if (surfaceState.interactionContract?.effective === true && !freeflowContext.interactionContractPrompt) {
86
- markUnavailable("interactionContract", "Interaction Contract prompt is unavailable.");
87
- }
88
- if (surfaceState.skills?.effective === true && !freeflowContext.skillsPrompt) {
89
- markUnavailable("skills", "Skills prompt is unavailable.");
76
+ if (
77
+ surfaceState.contextVirtualization?.effective === true &&
78
+ !isPromptAvailable(freeflowContext.contextVirtualizationPrompt)
79
+ ) {
80
+ markUnavailable("contextVirtualization", "Context Virtualization prompt is unavailable.");
90
81
  }
91
-
92
- if (surfaceState.skills?.effective === true) {
93
- if (surfaceState.contextVirtualization?.effective === true && !freeflowContext.contextVirtualizationPrompt) {
94
- markUnavailable("contextVirtualization", "Context Virtualization prompt is unavailable.");
95
- }
96
- if (surfaceState.conversationHistory?.effective === true && !freeflowContext.conversationHistoryPrompt) {
97
- markUnavailable("conversationHistory", "Conversation History prompt is unavailable.");
98
- }
99
- } else {
100
- for (const key of ["contextVirtualization", "conversationHistory", "cognitiveRouting"]) {
101
- markUnavailable(key, "Skills prompt is unavailable.");
102
- }
82
+ if (
83
+ surfaceState.conversationHistory?.effective === true &&
84
+ !isPromptAvailable(freeflowContext.conversationHistoryPrompt)
85
+ ) {
86
+ markUnavailable("conversationHistory", "Conversation History prompt is unavailable.");
103
87
  }
104
88
 
105
89
  if (surfaceState.cognitiveRouting?.effective === true) {
106
- if (startupSelectionSuppressesCognitiveRouting(ctx)) {
90
+ const controllerState = routingSnapshot.runtimeState;
91
+ if (routingSnapshot.disposition === "blocked") {
92
+ surfaceState.cognitiveRouting = unavailableCapability(
93
+ surfaceState.cognitiveRouting,
94
+ "runtime_blocked",
95
+ routingSnapshot.reason ?? "Cognitive Routing is blocked because its inactive state could not be persisted.",
96
+ );
97
+ } else if (routingSnapshot.startupSelectionSuppressed || routingSnapshot.disposition === "inactive") {
98
+ surfaceState.cognitiveRouting = unavailableCapability(
99
+ surfaceState.cognitiveRouting,
100
+ "runtime_inactive",
101
+ "Cognitive Routing is inactive because Pi owns the startup model or thinking level.",
102
+ );
103
+ } else if (controllerState?.runtimeStatus === "inactive") {
107
104
  surfaceState.cognitiveRouting = unavailableCapability(
108
105
  surfaceState.cognitiveRouting,
109
106
  "runtime_inactive",
110
- "Cognitive Routing is inactive because the host selected the startup model or thinking level.",
107
+ controllerState.runtimeReason ?? "Cognitive Routing is inactive because Pi owns the current model state.",
108
+ );
109
+ } else if (controllerState?.runtimeStatus === "blocked") {
110
+ surfaceState.cognitiveRouting = unavailableCapability(
111
+ surfaceState.cognitiveRouting,
112
+ "runtime_blocked",
113
+ controllerState.runtimeReason ?? "Cognitive Routing is blocked because its state is not proven.",
111
114
  );
112
- } else if (cognitiveRoutingActivationFailed || cognitiveRoutingController?.state()?.effective === false) {
115
+ } else if (cognitiveRoutingActivationFailed || controllerState?.effective === false) {
113
116
  surfaceState.cognitiveRouting = unavailableCapability(
114
117
  surfaceState.cognitiveRouting,
115
118
  "unavailable",
116
119
  "Cognitive Routing could not activate for this session.",
117
120
  );
118
- } else if (!freeflowContext.cognitiveRoutingPrompt) {
121
+ } else if (!isPromptAvailable(freeflowContext.cognitiveRoutingPrompt)) {
119
122
  markUnavailable("cognitiveRouting", "Cognitive Routing prompt is unavailable.");
120
123
  }
121
124
  }
@@ -123,11 +126,6 @@ function modelFacingCapabilityState(
123
126
  return surfaceState;
124
127
  }
125
128
 
126
- function modelFacingModeState(modeState: any, capabilityState: any): any {
127
- if (capabilityState?.skills?.effective === true) return modeState;
128
- return { ...modeState, active: false, effectiveMode: null };
129
- }
130
-
131
129
  type SessionEntriesContext = {
132
130
  sessionManager?: {
133
131
  getBranch?: () => readonly unknown[];
@@ -150,7 +148,9 @@ function sessionHasConversationOrRoutingState(ctx: SessionEntriesContext | undef
150
148
  }
151
149
  return (
152
150
  record.type === "custom" &&
153
- (record.customType === COGNITIVE_ROUTING_INTENT_ENTRY || record.customType === COGNITIVE_ROUTING_CONTROL_ENTRY)
151
+ (record.customType === COGNITIVE_ROUTING_INTENT_ENTRY ||
152
+ record.customType === COGNITIVE_ROUTING_CONTROL_ENTRY ||
153
+ record.customType === COGNITIVE_ROUTING_INACTIVE_ENTRY)
154
154
  );
155
155
  });
156
156
  } catch {
@@ -158,51 +158,17 @@ function sessionHasConversationOrRoutingState(ctx: SessionEntriesContext | undef
158
158
  }
159
159
  }
160
160
 
161
- function hasCognitiveRoutingHost(pi: any, ctx: any): boolean {
162
- return (
163
- isPiFlowHost(pi?.host) &&
164
- typeof pi?.appendEntryDurable === "function" &&
165
- typeof pi?.acquireModelStateControl === "function" &&
166
- typeof ctx?.modelRegistry?.getApiKeyAndHeaders === "function"
167
- );
168
- }
169
-
170
- async function reconcileCognitiveRoutingController(
171
- pi: any,
172
- ctx: any,
173
- capabilityState: any,
174
- previous: CognitiveRoutingController | undefined,
175
- ): Promise<CognitiveRoutingController | undefined> {
176
- const cognitiveRouting = capabilityState?.cognitiveRouting;
177
- if (
178
- !isPiFlowHost(pi?.host) ||
179
- !cognitiveRouting?.effective ||
180
- startupSelectionSuppressesCognitiveRouting(ctx) ||
181
- !hasCognitiveRoutingHost(pi, ctx)
182
- ) {
183
- if (previous) await previous.deactivate();
184
- return undefined;
185
- }
186
-
187
- const controller = new CognitiveRoutingController({ capabilityState: cognitiveRouting, pi, ctx });
188
- const recovered = await controller.recover();
189
- if (recovered.status === "pending") return controller;
190
- if (recovered.status !== "active") {
191
- const activated = await controller.activate();
192
- return activated.status === "active" ? controller : undefined;
193
- }
194
- return controller;
195
- }
196
-
197
161
  async function applyCapabilityToolVisibility(
198
162
  pi: any,
199
163
  ctx: any,
200
164
  capabilityState = undefined,
201
- cognitiveRoutingController: CognitiveRoutingController | undefined = undefined,
165
+ routingSnapshot: PiRoutingSessionSnapshot = {
166
+ status: "available",
167
+ disposition: "available",
168
+ startupSelectionSuppressed: false,
169
+ },
202
170
  ): Promise<void> {
203
- if (typeof pi?.setActiveTools !== "function" || typeof pi?.getAllTools !== "function") {
204
- return;
205
- }
171
+ if (typeof pi?.setActiveTools !== "function" || typeof pi?.getAllTools !== "function") return;
206
172
 
207
173
  const state = capabilityState ?? (await readCapabilityState(ctx.cwd, ctx, pi?.host));
208
174
  const allToolNames = pi
@@ -216,12 +182,11 @@ async function applyCapabilityToolVisibility(
216
182
  );
217
183
 
218
184
  if (allToolNameSet.has(COGNITIVE_ROUTING_SWITCH_TOOL_NAME)) {
219
- const controllerState = cognitiveRoutingController?.state();
185
+ const controllerState = routingSnapshot.runtimeState;
220
186
  const routingToolActive =
221
187
  state.cognitiveRouting?.effective === true &&
222
188
  controllerState?.effective === true &&
223
- controllerState.controlMode === "automatic" &&
224
- !startupSelectionSuppressesCognitiveRouting(ctx);
189
+ controllerState.controlMode === "automatic";
225
190
  if (routingToolActive) active.add(COGNITIVE_ROUTING_SWITCH_TOOL_NAME);
226
191
  else active.delete(COGNITIVE_ROUTING_SWITCH_TOOL_NAME);
227
192
  }
@@ -237,29 +202,6 @@ async function applyCapabilityToolVisibility(
237
202
  pi.setActiveTools([...active]);
238
203
  }
239
204
 
240
- async function applyLiveCapabilityState(
241
- pi: any,
242
- ctx: any,
243
- cognitiveRoutingController: CognitiveRoutingController | undefined,
244
- options: { reconcileCognitiveRouting?: boolean } = {},
245
- ): Promise<CognitiveRoutingController | undefined> {
246
- const [modeState, capabilityState] = await Promise.all([
247
- readModeState(ctx.cwd),
248
- readCapabilityState(ctx.cwd, ctx, pi?.host),
249
- ]);
250
- await refreshRuntimeContext(capabilityState);
251
- let nextController = cognitiveRoutingController;
252
- if (
253
- options.reconcileCognitiveRouting &&
254
- (nextController === undefined || capabilityState.cognitiveRouting?.effective !== true)
255
- ) {
256
- nextController = await reconcileCognitiveRoutingController(pi, ctx, capabilityState, nextController);
257
- }
258
- setModeStatus(ctx, modeState, capabilityState, nextController?.state());
259
- await applyCapabilityToolVisibility(pi, ctx, capabilityState, nextController);
260
- return nextController;
261
- }
262
-
263
205
  function disabledToolCall(toolName: string, capability: string) {
264
206
  const command = capability === "freeflow" ? "/freeflow settings" : `/${capability} settings`;
265
207
  return {
@@ -268,7 +210,7 @@ function disabledToolCall(toolName: string, capability: string) {
268
210
  };
269
211
  }
270
212
 
271
- function freeflowCompletions(prefix: string | undefined, hostInfo = undefined) {
213
+ function freeflowCompletions(prefix: string | undefined, cognitiveRoutingAvailable = false) {
272
214
  const query = prefix ?? "";
273
215
  if (query.startsWith("settings ")) {
274
216
  const settingsQuery = query.slice("settings ".length);
@@ -280,27 +222,12 @@ function freeflowCompletions(prefix: string | undefined, hostInfo = undefined) {
280
222
  .filter((item) => item.value.startsWith(settingsQuery))
281
223
  .map((item) => ({ ...item, value: `settings ${item.value}` }));
282
224
  }
283
- if (isPiFlowHost(hostInfo) && query.startsWith("profile ")) {
225
+ if (cognitiveRoutingAvailable && query.startsWith("profile ")) {
284
226
  return cognitiveRoutingProfileCompletions(query.slice("profile ".length)).map((item) => ({
285
227
  ...item,
286
228
  value: `profile ${item.value}`,
287
229
  }));
288
230
  }
289
- if (query.startsWith("mode ")) {
290
- const modeQuery = query.slice("mode ".length);
291
- return [
292
- { value: "conversation", label: "conversation", description: "Read-only discussion and inspection" },
293
- { value: "workflow", label: "workflow", description: "Adaptive workflow for consequential work" },
294
- { value: "strict-workflow", label: "strict-workflow", description: "Stronger pressure at high-risk boundaries" },
295
- {
296
- value: "reset",
297
- label: "reset",
298
- description: "Clear the session override and use the configured default",
299
- },
300
- ]
301
- .filter((item) => item.value.startsWith(modeQuery))
302
- .map((item) => ({ ...item, value: `mode ${item.value}` }));
303
- }
304
231
  if (query.startsWith("context ")) {
305
232
  const contextQuery = query.slice("context ".length);
306
233
  return [
@@ -316,8 +243,7 @@ function freeflowCompletions(prefix: string | undefined, hostInfo = undefined) {
316
243
  { value: "settings", label: "settings", description: "Open personal override settings" },
317
244
  { value: "status", label: "status", description: "Show effective Freeflow state" },
318
245
  { value: "context", label: "context", description: "Inspect Freeflow Context" },
319
- { value: "mode", label: "mode", description: "Select a temporary session mode" },
320
- ...(isPiFlowHost(hostInfo)
246
+ ...(cognitiveRoutingAvailable
321
247
  ? [{ value: "profile", label: "profile", description: "Hold or release Cognitive Routing profile control" }]
322
248
  : []),
323
249
  { value: "enable", label: "enable", description: "Enable Freeflow for this repository" },
@@ -333,10 +259,17 @@ function bypassCompletions(prefix: string | undefined) {
333
259
  ].filter((item) => item.value.startsWith(query));
334
260
  }
335
261
 
336
- async function sendSkillCommand(pi: any, ctx: any, skill: string, args: string | undefined) {
262
+ type FreeflowExtensionAPI = ExtensionAPI & { host?: unknown };
263
+
264
+ async function sendSkillCommand(
265
+ pi: FreeflowExtensionAPI,
266
+ ctx: ExtensionCommandContext,
267
+ skill: string,
268
+ args: string | undefined,
269
+ ) {
337
270
  const state = await readCapabilityState(ctx.cwd, ctx, pi?.host);
338
271
  if (skill === "setup-freeflow" && !state.configured) {
339
- await pi.sendUserMessage(skillPrompt(skill, args));
272
+ await pi.sendUserMessage(skillPrompt(skill, args), { expandPromptTemplates: true });
340
273
  return;
341
274
  }
342
275
  if (!state.configured) {
@@ -350,23 +283,27 @@ async function sendSkillCommand(pi: any, ctx: any, skill: string, args: string |
350
283
  );
351
284
  return;
352
285
  }
353
- if (!state.skills.effective) {
354
- ctx.ui.notify("Freeflow skills are disabled. Use /freeflow settings to enable skills.", "warning");
286
+ const freeflowContext = await getRuntimeContext(state);
287
+ if (!hasUsableMandatoryPrompts(freeflowContext)) {
288
+ ctx.ui.notify("Freeflow core prompts are unavailable; the requested skill cannot be dispatched.", "warning");
355
289
  return;
356
290
  }
357
- await pi.sendUserMessage(skillPrompt(skill, args));
291
+ await pi.sendUserMessage(skillPrompt(skill, args), { expandPromptTemplates: true });
358
292
  }
359
293
 
360
- export default function freeflow(pi) {
361
- let cognitiveRoutingController: CognitiveRoutingController | undefined;
294
+ export default function freeflow(pi: FreeflowExtensionAPI) {
295
+ // SAFETY: These helpers register complete Pi tool definitions but retain legacy structural typing for PiFlow compatibility.
296
+ const toolRegistrar = pi as unknown as { registerTool(tool: Record<string, unknown>): void };
297
+ const routingSession = new PiRoutingSession({ pi, stockPi: !isPiFlowHost(pi?.host) });
362
298
  let latestCognitiveRoutingContext: any;
363
299
  let providerSurfaceSnapshot: { context: any; value: any } | undefined;
300
+ let runtimeStateRefreshRequired = true;
364
301
  let freeflowContextRuntime: FreeflowContextRuntime | undefined;
365
302
  let contextVirtualizationRuntime: ContextVirtualizationRuntime | undefined;
366
303
  let conversationHistoryRuntime: ConversationHistoryRuntime | undefined;
367
304
  const registerContextToolForState = (capabilityState: any) => {
368
305
  registerFreeflowContextTool(
369
- pi,
306
+ toolRegistrar,
370
307
  () => contextVirtualizationRuntime,
371
308
  () => conversationHistoryRuntime,
372
309
  {
@@ -376,73 +313,86 @@ export default function freeflow(pi) {
376
313
  );
377
314
  };
378
315
  const buildProviderSurface = async (ctx: any, activateCognitiveRouting = false): Promise<any> => {
379
- const [modeState, capabilityState] = await Promise.all([
380
- readModeState(ctx.cwd),
381
- readCapabilityState(ctx.cwd, ctx, pi?.host),
382
- ]);
316
+ const capabilityState = await readCapabilityState(ctx.cwd, ctx, pi?.host);
383
317
  const freeflowContext = await getRuntimeContext(capabilityState);
384
318
  const promptCapabilityState = modelFacingCapabilityState(
385
319
  capabilityState,
386
- ctx,
387
320
  freeflowContext,
388
- cognitiveRoutingController,
321
+ routingSession.snapshot(),
389
322
  );
390
323
  let activationAttempted = false;
391
324
  if (
392
325
  activateCognitiveRouting &&
393
- (cognitiveRoutingController === undefined || promptCapabilityState.cognitiveRouting?.effective !== true)
326
+ routingSession.canAutoActivate() &&
327
+ (!routingSession.hasController() || promptCapabilityState.cognitiveRouting?.effective !== true)
394
328
  ) {
395
- activationAttempted =
396
- promptCapabilityState.cognitiveRouting?.effective === true && !startupSelectionSuppressesCognitiveRouting(ctx);
329
+ activationAttempted = promptCapabilityState.cognitiveRouting?.effective === true;
397
330
  try {
398
- cognitiveRoutingController = await reconcileCognitiveRoutingController(
399
- pi,
331
+ await routingSession.reconcileController(
400
332
  ctx,
401
333
  promptCapabilityState,
402
- cognitiveRoutingController,
334
+ routingSession.armed ? { ...routingSession.armed } : undefined,
403
335
  );
336
+ if (routingSession.hasController()) {
337
+ routingSession.clearArmed();
338
+ if (routingSession.snapshot().status === "active") routingSession.markActive();
339
+ }
404
340
  } catch {
405
- cognitiveRoutingController = undefined;
341
+ routingSession.clearController();
406
342
  }
407
343
  }
408
344
  const surfaceCapabilityState = modelFacingCapabilityState(
409
345
  promptCapabilityState,
410
- ctx,
411
346
  freeflowContext,
412
- cognitiveRoutingController,
413
- activationAttempted && !cognitiveRoutingController,
347
+ routingSession.snapshot(),
348
+ activationAttempted && !routingSession.hasController(),
414
349
  );
350
+ const routingSnapshot = routingSession.snapshot();
415
351
  return {
416
- modeState: modelFacingModeState(modeState, surfaceCapabilityState),
417
352
  capabilityState: surfaceCapabilityState,
418
353
  freeflowContext,
419
- cognitiveRoutingRuntime: cognitiveRoutingController?.state(),
354
+ cognitiveRoutingRuntime: routingSnapshot.runtimeState,
420
355
  configuredCapabilityState: capabilityState,
421
356
  };
422
357
  };
423
358
  const applyLiveCapabilityStateForSession = async (
424
359
  ctx: any,
425
- options: { reconcileCognitiveRouting?: boolean } = {},
360
+ options: { reconcileCognitiveRouting?: boolean; materializeCognitiveRouting?: boolean } = {},
426
361
  ): Promise<void> => {
427
- cognitiveRoutingController = await applyLiveCapabilityState(pi, ctx, cognitiveRoutingController, options);
428
- registerContextToolForState(await readCapabilityState(ctx.cwd, ctx, pi?.host));
362
+ providerSurfaceSnapshot = undefined;
363
+ await routingSession.applyLiveCapabilityState(ctx, options);
364
+ const capabilityState = await readCapabilityState(ctx.cwd, ctx, pi?.host);
365
+ const freeflowContext = await getRuntimeContext(capabilityState);
366
+ const surfaceCapabilityState = modelFacingCapabilityState(
367
+ capabilityState,
368
+ freeflowContext,
369
+ routingSession.snapshot(),
370
+ );
371
+ const routingSnapshot = routingSession.snapshot();
372
+ setFreeflowStatus(ctx, surfaceCapabilityState, routingSnapshot.runtimeState, freeflowContext, {
373
+ startupSelectionSuppressed: routingSnapshot.startupSelectionSuppressed,
374
+ });
375
+ await applyCapabilityToolVisibility(pi, ctx, surfaceCapabilityState, routingSnapshot);
376
+ registerContextToolForState(surfaceCapabilityState);
429
377
  };
430
378
 
431
- if (isPiFlowHost(pi?.host)) {
432
- registerCognitiveRoutingTool(pi, () => cognitiveRoutingController);
433
- registerCognitiveRoutingHistoryTool(pi, (options, context) => {
434
- if (cognitiveRoutingController) return cognitiveRoutingController.history(options);
435
- return readCognitiveRoutingHistory(context ?? latestCognitiveRoutingContext, options);
379
+ if (routingSession.supportsCognitiveRoutingRuntime()) {
380
+ registerCognitiveRoutingTool(toolRegistrar, () => routingSession.controllerForTool());
381
+ registerCognitiveRoutingHistoryTool(toolRegistrar, (options, context) => {
382
+ return (
383
+ routingSession.history(options) ??
384
+ readCognitiveRoutingHistory(context ?? latestCognitiveRoutingContext, options)
385
+ );
436
386
  });
437
387
  }
438
388
  registerFreeflowContextTool(
439
- pi,
389
+ toolRegistrar,
440
390
  () => contextVirtualizationRuntime,
441
391
  () => conversationHistoryRuntime,
442
392
  { contextVirtualization: false, conversationHistory: false },
443
393
  );
444
394
 
445
- if (isPiFlowHost(pi?.host) && typeof pi.registerShortcut === "function") {
395
+ if (routingSession.supportsCognitiveRoutingRuntime() && typeof pi.registerShortcut === "function") {
446
396
  pi.registerShortcut("ctrl+shift+r", {
447
397
  description: "Cycle the Cognitive Routing manual standard/reasoning hold",
448
398
  handler: async (ctx) => {
@@ -450,17 +400,27 @@ export default function freeflow(pi) {
450
400
  ctx.ui?.notify?.("Freeflow settings and profile changes are available only while Pi is idle.", "warning");
451
401
  return;
452
402
  }
453
- if (!cognitiveRoutingController) {
403
+ const armedCycle = routingSession.hasController() ? undefined : routingSession.cycleArmedManual();
404
+ if (armedCycle) {
405
+ routingSession.beginExplicitReactivation();
406
+ ctx.ui?.notify?.(`Cognitive Routing manual hold set to ${armedCycle.target}.`, "info");
407
+ await applyLiveCapabilityStateForSession(ctx, {
408
+ reconcileCognitiveRouting: true,
409
+ materializeCognitiveRouting: !isPiFlowHost(pi?.host),
410
+ });
411
+ return;
412
+ }
413
+ if (!routingSession.hasController()) {
414
+ routingSession.beginExplicitReactivation();
454
415
  await applyLiveCapabilityStateForSession(ctx, { reconcileCognitiveRouting: true });
455
416
  }
456
- const controller = cognitiveRoutingController;
457
- if (!controller) {
417
+ const state = routingSession.snapshot().runtimeState;
418
+ if (!state) {
458
419
  ctx.ui?.notify?.("Cognitive Routing is unavailable for this session.", "warning");
459
420
  return;
460
421
  }
461
- const current = controller.state().activeProfile;
462
- const target = current === "reasoning" ? "standard" : "reasoning";
463
- const result = await controller.setManualProfile(target, "profile-shortcut");
422
+ const target = state.activeProfile === "reasoning" ? "standard" : "reasoning";
423
+ const result = await routingSession.setManualProfile(target, "profile-shortcut");
464
424
  if (result.status === "active") {
465
425
  ctx.ui?.notify?.(`Cognitive Routing manual hold set to ${target}.`, "info");
466
426
  } else {
@@ -470,43 +430,74 @@ export default function freeflow(pi) {
470
430
  },
471
431
  });
472
432
  pi.registerShortcut("ctrl+shift+a", {
473
- description: "Cycle the Cognitive Routing automatic standard/reasoning profile",
433
+ description: "Set Cognitive Routing to automatic Reasoning control",
474
434
  handler: async (ctx) => {
475
435
  if (typeof ctx?.isIdle === "function" && !ctx.isIdle()) {
476
436
  ctx.ui?.notify?.("Freeflow settings and profile changes are available only while Pi is idle.", "warning");
477
437
  return;
478
438
  }
479
- if (!cognitiveRoutingController) {
480
- await applyLiveCapabilityStateForSession(ctx, { reconcileCognitiveRouting: true });
481
- }
482
- const controller = cognitiveRoutingController;
483
- if (!controller) {
484
- ctx.ui?.notify?.("Cognitive Routing is unavailable for this session.", "warning");
439
+ const armedCycle = routingSession.hasController() ? undefined : routingSession.cycleArmedAutomatic();
440
+ if (armedCycle) {
441
+ if (!armedCycle.changed) {
442
+ ctx.ui?.notify?.("Cognitive Routing automatic control active.", "info");
443
+ return;
444
+ }
445
+ ctx.ui?.notify?.("Cognitive Routing automatic control active.", "info");
446
+ routingSession.beginExplicitReactivation();
447
+ await applyLiveCapabilityStateForSession(ctx, {
448
+ reconcileCognitiveRouting: true,
449
+ materializeCognitiveRouting: !isPiFlowHost(pi?.host),
450
+ });
485
451
  return;
486
452
  }
487
- const state = controller.state();
488
- const target = state.activeProfile === "reasoning" ? "standard" : "reasoning";
489
- const result =
490
- state.controlMode === "automatic" && state.activeProfile
491
- ? await controller.switchAutomaticProfile(
492
- target,
493
- `Cycle automatic profile to ${target}.`,
494
- "user",
495
- "profile-shortcut",
496
- )
497
- : await controller.setAutomaticControl("profile-shortcut");
453
+ if (!routingSession.hasController()) {
454
+ routingSession.beginExplicitReactivation();
455
+ await applyLiveCapabilityStateForSession(ctx, { reconcileCognitiveRouting: true });
456
+ }
457
+ const result = await routingSession.setAutomaticControl("profile-shortcut");
498
458
  if (result.status === "automatic") {
499
459
  ctx.ui?.notify?.("Cognitive Routing automatic control active.", "info");
500
- } else if (result.status === "active") {
501
- ctx.ui?.notify?.(`Cognitive Routing automatic profile set to ${result.profile}.`, "info");
502
460
  } else {
503
- ctx.ui?.notify?.(`Cognitive Routing could not cycle the automatic profile: ${result.reason}.`, "warning");
461
+ ctx.ui?.notify?.(`Cognitive Routing could not change control: ${result.reason}.`, "warning");
504
462
  }
505
463
  await applyLiveCapabilityStateForSession(ctx);
506
464
  },
507
465
  });
508
466
  }
509
467
 
468
+ if (!isPiFlowHost(pi?.host) && routingSession.supportsCognitiveRoutingRuntime()) {
469
+ pi.on("model_select", async (event, ctx) => {
470
+ if (!routingSession.hasController()) {
471
+ if (!routingSession.armed) return;
472
+ routingSession.clearArmed();
473
+ routingSession.observeArmedNativeChange(ctx, "Pi model selection");
474
+ await applyLiveCapabilityStateForSession(ctx);
475
+ return;
476
+ }
477
+ const result = await routingSession.observeNativeChange("Pi model selection", event?.source);
478
+ if (result.status !== "ignored") {
479
+ latestCognitiveRoutingContext = ctx;
480
+ providerSurfaceSnapshot = undefined;
481
+ await applyLiveCapabilityStateForSession(ctx);
482
+ }
483
+ });
484
+ pi.on("thinking_level_select", async (_event, ctx) => {
485
+ if (!routingSession.hasController()) {
486
+ if (!routingSession.armed) return;
487
+ routingSession.clearArmed();
488
+ routingSession.observeArmedNativeChange(ctx, "Pi thinking-level selection");
489
+ await applyLiveCapabilityStateForSession(ctx);
490
+ return;
491
+ }
492
+ const result = await routingSession.observeNativeChange("Pi thinking-level selection");
493
+ if (result.status !== "ignored") {
494
+ latestCognitiveRoutingContext = ctx;
495
+ providerSurfaceSnapshot = undefined;
496
+ await applyLiveCapabilityStateForSession(ctx);
497
+ }
498
+ });
499
+ }
500
+
510
501
  pi.on("resources_discover", async (event, ctx) => {
511
502
  const cwd = ctx?.cwd ?? event?.cwd ?? process.cwd();
512
503
  const snapshot =
@@ -517,16 +508,18 @@ export default function freeflow(pi) {
517
508
  if (!state.configured) {
518
509
  return { skillPaths: [freeflowSkillPath("setup-freeflow")] };
519
510
  }
520
- if (!state.enabled || !state.skills.effective) {
511
+ if (!state.enabled || !hasUsableMandatoryPrompts(snapshot.freeflowContext)) {
521
512
  return { skillPaths: [] };
522
513
  }
523
514
  return { skillPaths: freeflowModelSkillPaths(state) };
524
515
  });
525
516
 
526
- pi.on("session_start", async (_event, ctx) => {
517
+ pi.on("session_start", async (event, ctx) => {
518
+ routingSession.resetForSession(ctx, event);
527
519
  latestCognitiveRoutingContext = ctx;
528
520
  providerSurfaceSnapshot = undefined;
529
- restoreModeOverride(ctx);
521
+ runtimeStateRefreshRequired = true;
522
+ restoreSessionOverrides(ctx);
530
523
  freeflowContextRuntime = new FreeflowContextRuntime(ctx);
531
524
  contextVirtualizationRuntime = new ContextVirtualizationRuntime(pi, ctx, freeflowContextRuntime);
532
525
  conversationHistoryRuntime = new ConversationHistoryRuntime(
@@ -537,41 +530,51 @@ export default function freeflow(pi) {
537
530
  await contextVirtualizationRuntime.recover(ctx);
538
531
  const capabilityState = await readCapabilityState(ctx.cwd, ctx, pi?.host);
539
532
  await refreshRuntimeContext(capabilityState);
533
+ routingSession.ensureStartupInactive(ctx, capabilityState.cognitiveRouting?.effective === true);
534
+ routingSession.reconcileNativeEntries(ctx);
540
535
  const hasSessionState = sessionHasConversationOrRoutingState(ctx);
541
536
  const snapshot = await buildProviderSurface(ctx, hasSessionState);
542
- const cognitiveRoutingStartupPending =
543
- snapshot.capabilityState?.cognitiveRouting?.effective === true &&
537
+ if (
544
538
  !hasSessionState &&
545
- !startupSelectionSuppressesCognitiveRouting(ctx);
539
+ snapshot.capabilityState?.cognitiveRouting?.effective === true &&
540
+ routingSession.canAutoActivate()
541
+ ) {
542
+ routingSession.armFromCapability(snapshot.capabilityState.cognitiveRouting);
543
+ } else {
544
+ routingSession.clearArmed();
545
+ }
546
546
  registerContextToolForState(snapshot.capabilityState);
547
- setModeStatus(ctx, snapshot.modeState, snapshot.capabilityState, snapshot.cognitiveRoutingRuntime, {
548
- cognitiveRoutingStartupPending,
549
- });
550
- await applyCapabilityToolVisibility(pi, ctx, snapshot.capabilityState, cognitiveRoutingController);
547
+ const routingSnapshot = routingSession.snapshot();
548
+ setFreeflowStatus(
549
+ ctx,
550
+ snapshot.capabilityState,
551
+ snapshot.cognitiveRoutingRuntime ?? routingSnapshot.runtimeState,
552
+ snapshot.freeflowContext,
553
+ { startupSelectionSuppressed: routingSnapshot.startupSelectionSuppressed },
554
+ );
555
+ await applyCapabilityToolVisibility(pi, ctx, snapshot.capabilityState, routingSnapshot);
551
556
  });
552
557
 
553
558
  pi.on("agent_settled", async (_event, ctx) => {
554
- if (!cognitiveRoutingController) return undefined;
559
+ if (!routingSession.hasController()) return undefined;
555
560
  await applyLiveCapabilityStateForSession(ctx);
556
561
  return undefined;
557
562
  });
558
563
 
559
564
  pi.on("session_shutdown", async (event) => {
565
+ await routingSession.shutdown(event?.reason);
560
566
  providerSurfaceSnapshot = undefined;
561
- const controller = cognitiveRoutingController;
562
- cognitiveRoutingController = undefined;
563
567
  freeflowContextRuntime = undefined;
564
568
  contextVirtualizationRuntime = undefined;
565
569
  conversationHistoryRuntime = undefined;
566
- if (controller) await controller.shutdown(event?.reason);
567
570
  });
568
571
 
569
572
  pi.on("session_tree", async (_event, ctx) => {
570
573
  latestCognitiveRoutingContext = ctx;
571
574
  providerSurfaceSnapshot = undefined;
572
- restoreModeOverride(ctx);
573
- const controller = cognitiveRoutingController;
574
- if (controller) await controller.reconcileBranch();
575
+ runtimeStateRefreshRequired = true;
576
+ restoreSessionOverrides(ctx);
577
+ await routingSession.reconcileBranch();
575
578
  if (contextVirtualizationRuntime) {
576
579
  contextVirtualizationRuntime.setContext(ctx);
577
580
  await contextVirtualizationRuntime.recover(ctx);
@@ -583,21 +586,14 @@ export default function freeflow(pi) {
583
586
  pi.on("session_compact", async (_event, ctx) => {
584
587
  latestCognitiveRoutingContext = ctx;
585
588
  providerSurfaceSnapshot = undefined;
586
- const controller = cognitiveRoutingController;
587
- if (controller) await controller.reconcileBranch("session-compact");
589
+ runtimeStateRefreshRequired = true;
590
+ await routingSession.reconcileBranch("session-compact");
588
591
  if (contextVirtualizationRuntime) {
589
592
  contextVirtualizationRuntime.setContext(ctx);
590
593
  await contextVirtualizationRuntime.recover(ctx);
591
594
  }
592
595
  conversationHistoryRuntime?.setContext(ctx);
593
- const [modeState, capabilityState] = await Promise.all([
594
- readModeState(ctx.cwd),
595
- readCapabilityState(ctx.cwd, ctx, pi?.host),
596
- ]);
597
- await refreshRuntimeContext(capabilityState);
598
- registerContextToolForState(capabilityState);
599
- setModeStatus(ctx, modeState, capabilityState, cognitiveRoutingController?.state());
600
- await applyCapabilityToolVisibility(pi, ctx, capabilityState, cognitiveRoutingController);
596
+ await applyLiveCapabilityStateForSession(ctx);
601
597
  });
602
598
 
603
599
  pi.on("before_agent_start", async (event, ctx) => {
@@ -605,8 +601,11 @@ export default function freeflow(pi) {
605
601
  const snapshot = await buildProviderSurface(ctx, true);
606
602
  providerSurfaceSnapshot = { context: ctx, value: snapshot };
607
603
  registerContextToolForState(snapshot.capabilityState);
608
- setModeStatus(ctx, snapshot.modeState, snapshot.capabilityState, snapshot.cognitiveRoutingRuntime);
609
- await applyCapabilityToolVisibility(pi, ctx, snapshot.capabilityState, cognitiveRoutingController);
604
+ const routingSnapshot = routingSession.snapshot();
605
+ setFreeflowStatus(ctx, snapshot.capabilityState, snapshot.cognitiveRoutingRuntime, snapshot.freeflowContext, {
606
+ startupSelectionSuppressed: routingSnapshot.startupSelectionSuppressed,
607
+ });
608
+ await applyCapabilityToolVisibility(pi, ctx, snapshot.capabilityState, routingSnapshot);
610
609
  const freeflowRuntimeContext = runtimeContext(snapshot.freeflowContext, snapshot.capabilityState);
611
610
  const systemPrompt = freeflowRuntimeContext
612
611
  ? `${event.systemPrompt}\n\n${freeflowRuntimeContext}`
@@ -620,7 +619,6 @@ export default function freeflow(pi) {
620
619
  if (providerSurfaceSnapshot?.context === ctx) providerSurfaceSnapshot = undefined;
621
620
  const cognitiveRoutingRuntime = snapshot.cognitiveRoutingRuntime;
622
621
  const surfaceCapabilityState = snapshot.capabilityState;
623
- const surfaceModeState = snapshot.modeState;
624
622
  let changed = false;
625
623
  let messages = event.messages
626
624
  .map((message) => {
@@ -644,14 +642,19 @@ export default function freeflow(pi) {
644
642
  conversationHistoryRuntime.setContext(ctx);
645
643
  conversationHistoryRuntime.capture(surfaceCapabilityState.contextVirtualization?.effective === true);
646
644
  }
645
+ const forceRuntimeStateRefresh = runtimeStateRefreshRequired;
647
646
  const nextMessages = withFreeflowRuntimeState(
648
647
  messages,
649
- surfaceModeState,
650
648
  surfaceCapabilityState,
651
649
  cognitiveRoutingRuntime,
650
+ snapshot.freeflowContext,
651
+ { force: forceRuntimeStateRefresh },
652
652
  );
653
- changed = true;
654
- messages = nextMessages;
653
+ if (nextMessages !== messages) {
654
+ changed = true;
655
+ messages = nextMessages;
656
+ }
657
+ if (forceRuntimeStateRefresh) runtimeStateRefreshRequired = false;
655
658
  return changed ? { messages } : undefined;
656
659
  });
657
660
 
@@ -670,7 +673,7 @@ export default function freeflow(pi) {
670
673
 
671
674
  pi.on("tool_result", async (event, ctx) => {
672
675
  const toolName = typeof event?.toolName === "string" ? event.toolName : "";
673
- if (isPiFlowHost(pi?.host) && toolName === COGNITIVE_ROUTING_SWITCH_TOOL_NAME) {
676
+ if (routingSession.supportsCognitiveRoutingRuntime() && toolName === COGNITIVE_ROUTING_SWITCH_TOOL_NAME) {
674
677
  await applyLiveCapabilityStateForSession(ctx);
675
678
  }
676
679
  return undefined;
@@ -697,7 +700,7 @@ export default function freeflow(pi) {
697
700
 
698
701
  pi.registerCommand("freeflow", {
699
702
  description: "Open unified Freeflow settings or print compact status",
700
- getArgumentCompletions: (prefix) => freeflowCompletions(prefix, pi?.host),
703
+ getArgumentCompletions: (prefix) => freeflowCompletions(prefix, routingSession.supportsCognitiveRoutingRuntime()),
701
704
  handler: async (args, ctx) => {
702
705
  const contextInput = (args ?? "").trim();
703
706
  if (contextInput === "context" || contextInput.startsWith("context ")) {
@@ -711,23 +714,30 @@ export default function freeflow(pi) {
711
714
  );
712
715
  return;
713
716
  }
714
- if (isPiFlowHost(pi?.host) && /^profile(?:\s|$)/i.test((args ?? "").trim())) {
717
+ if (routingSession.supportsCognitiveRoutingRuntime() && /^profile(?:\s|$)/i.test((args ?? "").trim())) {
715
718
  const profileCommandContext = {
716
719
  isIdle: ctx?.isIdle,
717
720
  ui: ctx?.ui,
718
- history: (options) =>
719
- cognitiveRoutingController?.history(options) ?? readCognitiveRoutingHistory(ctx, options),
721
+ history: (options) => routingSession.history(options) ?? readCognitiveRoutingHistory(ctx, options),
720
722
  };
721
723
  const historyCommand = /^profile\s+history(?:\s|$)/i.test((args ?? "").trim());
722
724
  if (historyCommand) {
723
- await handleCognitiveRoutingProfileCommand(args, profileCommandContext, cognitiveRoutingController);
725
+ await handleCognitiveRoutingProfileCommand(args, profileCommandContext, routingSession.commandController());
724
726
  return;
725
727
  }
726
- if (!cognitiveRoutingController) {
728
+ if (!routingSession.hasController() && !routingSession.armed) {
729
+ routingSession.beginExplicitReactivation();
727
730
  await applyLiveCapabilityStateForSession(ctx, { reconcileCognitiveRouting: true });
728
731
  }
729
- if (await handleCognitiveRoutingProfileCommand(args, profileCommandContext, cognitiveRoutingController)) {
730
- await applyLiveCapabilityStateForSession(ctx);
732
+ const profileController = routingSession.commandController();
733
+ if (await handleCognitiveRoutingProfileCommand(args, profileCommandContext, profileController)) {
734
+ if (profileController) {
735
+ routingSession.beginExplicitReactivation();
736
+ await applyLiveCapabilityStateForSession(ctx, {
737
+ reconcileCognitiveRouting: routingSession.armed !== undefined,
738
+ materializeCognitiveRouting: routingSession.armed !== undefined && !isPiFlowHost(pi?.host),
739
+ });
740
+ }
731
741
  return;
732
742
  }
733
743
  }
@@ -740,7 +750,7 @@ export default function freeflow(pi) {
740
750
  });
741
751
  },
742
752
  pi,
743
- cognitiveRoutingController,
753
+ routingSession.commandController(),
744
754
  );
745
755
  },
746
756
  });