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