@hassangameryt/freeflow 0.7.1 → 0.7.3

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 (147) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.cursor-plugin/plugin.json +1 -1
  5. package/README.md +153 -128
  6. package/capabilities/cognitive-routing/SKILL.md +126 -274
  7. package/command-surface.json +7 -1
  8. package/gemini-extension.json +1 -1
  9. package/package.json +4 -4
  10. package/pi-extension/dist/cognitive-routing-v2/budget.js +42 -0
  11. package/pi-extension/dist/cognitive-routing-v2/config.js +136 -0
  12. package/pi-extension/dist/cognitive-routing-v2/projection.js +330 -0
  13. package/pi-extension/dist/cognitive-routing-v2/render.js +252 -0
  14. package/pi-extension/dist/cognitive-routing-v2/runtime.js +2181 -0
  15. package/pi-extension/dist/cognitive-routing-v2/schemas.js +173 -0
  16. package/pi-extension/dist/cognitive-routing-v2/state.js +828 -0
  17. package/pi-extension/dist/cognitive-routing-v2/tools.js +60 -0
  18. package/pi-extension/dist/cognitive-routing-v2/types.js +48 -0
  19. package/pi-extension/dist/context-virtualization/runtime.js +29 -7
  20. package/pi-extension/dist/freeflow-context/tool.js +12 -6
  21. package/pi-extension/dist/index.js +302 -626
  22. package/pi-extension/dist/provider-support/astra/adapter.js +110 -0
  23. package/pi-extension/dist/provider-support/astra/history.js +125 -0
  24. package/pi-extension/dist/provider-support/astra/index.js +11 -0
  25. package/pi-extension/dist/provider-support/astra/session-state.js +114 -0
  26. package/pi-extension/dist/provider-support/index.js +4 -0
  27. package/pi-extension/dist/runtime/request-history.js +216 -0
  28. package/pi-extension/dist/runtime/runtime-context.js +85 -16
  29. package/pi-extension/dist/session-sources/events.js +217 -0
  30. package/pi-extension/dist/session-sources/provenance.js +32 -0
  31. package/pi-extension/dist/session-sources/read-only-session.js +148 -0
  32. package/pi-extension/dist/session-sources/sources.js +277 -0
  33. package/pi-extension/dist/settings/settings-ui.js +274 -147
  34. package/pi-extension/src/cognitive-routing-v2/budget.ts +42 -0
  35. package/pi-extension/src/cognitive-routing-v2/config.ts +173 -0
  36. package/pi-extension/src/cognitive-routing-v2/projection.ts +375 -0
  37. package/pi-extension/src/cognitive-routing-v2/render.ts +277 -0
  38. package/pi-extension/src/cognitive-routing-v2/runtime.ts +2241 -0
  39. package/pi-extension/src/cognitive-routing-v2/schemas.ts +180 -0
  40. package/pi-extension/src/cognitive-routing-v2/state.ts +839 -0
  41. package/pi-extension/src/cognitive-routing-v2/tools.ts +62 -0
  42. package/pi-extension/src/cognitive-routing-v2/types.ts +254 -0
  43. package/pi-extension/src/context-virtualization/runtime.ts +30 -8
  44. package/pi-extension/src/freeflow-context/tool.ts +12 -6
  45. package/pi-extension/src/index.ts +308 -675
  46. package/pi-extension/src/provider-support/astra/adapter.ts +108 -0
  47. package/pi-extension/src/provider-support/astra/history.ts +145 -0
  48. package/pi-extension/src/provider-support/astra/index.ts +12 -0
  49. package/pi-extension/src/provider-support/astra/session-state.ts +113 -0
  50. package/pi-extension/src/provider-support/index.ts +5 -0
  51. package/pi-extension/src/runtime/request-history.ts +243 -0
  52. package/pi-extension/src/runtime/runtime-context.ts +100 -17
  53. package/pi-extension/src/session-sources/events.ts +230 -0
  54. package/pi-extension/src/session-sources/provenance.ts +39 -0
  55. package/pi-extension/src/session-sources/read-only-session.ts +170 -0
  56. package/pi-extension/src/session-sources/sources.ts +334 -0
  57. package/pi-extension/src/settings/settings-ui.ts +311 -158
  58. package/pi-extension/tests/cache-reuse/history.test.js +117 -0
  59. package/pi-extension/tests/cache-reuse/native.test.js +91 -0
  60. package/pi-extension/tests/cache-reuse/settings.test.js +211 -0
  61. package/pi-extension/tests/cognitive-routing-v2/communication.test.js +577 -0
  62. package/pi-extension/tests/cognitive-routing-v2/compaction-input.test.js +183 -0
  63. package/pi-extension/tests/cognitive-routing-v2/delegation.test.js +557 -0
  64. package/pi-extension/tests/cognitive-routing-v2/native.test.js +474 -0
  65. package/pi-extension/tests/cognitive-routing-v2/performance.test.js +87 -0
  66. package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +300 -0
  67. package/pi-extension/tests/cognitive-routing-v2/projection.test.js +241 -0
  68. package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +1034 -0
  69. package/pi-extension/tests/cognitive-routing-v2/render.test.js +244 -0
  70. package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +423 -0
  71. package/pi-extension/tests/cognitive-routing-v2/stabilization.test.js +521 -0
  72. package/pi-extension/tests/cognitive-routing-v2/state.test.js +586 -0
  73. package/pi-extension/tests/context-virtualization/extension.test.js +33 -7
  74. package/pi-extension/tests/context-virtualization/runtime.test.js +17 -3
  75. package/pi-extension/tests/fixtures/routing-native.js +191 -0
  76. package/pi-extension/tests/integration/mode-free-core.test.js +6 -4
  77. package/pi-extension/tests/integration/pi-extension.test.js +655 -59
  78. package/pi-extension/tests/integration/prompt-architecture.test.js +127 -30
  79. package/pi-extension/tests/integration/unaffected-regressions.test.js +78 -11
  80. package/pi-extension/tests/provider-support/astra-boundaries.test.js +75 -0
  81. package/pi-extension/tests/provider-support/astra.test.js +306 -0
  82. package/pi-extension/tests/provider-support/cli.test.js +92 -0
  83. package/pi-extension/tests/provider-support/native.test.js +183 -0
  84. package/plugin.json +1 -1
  85. package/runtime/prompts/cognitive-routing.md +4 -5
  86. package/runtime/prompts/core.md +51 -45
  87. package/runtime/prompts/interaction-contract.md +5 -5
  88. package/skills/action-selection/SKILL.md +57 -97
  89. package/skills/design-for-depth/SKILL.md +58 -193
  90. package/skills/design-for-depth/references/implementation-decisions.md +76 -0
  91. package/skills/diagnose-failure/SKILL.md +64 -196
  92. package/skills/discuss/SKILL.md +64 -123
  93. package/skills/discuss/references/discussion-continuity.md +7 -3
  94. package/skills/execute-work/SKILL.md +69 -88
  95. package/skills/execute-work/references/execute-work-edges.md +9 -3
  96. package/skills/handoff/SKILL.md +44 -54
  97. package/skills/handoff/references/handoff-templates.md +15 -6
  98. package/skills/review-artifact/SKILL.md +80 -127
  99. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +1 -1
  100. package/skills/review-work/SKILL.md +65 -115
  101. package/skills/review-work/references/independent-work-reviewer-contract.md +1 -1
  102. package/skills/simplify-code/SKILL.md +47 -100
  103. package/skills/track-work/SKILL.md +128 -119
  104. package/skills/track-work/references/working-record-format.md +14 -2
  105. package/skills/verify-work/SKILL.md +58 -62
  106. package/skills/verify-work/references/integration-evidence.md +4 -2
  107. package/skills/verify-work/references/test-design.md +89 -0
  108. package/skills/workflow/SKILL.md +73 -119
  109. package/skills/write-plan/SKILL.md +46 -77
  110. package/skills/write-plan/references/plan-shapes.md +5 -5
  111. package/skills/write-spec/SKILL.md +51 -63
  112. package/skills/write-spec/references/artifact-standards.md +2 -2
  113. package/skills/write-spec/references/decision-records.md +3 -2
  114. package/skills/write-spec/references/spec-shapes.md +15 -0
  115. package/pi-extension/dist/cognitive-routing/commands.js +0 -96
  116. package/pi-extension/dist/cognitive-routing/config.js +0 -232
  117. package/pi-extension/dist/cognitive-routing/controller.js +0 -1292
  118. package/pi-extension/dist/cognitive-routing/history.js +0 -451
  119. package/pi-extension/dist/cognitive-routing/host.js +0 -188
  120. package/pi-extension/dist/cognitive-routing/pi-routing-session.js +0 -414
  121. package/pi-extension/dist/cognitive-routing/pi-session-control.js +0 -320
  122. package/pi-extension/dist/cognitive-routing/runtime.js +0 -43
  123. package/pi-extension/dist/cognitive-routing/tool.js +0 -212
  124. package/pi-extension/dist/cognitive-routing/types.js +0 -6
  125. package/pi-extension/src/cognitive-routing/commands.ts +0 -129
  126. package/pi-extension/src/cognitive-routing/config.ts +0 -303
  127. package/pi-extension/src/cognitive-routing/controller.ts +0 -1628
  128. package/pi-extension/src/cognitive-routing/history.ts +0 -650
  129. package/pi-extension/src/cognitive-routing/host.ts +0 -266
  130. package/pi-extension/src/cognitive-routing/pi-routing-session.ts +0 -538
  131. package/pi-extension/src/cognitive-routing/pi-session-control.ts +0 -444
  132. package/pi-extension/src/cognitive-routing/runtime.ts +0 -57
  133. package/pi-extension/src/cognitive-routing/tool.ts +0 -273
  134. package/pi-extension/src/cognitive-routing/types.ts +0 -89
  135. package/pi-extension/tests/cognitive-routing/commands.test.js +0 -134
  136. package/pi-extension/tests/cognitive-routing/controller.test.js +0 -1128
  137. package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +0 -1142
  138. package/pi-extension/tests/cognitive-routing/history.test.js +0 -274
  139. package/pi-extension/tests/cognitive-routing/host-identity.test.js +0 -63
  140. package/pi-extension/tests/cognitive-routing/pi-normal-lifecycle.test.js +0 -434
  141. package/pi-extension/tests/cognitive-routing/pi-session-control.test.js +0 -174
  142. package/pi-extension/tests/cognitive-routing/pi-session-history.test.js +0 -91
  143. package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +0 -279
  144. package/pi-extension/tests/cognitive-routing/tool.test.js +0 -230
  145. package/skills/tdd/SKILL.md +0 -143
  146. package/skills/tdd/references/test-design.md +0 -96
  147. /package/pi-extension/tests/{cognitive-routing/host-fixture.js → fixtures/pi-host.js} +0 -0
@@ -1,17 +1,11 @@
1
1
  import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
-
3
- import {
4
- COGNITIVE_ROUTING_CONTROL_ENTRY,
5
- COGNITIVE_ROUTING_INACTIVE_ENTRY,
6
- COGNITIVE_ROUTING_INTENT_ENTRY,
7
- } from "./cognitive-routing/controller.js";
8
- import { PiRoutingSession, type PiRoutingSessionSnapshot } from "./cognitive-routing/pi-routing-session.js";
9
- import {
10
- cognitiveRoutingProfileCompletions,
11
- handleCognitiveRoutingProfileCommand,
12
- } from "./cognitive-routing/commands.js";
13
- import { registerCognitiveRoutingHistoryTool, registerCognitiveRoutingTool } from "./cognitive-routing/tool.js";
14
- import { readCognitiveRoutingHistory } from "./cognitive-routing/history.js";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { RequestHistory } from "./runtime/request-history.js";
5
+ import { registerProviderSupport } from "./provider-support/index.js";
6
+ import { RoutingRuntime } from "./cognitive-routing-v2/runtime.js";
7
+ import { workersForDelegation, type Profile } from "./cognitive-routing-v2/types.js";
8
+ import { applyRoutingToolVisibility, registerRoutingTools } from "./cognitive-routing-v2/tools.js";
15
9
  import { ConversationHistoryRuntime } from "./conversation-history/runtime.js";
16
10
  import { FreeflowContextRuntime } from "./freeflow-context/runtime.js";
17
11
  import { CONTEXT_VIRTUALIZATION_TOOL_NAME, registerFreeflowContextTool } from "./freeflow-context/tool.js";
@@ -21,7 +15,6 @@ import { handleFreeflowCommand } from "./settings/settings-ui.js";
21
15
  import { isPiFlowHost } from "./runtime/runtime-identity.js";
22
16
  import {
23
17
  CONTRIBUTOR_COMMANDS,
24
- COGNITIVE_ROUTING_SWITCH_TOOL_NAME,
25
18
  WORKFLOW_COMMANDS,
26
19
  freeflowModelSkillPaths,
27
20
  freeflowSkillPath,
@@ -31,727 +24,367 @@ import {
31
24
  readCapabilityState,
32
25
  refreshRuntimeContext,
33
26
  restoreSessionOverrides,
34
- runtimeContext,
27
+ stableRuntimeContext,
28
+ STABLE_FREEFLOW_SURFACE,
35
29
  filterBootstrapMessage,
36
30
  setFreeflowStatus,
37
31
  skillPrompt,
38
32
  withFreeflowRuntimeState,
39
33
  } from "./runtime/runtime-context.js";
40
34
 
41
- function unavailableCapability(capability: any, code: string, message: string): any {
42
- return {
43
- ...capability,
44
- effective: false,
45
- blockingReason: { code, message },
46
- };
47
- }
48
-
49
- function modelFacingCapabilityState(
50
- capabilityState: any,
51
- freeflowContext: any,
52
- routingSnapshot: PiRoutingSessionSnapshot,
53
- cognitiveRoutingActivationFailed = false,
54
- ): any {
55
- const surfaceState = {
56
- ...capabilityState,
57
- contextVirtualization: { ...capabilityState?.contextVirtualization },
58
- conversationHistory: { ...capabilityState?.conversationHistory },
59
- cognitiveRouting: { ...capabilityState?.cognitiveRouting },
60
- };
61
- if (surfaceState.enabled !== true) return surfaceState;
62
-
63
- const markUnavailable = (key: string, message: string) => {
64
- if (surfaceState[key]?.effective === true) {
65
- surfaceState[key] = unavailableCapability(surfaceState[key], "unavailable", message);
66
- }
67
- };
68
-
69
- if (!hasUsableMandatoryPrompts(freeflowContext)) {
70
- for (const key of ["contextVirtualization", "conversationHistory", "cognitiveRouting"]) {
71
- markUnavailable(key, "Mandatory Freeflow prompt is unavailable.");
72
- }
73
- return surfaceState;
74
- }
75
-
76
- if (
77
- surfaceState.contextVirtualization?.effective === true &&
78
- !isPromptAvailable(freeflowContext.contextVirtualizationPrompt)
79
- ) {
80
- markUnavailable("contextVirtualization", "Context Virtualization prompt is unavailable.");
81
- }
82
- if (
83
- surfaceState.conversationHistory?.effective === true &&
84
- !isPromptAvailable(freeflowContext.conversationHistoryPrompt)
85
- ) {
86
- markUnavailable("conversationHistory", "Conversation History prompt is unavailable.");
87
- }
88
-
89
- if (surfaceState.cognitiveRouting?.effective === true) {
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") {
104
- surfaceState.cognitiveRouting = unavailableCapability(
105
- surfaceState.cognitiveRouting,
106
- "runtime_inactive",
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.",
114
- );
115
- } else if (cognitiveRoutingActivationFailed || controllerState?.effective === false) {
116
- surfaceState.cognitiveRouting = unavailableCapability(
117
- surfaceState.cognitiveRouting,
118
- "unavailable",
119
- "Cognitive Routing could not activate for this session.",
120
- );
121
- } else if (!isPromptAvailable(freeflowContext.cognitiveRoutingPrompt)) {
122
- markUnavailable("cognitiveRouting", "Cognitive Routing prompt is unavailable.");
123
- }
124
- }
125
-
126
- return surfaceState;
127
- }
128
-
129
- type SessionEntriesContext = {
130
- sessionManager?: {
131
- getBranch?: () => readonly unknown[];
132
- getEntries?: () => readonly unknown[];
133
- };
134
- };
135
-
136
- function sessionHasConversationOrRoutingState(ctx: SessionEntriesContext | undefined): boolean {
137
- try {
138
- const entries = ctx?.sessionManager?.getBranch?.() ?? ctx?.sessionManager?.getEntries?.() ?? [];
139
- if (!Array.isArray(entries)) return false;
140
- return entries.some((entry: unknown) => {
141
- if (!entry || typeof entry !== "object") return false;
142
- const record = entry as { type?: unknown; customType?: unknown };
143
- if (
144
- typeof record.type === "string" &&
145
- ["message", "custom_message", "compaction", "branch_summary"].includes(record.type)
146
- ) {
147
- return true;
148
- }
149
- return (
150
- record.type === "custom" &&
151
- (record.customType === COGNITIVE_ROUTING_INTENT_ENTRY ||
152
- record.customType === COGNITIVE_ROUTING_CONTROL_ENTRY ||
153
- record.customType === COGNITIVE_ROUTING_INACTIVE_ENTRY)
154
- );
155
- });
156
- } catch {
157
- return false;
158
- }
159
- }
160
-
161
- async function applyCapabilityToolVisibility(
162
- pi: any,
163
- ctx: any,
164
- capabilityState = undefined,
165
- routingSnapshot: PiRoutingSessionSnapshot = {
166
- status: "available",
167
- disposition: "available",
168
- startupSelectionSuppressed: false,
169
- },
170
- ): Promise<void> {
171
- if (typeof pi?.setActiveTools !== "function" || typeof pi?.getAllTools !== "function") return;
172
-
173
- const state = capabilityState ?? (await readCapabilityState(ctx.cwd, ctx, pi?.host));
174
- const allToolNames = pi
175
- .getAllTools()
176
- .map((tool: any) => tool?.name)
177
- .filter(Boolean);
178
- const allToolNameSet = new Set(allToolNames);
179
- const currentActive = typeof pi.getActiveTools === "function" ? pi.getActiveTools() : undefined;
180
- const active = new Set(
181
- (Array.isArray(currentActive) ? currentActive : allToolNames).filter((name: string) => allToolNameSet.has(name)),
182
- );
183
-
184
- if (allToolNameSet.has(COGNITIVE_ROUTING_SWITCH_TOOL_NAME)) {
185
- const controllerState = routingSnapshot.runtimeState;
186
- const routingToolActive =
187
- state.cognitiveRouting?.effective === true &&
188
- controllerState?.effective === true &&
189
- controllerState.controlMode === "automatic";
190
- if (routingToolActive) active.add(COGNITIVE_ROUTING_SWITCH_TOOL_NAME);
191
- else active.delete(COGNITIVE_ROUTING_SWITCH_TOOL_NAME);
192
- }
193
-
194
- if (allToolNameSet.has(CONTEXT_VIRTUALIZATION_TOOL_NAME)) {
195
- if (state.contextVirtualization?.effective === true || state.conversationHistory?.effective === true) {
196
- active.add(CONTEXT_VIRTUALIZATION_TOOL_NAME);
197
- } else {
198
- active.delete(CONTEXT_VIRTUALIZATION_TOOL_NAME);
199
- }
200
- }
201
-
202
- pi.setActiveTools([...active]);
203
- }
204
-
205
- function disabledToolCall(toolName: string, capability: string) {
206
- const command = capability === "freeflow" ? "/freeflow settings" : `/${capability} settings`;
207
- return {
208
- block: true,
209
- reason: `${toolName} is disabled by Freeflow config. Configure ${capability} with ${command}.`,
210
- };
211
- }
212
-
213
- function freeflowCompletions(prefix: string | undefined, cognitiveRoutingAvailable = false) {
214
- const query = prefix ?? "";
215
- if (query.startsWith("settings ")) {
216
- const settingsQuery = query.slice("settings ".length);
217
- return [
218
- { value: "session", label: "session", description: "Override Freeflow for this Pi session" },
219
- { value: "local", label: "local", description: "Edit personal overrides for this repository" },
220
- { value: "repo", label: "repo", description: "Edit shared repository settings" },
221
- ]
222
- .filter((item) => item.value.startsWith(settingsQuery))
223
- .map((item) => ({ ...item, value: `settings ${item.value}` }));
224
- }
225
- if (cognitiveRoutingAvailable && query.startsWith("profile ")) {
226
- return cognitiveRoutingProfileCompletions(query.slice("profile ".length)).map((item) => ({
227
- ...item,
228
- value: `profile ${item.value}`,
229
- }));
230
- }
231
- if (query.startsWith("context ")) {
232
- const contextQuery = query.slice("context ".length);
233
- return [
234
- { value: "status", label: "status", description: "Show Freeflow Context state" },
235
- { value: "list", label: "list", description: "List archived context projections" },
236
- { value: "restore", label: "restore", description: "Restore one or more context references" },
237
- { value: "reset all", label: "reset all", description: "Reset projection decisions on the active branch" },
238
- ]
239
- .filter((item) => item.value.startsWith(contextQuery))
240
- .map((item) => ({ ...item, value: `context ${item.value}` }));
241
- }
242
- return [
243
- { value: "settings", label: "settings", description: "Open personal override settings" },
244
- { value: "status", label: "status", description: "Show effective Freeflow state" },
245
- { value: "context", label: "context", description: "Inspect Freeflow Context" },
246
- ...(cognitiveRoutingAvailable
247
- ? [{ value: "profile", label: "profile", description: "Hold or release Cognitive Routing profile control" }]
248
- : []),
249
- { value: "enable", label: "enable", description: "Enable Freeflow for this repository" },
250
- { value: "disable", label: "disable", description: "Disable Freeflow for this repository" },
251
- ].filter((item) => item.value.startsWith(query));
252
- }
253
-
254
- function bypassCompletions(prefix: string | undefined) {
255
- const query = prefix ?? "";
256
- return [
257
- { value: "next", label: "next", description: "Skip one optional step" },
258
- { value: "task", label: "task", description: "Reduce optional pressure for the current task" },
259
- ].filter((item) => item.value.startsWith(query));
260
- }
261
-
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
- ) {
270
- const state = await readCapabilityState(ctx.cwd, ctx, pi?.host);
35
+ type FreeflowAPI = ExtensionAPI & { host?: unknown };
36
+ async function sendSkillCommand(pi: FreeflowAPI, ctx: ExtensionCommandContext, skill: string, args?: string) {
37
+ const state = await readCapabilityState(ctx.cwd, ctx, pi.host);
271
38
  if (skill === "setup-freeflow" && !state.configured) {
272
39
  await pi.sendUserMessage(skillPrompt(skill, args), { expandPromptTemplates: true });
273
40
  return;
274
41
  }
275
- if (!state.configured) {
276
- ctx.ui.notify("Freeflow is installed but this repo is not set up. Run /setup-freeflow first.", "warning");
42
+ if (!state.configured || !state.enabled) {
43
+ ctx.ui.notify("Run /setup-freeflow or enable Freeflow before dispatching this skill.", "warning");
277
44
  return;
278
45
  }
279
- if (!state.enabled) {
280
- ctx.ui.notify(
281
- "Freeflow is disabled for this repo. Use /freeflow enable or /freeflow settings to re-enable it.",
282
- "warning",
283
- );
284
- return;
285
- }
286
- const freeflowContext = await getRuntimeContext(state);
287
- if (!hasUsableMandatoryPrompts(freeflowContext)) {
46
+ const prompts = await getRuntimeContext(state);
47
+ if (!hasUsableMandatoryPrompts(prompts)) {
288
48
  ctx.ui.notify("Freeflow core prompts are unavailable; the requested skill cannot be dispatched.", "warning");
289
49
  return;
290
50
  }
291
51
  await pi.sendUserMessage(skillPrompt(skill, args), { expandPromptTemplates: true });
292
52
  }
293
53
 
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) });
298
- let latestCognitiveRoutingContext: any;
299
- let providerSurfaceSnapshot: { context: any; value: any } | undefined;
300
- let runtimeStateRefreshRequired = true;
301
- let freeflowContextRuntime: FreeflowContextRuntime | undefined;
302
- let contextVirtualizationRuntime: ContextVirtualizationRuntime | undefined;
303
- let conversationHistoryRuntime: ConversationHistoryRuntime | undefined;
304
- const registerContextToolForState = (capabilityState: any) => {
54
+ function freeflowCompletions(prefix: string | undefined, routingAvailable: boolean) {
55
+ const query = prefix ?? "";
56
+ const choices = query.startsWith("settings ")
57
+ ? [
58
+ ["settings session", "session", "Override Freeflow for this Pi session"],
59
+ ["settings local", "local", "Edit personal overrides for this repository"],
60
+ ["settings repo", "repo", "Edit shared repository settings"],
61
+ ]
62
+ : query.startsWith("profile ") && routingAvailable
63
+ ? [
64
+ ["profile coordinator", "coordinator", "Hold Coordinator manually"],
65
+ ["profile helper", "helper", "Hold Helper manually when enabled"],
66
+ ["profile executor", "executor", "Hold Executor manually when enabled"],
67
+ ["profile auto", "auto", "Return to automatic Coordinator reconciliation"],
68
+ ["profile history", "history", "Read routing observations"],
69
+ ]
70
+ : query.startsWith("context ")
71
+ ? [
72
+ ["context status", "status", "Show Freeflow Context state"],
73
+ ["context list", "list", "List archived context projections"],
74
+ ["context restore", "restore", "Restore one or more context references"],
75
+ ["context reset all", "reset all", "Reset projection decisions on the active branch"],
76
+ ]
77
+ : [
78
+ ["settings", "settings", "Open personal override settings"],
79
+ ["status", "status", "Show effective Freeflow state"],
80
+ ["context", "context", "Inspect Freeflow Context"],
81
+ ...(routingAvailable
82
+ ? [
83
+ ["profile", "profile", "Hold or release Cognitive Routing profile control"],
84
+ ["resume", "resume", "Resume the current saved routing responsibility"],
85
+ ]
86
+ : []),
87
+ ["enable", "enable", "Enable Freeflow for this repository"],
88
+ ["disable", "disable", "Disable Freeflow for this repository"],
89
+ ];
90
+ return choices
91
+ .filter(([value]) => value.startsWith(query))
92
+ .map(([value, label, description]) => ({ value, label, description }));
93
+ }
94
+
95
+ export default function freeflow(pi: FreeflowAPI) {
96
+ registerProviderSupport(pi);
97
+ const api = pi as any;
98
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
99
+ const routing = new RoutingRuntime(api, [packageRoot]);
100
+ const requestHistory = new RequestHistory(api);
101
+ let capability: any;
102
+ let prompts: any;
103
+ let context: FreeflowContextRuntime | undefined;
104
+ let virtualization: ContextVirtualizationRuntime | undefined;
105
+ let history: ConversationHistoryRuntime | undefined;
106
+ let refreshState = true;
107
+ let sessionContext: any;
108
+ let surfaceGeneration = 0;
109
+
110
+ const unavailable = (state: any, message: string) => ({
111
+ ...state,
112
+ effective: false,
113
+ blockingReason: { code: "unavailable", message },
114
+ });
115
+ async function loadSurface(ctx: any) {
116
+ const generation = surfaceGeneration;
117
+ const next = await readCapabilityState(ctx.cwd, ctx, pi.host);
118
+ const loaded = await getRuntimeContext(STABLE_FREEFLOW_SURFACE);
119
+ if (generation !== surfaceGeneration) throw new Error("Discarded surface preparation for a replaced session.");
120
+ if (!hasUsableMandatoryPrompts(loaded)) {
121
+ for (const key of ["cognitiveRouting", "contextVirtualization", "conversationHistory"])
122
+ next[key] = unavailable(next[key], "Mandatory Freeflow prompts are unavailable.");
123
+ }
124
+ if (next.cognitiveRouting.effective && !isPromptAvailable(loaded.cognitiveRoutingPrompt))
125
+ next.cognitiveRouting = unavailable(next.cognitiveRouting, "Routing bootstrap cue is unavailable.");
126
+ if (next.contextVirtualization.effective && !isPromptAvailable(loaded.contextVirtualizationPrompt))
127
+ next.contextVirtualization = unavailable(
128
+ next.contextVirtualization,
129
+ "Context Virtualization guidance is unavailable.",
130
+ );
131
+ if (next.conversationHistory.effective && !isPromptAvailable(loaded.conversationHistoryPrompt))
132
+ next.conversationHistory = unavailable(next.conversationHistory, "Conversation History guidance is unavailable.");
133
+ // The existing reduction engine is not the future Context Control port. Do not pretend
134
+ // its independent transform ordering has been qualified with the new projection.
135
+ if (
136
+ next.cognitiveRouting.effective &&
137
+ next.cognitiveRouting.projection &&
138
+ (next.contextVirtualization.effective || next.conversationHistory.effective)
139
+ )
140
+ next.cognitiveRouting = unavailable(
141
+ next.cognitiveRouting,
142
+ "New routing projection with legacy context transforms is not qualified. Disable that combination explicitly.",
143
+ );
144
+ if (next.cognitiveRouting.enabled && isPiFlowHost(pi.host))
145
+ next.cognitiveRouting = unavailable(
146
+ next.cognitiveRouting,
147
+ "The redesigned PiFlow adapter requires separate qualification.",
148
+ );
149
+ capability = next;
150
+ prompts = loaded;
151
+ sessionContext = ctx;
152
+ return next;
153
+ }
154
+ function contextTools() {
305
155
  registerFreeflowContextTool(
306
- toolRegistrar,
307
- () => contextVirtualizationRuntime,
308
- () => conversationHistoryRuntime,
156
+ api,
157
+ () => virtualization,
158
+ () => history,
309
159
  {
310
- contextVirtualization: capabilityState?.contextVirtualization?.effective === true,
311
- conversationHistory: capabilityState?.conversationHistory?.effective === true,
160
+ contextVirtualization: capability?.contextVirtualization?.effective === true,
161
+ conversationHistory: capability?.conversationHistory?.effective === true,
312
162
  },
313
163
  );
314
- };
315
- const buildProviderSurface = async (ctx: any, activateCognitiveRouting = false): Promise<any> => {
316
- const capabilityState = await readCapabilityState(ctx.cwd, ctx, pi?.host);
317
- const freeflowContext = await getRuntimeContext(capabilityState);
318
- const promptCapabilityState = modelFacingCapabilityState(
319
- capabilityState,
320
- freeflowContext,
321
- routingSession.snapshot(),
322
- );
323
- let activationAttempted = false;
324
- if (
325
- activateCognitiveRouting &&
326
- routingSession.canAutoActivate() &&
327
- (!routingSession.hasController() || promptCapabilityState.cognitiveRouting?.effective !== true)
328
- ) {
329
- activationAttempted = promptCapabilityState.cognitiveRouting?.effective === true;
330
- try {
331
- await routingSession.reconcileController(
332
- ctx,
333
- promptCapabilityState,
334
- routingSession.armed ? { ...routingSession.armed } : undefined,
335
- );
336
- if (routingSession.hasController()) {
337
- routingSession.clearArmed();
338
- if (routingSession.snapshot().status === "active") routingSession.markActive();
339
- }
340
- } catch {
341
- routingSession.clearController();
342
- }
164
+ if (api.getActiveTools && api.setActiveTools) {
165
+ const current = new Set<string>(api.getActiveTools());
166
+ current.add(CONTEXT_VIRTUALIZATION_TOOL_NAME);
167
+ const next = [...current];
168
+ if (JSON.stringify(next) !== JSON.stringify(api.getActiveTools())) api.setActiveTools(next);
343
169
  }
344
- const surfaceCapabilityState = modelFacingCapabilityState(
345
- promptCapabilityState,
346
- freeflowContext,
347
- routingSession.snapshot(),
348
- activationAttempted && !routingSession.hasController(),
349
- );
350
- const routingSnapshot = routingSession.snapshot();
351
- return {
352
- capabilityState: surfaceCapabilityState,
353
- freeflowContext,
354
- cognitiveRoutingRuntime: routingSnapshot.runtimeState,
355
- configuredCapabilityState: capabilityState,
356
- };
357
- };
358
- const applyLiveCapabilityStateForSession = async (
359
- ctx: any,
360
- options: { reconcileCognitiveRouting?: boolean; materializeCognitiveRouting?: boolean } = {},
361
- ): Promise<void> => {
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);
377
- };
378
-
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
- );
386
- });
387
170
  }
171
+ function status(ctx: any) {
172
+ applyRoutingToolVisibility(api, routing, capability?.cognitiveRouting?.effective === true);
173
+ setFreeflowStatus(ctx, capability, routing.state(), prompts);
174
+ }
175
+ async function update(ctx: any) {
176
+ const next = await loadSurface(ctx);
177
+ await routing.refresh(ctx, next.cognitiveRouting);
178
+ contextTools();
179
+ status(ctx);
180
+ refreshState = true;
181
+ }
182
+
183
+ registerRoutingTools(api, routing);
388
184
  registerFreeflowContextTool(
389
- toolRegistrar,
390
- () => contextVirtualizationRuntime,
391
- () => conversationHistoryRuntime,
185
+ api,
186
+ () => virtualization,
187
+ () => history,
392
188
  { contextVirtualization: false, conversationHistory: false },
393
189
  );
394
190
 
395
- if (routingSession.supportsCognitiveRoutingRuntime() && typeof pi.registerShortcut === "function") {
396
- pi.registerShortcut("ctrl+shift+r", {
397
- description: "Cycle the Cognitive Routing manual standard/reasoning hold",
398
- handler: async (ctx) => {
399
- if (typeof ctx?.isIdle === "function" && !ctx.isIdle()) {
400
- ctx.ui?.notify?.("Freeflow settings and profile changes are available only while Pi is idle.", "warning");
401
- return;
402
- }
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();
415
- await applyLiveCapabilityStateForSession(ctx, { reconcileCognitiveRouting: true });
416
- }
417
- const state = routingSession.snapshot().runtimeState;
418
- if (!state) {
419
- ctx.ui?.notify?.("Cognitive Routing is unavailable for this session.", "warning");
420
- return;
421
- }
422
- const target = state.activeProfile === "reasoning" ? "standard" : "reasoning";
423
- const result = await routingSession.setManualProfile(target, "profile-shortcut");
424
- if (result.status === "active") {
425
- ctx.ui?.notify?.(`Cognitive Routing manual hold set to ${target}.`, "info");
426
- } else {
427
- ctx.ui?.notify?.(`Cognitive Routing could not set the manual profile: ${result.reason}.`, "warning");
428
- }
429
- await applyLiveCapabilityStateForSession(ctx);
430
- },
431
- });
432
- pi.registerShortcut("ctrl+shift+a", {
433
- description: "Set Cognitive Routing to automatic Reasoning control",
434
- handler: async (ctx) => {
435
- if (typeof ctx?.isIdle === "function" && !ctx.isIdle()) {
436
- ctx.ui?.notify?.("Freeflow settings and profile changes are available only while Pi is idle.", "warning");
437
- return;
438
- }
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
- });
451
- return;
452
- }
453
- if (!routingSession.hasController()) {
454
- routingSession.beginExplicitReactivation();
455
- await applyLiveCapabilityStateForSession(ctx, { reconcileCognitiveRouting: true });
456
- }
457
- const result = await routingSession.setAutomaticControl("profile-shortcut");
458
- if (result.status === "automatic") {
459
- ctx.ui?.notify?.("Cognitive Routing automatic control active.", "info");
460
- } else {
461
- ctx.ui?.notify?.(`Cognitive Routing could not change control: ${result.reason}.`, "warning");
462
- }
463
- await applyLiveCapabilityStateForSession(ctx);
464
- },
465
- });
466
- }
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
-
501
191
  pi.on("resources_discover", async (event, ctx) => {
502
- const cwd = ctx?.cwd ?? event?.cwd ?? process.cwd();
503
- const snapshot =
504
- providerSurfaceSnapshot?.context === ctx
505
- ? providerSurfaceSnapshot.value
506
- : await buildProviderSurface(ctx ?? { cwd }, false);
507
- const state = snapshot.capabilityState;
508
- if (!state.configured) {
509
- return { skillPaths: [freeflowSkillPath("setup-freeflow")] };
510
- }
511
- if (!state.enabled || !hasUsableMandatoryPrompts(snapshot.freeflowContext)) {
512
- return { skillPaths: [] };
513
- }
514
- return { skillPaths: freeflowModelSkillPaths(state) };
192
+ const state = capability ?? (await loadSurface(ctx ?? { cwd: (event as any)?.cwd ?? process.cwd() }));
193
+ return { skillPaths: freeflowModelSkillPaths(STABLE_FREEFLOW_SURFACE) };
515
194
  });
516
-
517
195
  pi.on("session_start", async (event, ctx) => {
518
- routingSession.resetForSession(ctx, event);
519
- latestCognitiveRoutingContext = ctx;
520
- providerSurfaceSnapshot = undefined;
521
- runtimeStateRefreshRequired = true;
196
+ const generation = ++surfaceGeneration;
197
+ requestHistory.reset();
198
+ routing.unbind();
199
+ capability = undefined;
200
+ prompts = undefined;
201
+ refreshState = true;
522
202
  restoreSessionOverrides(ctx);
523
- freeflowContextRuntime = new FreeflowContextRuntime(ctx);
524
- contextVirtualizationRuntime = new ContextVirtualizationRuntime(pi, ctx, freeflowContextRuntime);
525
- conversationHistoryRuntime = new ConversationHistoryRuntime(
526
- ctx,
527
- (entryId) => contextVirtualizationRuntime?.isSourceFullyProjected(entryId) ?? true,
528
- freeflowContextRuntime,
529
- );
530
- await contextVirtualizationRuntime.recover(ctx);
531
- const capabilityState = await readCapabilityState(ctx.cwd, ctx, pi?.host);
532
- await refreshRuntimeContext(capabilityState);
533
- routingSession.ensureStartupInactive(ctx, capabilityState.cognitiveRouting?.effective === true);
534
- routingSession.reconcileNativeEntries(ctx);
535
- const hasSessionState = sessionHasConversationOrRoutingState(ctx);
536
- const snapshot = await buildProviderSurface(ctx, hasSessionState);
537
- if (
538
- !hasSessionState &&
539
- snapshot.capabilityState?.cognitiveRouting?.effective === true &&
540
- routingSession.canAutoActivate()
541
- ) {
542
- routingSession.armFromCapability(snapshot.capabilityState.cognitiveRouting);
543
- } else {
544
- routingSession.clearArmed();
545
- }
546
- registerContextToolForState(snapshot.capabilityState);
547
- const routingSnapshot = routingSession.snapshot();
548
- setFreeflowStatus(
203
+ const initial = await readCapabilityState(ctx.cwd, ctx, pi.host);
204
+ await refreshRuntimeContext(STABLE_FREEFLOW_SURFACE);
205
+ if (generation !== surfaceGeneration) return;
206
+ await loadSurface(ctx);
207
+ context = new FreeflowContextRuntime(ctx);
208
+ virtualization = new ContextVirtualizationRuntime(pi, ctx, context);
209
+ history = new ConversationHistoryRuntime(
549
210
  ctx,
550
- snapshot.capabilityState,
551
- snapshot.cognitiveRoutingRuntime ?? routingSnapshot.runtimeState,
552
- snapshot.freeflowContext,
553
- { startupSelectionSuppressed: routingSnapshot.startupSelectionSuppressed },
211
+ (entryId) => virtualization?.isSourceFullyProjected(entryId) ?? true,
212
+ context,
554
213
  );
555
- await applyCapabilityToolVisibility(pi, ctx, snapshot.capabilityState, routingSnapshot);
214
+ await virtualization.recover(ctx);
215
+ await routing.bind(ctx, capability.cognitiveRouting, (event as any)?.reason !== "reload");
216
+ contextTools();
217
+ status(ctx);
556
218
  });
557
-
558
- pi.on("agent_settled", async (_event, ctx) => {
559
- if (!routingSession.hasController()) return undefined;
560
- await applyLiveCapabilityStateForSession(ctx);
561
- return undefined;
219
+ pi.on("session_shutdown", async () => {
220
+ surfaceGeneration++;
221
+ requestHistory.reset();
222
+ routing.unbind();
223
+ context = undefined;
224
+ virtualization = undefined;
225
+ history = undefined;
226
+ capability = undefined;
227
+ prompts = undefined;
228
+ sessionContext = undefined;
562
229
  });
563
-
564
- pi.on("session_shutdown", async (event) => {
565
- await routingSession.shutdown(event?.reason);
566
- providerSurfaceSnapshot = undefined;
567
- freeflowContextRuntime = undefined;
568
- contextVirtualizationRuntime = undefined;
569
- conversationHistoryRuntime = undefined;
230
+ pi.on("before_agent_start", async (event, ctx) => {
231
+ await update(ctx);
232
+ await routing.beforeRun(ctx);
233
+ status(ctx);
234
+ const text = stableRuntimeContext(prompts);
235
+ return { systemPrompt: text ? `${event.systemPrompt}\n\n${text}` : event.systemPrompt };
570
236
  });
571
-
572
- pi.on("session_tree", async (_event, ctx) => {
573
- latestCognitiveRoutingContext = ctx;
574
- providerSurfaceSnapshot = undefined;
575
- runtimeStateRefreshRequired = true;
576
- restoreSessionOverrides(ctx);
577
- await routingSession.reconcileBranch();
578
- if (contextVirtualizationRuntime) {
579
- contextVirtualizationRuntime.setContext(ctx);
580
- await contextVirtualizationRuntime.recover(ctx);
581
- }
582
- conversationHistoryRuntime?.setContext(ctx);
583
- await applyLiveCapabilityStateForSession(ctx);
237
+ pi.on("message_end", (event) => routing.messageEnd((event as any).message));
238
+ pi.on("tool_call", (event, ctx) => {
239
+ if (
240
+ (event as any).toolName === CONTEXT_VIRTUALIZATION_TOOL_NAME &&
241
+ !capability?.contextVirtualization?.effective &&
242
+ !capability?.conversationHistory?.effective
243
+ )
244
+ return { block: true, reason: "Freeflow Context is disabled." };
245
+ return routing.preflight(event, ctx);
584
246
  });
585
-
586
- pi.on("session_compact", async (_event, ctx) => {
587
- latestCognitiveRoutingContext = ctx;
588
- providerSurfaceSnapshot = undefined;
589
- runtimeStateRefreshRequired = true;
590
- await routingSession.reconcileBranch("session-compact");
591
- if (contextVirtualizationRuntime) {
592
- contextVirtualizationRuntime.setContext(ctx);
593
- await contextVirtualizationRuntime.recover(ctx);
594
- }
595
- conversationHistoryRuntime?.setContext(ctx);
596
- await applyLiveCapabilityStateForSession(ctx);
247
+ pi.on("turn_end", async (event, ctx) => {
248
+ await routing.turnEnd(event, ctx);
249
+ status(ctx);
597
250
  });
598
-
599
- pi.on("before_agent_start", async (event, ctx) => {
600
- latestCognitiveRoutingContext = ctx;
601
- const snapshot = await buildProviderSurface(ctx, true);
602
- providerSurfaceSnapshot = { context: ctx, value: snapshot };
603
- registerContextToolForState(snapshot.capabilityState);
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);
609
- const freeflowRuntimeContext = runtimeContext(snapshot.freeflowContext, snapshot.capabilityState);
610
- const systemPrompt = freeflowRuntimeContext
611
- ? `${event.systemPrompt}\n\n${freeflowRuntimeContext}`
612
- : event.systemPrompt;
613
- return { systemPrompt };
251
+ pi.on("agent_settled", async (_event, ctx) => {
252
+ await routing.settled(ctx);
253
+ await update(ctx);
254
+ });
255
+ pi.on("model_select", async (_event, ctx) => {
256
+ await routing.nativeChange(ctx);
257
+ status(ctx);
258
+ });
259
+ pi.on("thinking_level_select", async (_event, ctx) => {
260
+ await routing.nativeChange(ctx);
261
+ status(ctx);
614
262
  });
615
-
616
263
  pi.on("context", async (event, ctx) => {
617
- const snapshot =
618
- providerSurfaceSnapshot?.context === ctx ? providerSurfaceSnapshot.value : await buildProviderSurface(ctx, false);
619
- if (providerSurfaceSnapshot?.context === ctx) providerSurfaceSnapshot = undefined;
620
- const cognitiveRoutingRuntime = snapshot.cognitiveRoutingRuntime;
621
- const surfaceCapabilityState = snapshot.capabilityState;
622
- let changed = false;
623
- let messages = event.messages
624
- .map((message) => {
625
- const filtered = filterBootstrapMessage(message);
626
- if (filtered !== message) changed = true;
627
- return filtered;
628
- })
629
- .filter((message) => message !== undefined);
630
- if (contextVirtualizationRuntime) {
631
- contextVirtualizationRuntime.setContext(ctx);
632
- const projected = await contextVirtualizationRuntime.project(
633
- messages,
634
- surfaceCapabilityState.contextVirtualization?.effective === true,
635
- );
636
- if (projected.changed) {
637
- changed = true;
638
- messages = projected.messages;
639
- }
264
+ if (!capability) await loadSurface(ctx);
265
+ let messages = event.messages.map(filterBootstrapMessage).filter(Boolean);
266
+ if (virtualization) {
267
+ virtualization.setContext(ctx);
268
+ const projected = await virtualization.project(messages, capability.contextVirtualization.effective);
269
+ messages = projected.messages;
640
270
  }
641
- if (conversationHistoryRuntime && surfaceCapabilityState.conversationHistory?.effective === true) {
642
- conversationHistoryRuntime.setContext(ctx);
643
- conversationHistoryRuntime.capture(surfaceCapabilityState.contextVirtualization?.effective === true);
271
+ if (history && capability.conversationHistory.effective) {
272
+ history.setContext(ctx);
273
+ history.capture(capability.contextVirtualization.effective);
644
274
  }
645
- const forceRuntimeStateRefresh = runtimeStateRefreshRequired;
646
- const nextMessages = withFreeflowRuntimeState(
647
- messages,
648
- surfaceCapabilityState,
649
- cognitiveRoutingRuntime,
650
- snapshot.freeflowContext,
651
- { force: forceRuntimeStateRefresh },
652
- );
653
- if (nextMessages !== messages) {
654
- changed = true;
655
- messages = nextMessages;
656
- }
657
- if (forceRuntimeStateRefresh) runtimeStateRefreshRequired = false;
658
- return changed ? { messages } : undefined;
659
- });
660
-
661
- pi.on("tool_call", async (event, ctx) => {
662
- const capabilityState = await readCapabilityState(ctx.cwd, ctx, pi?.host);
663
- const toolName = typeof event?.toolName === "string" ? event.toolName : "";
664
- if (
665
- capabilityState.contextVirtualization?.effective !== true &&
666
- capabilityState.conversationHistory?.effective !== true &&
667
- toolName === CONTEXT_VIRTUALIZATION_TOOL_NAME
668
- ) {
669
- return disabledToolCall(toolName, "context");
670
- }
671
- return undefined;
275
+ messages = withFreeflowRuntimeState(messages, capability, routing.state(), prompts, {
276
+ force: refreshState,
277
+ projectionFailure: routing.state().projectionFailure,
278
+ });
279
+ refreshState = false;
280
+ const routed = await routing.context(ctx, messages);
281
+ const projected = virtualization?.decorate(routed) ?? routed;
282
+ const state = routing.state();
283
+ if (projected.some((message) => message.details?.routingRequestBlocked === true)) return { messages: projected };
284
+ const view = routing.projectionEnabled && state.activeProfile === "coordinator" ? "coordinator" : "ordinary";
285
+ return {
286
+ messages: await requestHistory.assemble(projected, view, ctx, (assembled) =>
287
+ routing.budgetNotice(assembled, ctx),
288
+ ),
289
+ };
672
290
  });
673
-
674
- pi.on("tool_result", async (event, ctx) => {
675
- const toolName = typeof event?.toolName === "string" ? event.toolName : "";
676
- if (routingSession.supportsCognitiveRoutingRuntime() && toolName === COGNITIVE_ROUTING_SWITCH_TOOL_NAME) {
677
- await applyLiveCapabilityStateForSession(ctx);
291
+ const restore = async (ctx: any, navigation = true) => {
292
+ requestHistory.reset();
293
+ restoreSessionOverrides(ctx);
294
+ refreshState = true;
295
+ if (virtualization) {
296
+ virtualization.setContext(ctx);
297
+ await virtualization.recover(ctx);
678
298
  }
679
- return undefined;
299
+ history?.setContext(ctx);
300
+ await routing.ancestryChanged(ctx, navigation);
301
+ await update(ctx);
302
+ };
303
+ pi.on("session_tree", async (_event, ctx) => restore(ctx));
304
+ pi.on("session_compact", async (_event, ctx) => restore(ctx, false));
305
+ pi.on("session_compact_failed", async (_event, ctx) => {
306
+ refreshState = true;
307
+ status(ctx);
680
308
  });
681
309
 
682
- for (const { command, skill } of WORKFLOW_COMMANDS) {
683
- pi.registerCommand(command, {
684
- description: command === skill ? `Run Freeflow ${skill}` : `Run Freeflow ${skill} via ${command}`,
685
- ...(command === "bypass" ? { getArgumentCompletions: bypassCompletions } : {}),
686
- handler: async (args, ctx) => {
687
- await sendSkillCommand(pi, ctx, skill, args);
310
+ if (
311
+ typeof pi.registerShortcut === "function" &&
312
+ typeof api.appendEntry === "function" &&
313
+ typeof api.setModel === "function" &&
314
+ typeof api.setThinkingLevel === "function"
315
+ ) {
316
+ pi.registerShortcut("ctrl+shift+r", {
317
+ description: "Cycle enabled Cognitive Routing manual holds",
318
+ handler: async (ctx) => {
319
+ if (!ctx.isIdle()) {
320
+ ctx.ui.notify("Wait for Pi to become idle before changing control.", "warning");
321
+ return;
322
+ }
323
+ const state = routing.state();
324
+ const profiles: Profile[] = ["coordinator", ...workersForDelegation(state.delegation)];
325
+ const current = profiles.indexOf(state.activeProfile ?? "coordinator");
326
+ const target = profiles[(current + 1) % profiles.length]!;
327
+ ctx.ui.notify(JSON.stringify(await routing.setManualProfile(target)), "info");
328
+ await update(ctx);
329
+ },
330
+ });
331
+ pi.registerShortcut("ctrl+shift+a", {
332
+ description: "Release manual hold to Automatic Coordinator",
333
+ handler: async (ctx) => {
334
+ if (!ctx.isIdle()) {
335
+ ctx.ui.notify("Wait for Pi to become idle before changing control.", "warning");
336
+ return;
337
+ }
338
+ ctx.ui.notify(JSON.stringify(await routing.setAutomaticControl()), "info");
339
+ await update(ctx);
688
340
  },
689
341
  });
690
342
  }
691
-
692
- for (const skill of CONTRIBUTOR_COMMANDS) {
343
+ for (const { command, skill } of WORKFLOW_COMMANDS)
344
+ pi.registerCommand(command, {
345
+ description: command === skill ? `Run Freeflow ${skill}` : `Run Freeflow ${skill} via ${command}`,
346
+ ...(command === "bypass"
347
+ ? {
348
+ getArgumentCompletions: (prefix: string) =>
349
+ [
350
+ { value: "next", label: "next", description: "Skip one optional step" },
351
+ { value: "task", label: "task", description: "Reduce optional pressure for the current task" },
352
+ ].filter((item) => item.value.startsWith(prefix ?? "")),
353
+ }
354
+ : {}),
355
+ handler: (args, ctx) => sendSkillCommand(pi, ctx, skill, args),
356
+ });
357
+ for (const skill of CONTRIBUTOR_COMMANDS)
693
358
  pi.registerCommand(skill, {
694
359
  description: `Run Freeflow ${skill}`,
695
- handler: async (args, ctx) => {
696
- await sendSkillCommand(pi, ctx, skill, args);
697
- },
360
+ handler: (args, ctx) => sendSkillCommand(pi, ctx, skill, args),
698
361
  });
699
- }
700
-
701
362
  pi.registerCommand("freeflow", {
702
- description: "Open unified Freeflow settings or print compact status",
703
- getArgumentCompletions: (prefix) => freeflowCompletions(prefix, routingSession.supportsCognitiveRoutingRuntime()),
363
+ description: "Freeflow settings, status, profile control, saved-operation resume, and context tools",
364
+ getArgumentCompletions: (prefix) =>
365
+ freeflowCompletions(
366
+ prefix,
367
+ typeof api.appendEntry === "function" &&
368
+ typeof api.setModel === "function" &&
369
+ typeof api.setThinkingLevel === "function",
370
+ ),
704
371
  handler: async (args, ctx) => {
705
- const contextInput = (args ?? "").trim();
706
- if (contextInput === "context" || contextInput.startsWith("context ")) {
707
- const capabilityState = await readCapabilityState(ctx.cwd, ctx, pi?.host);
372
+ if (await routing.command(args ?? "", ctx)) {
373
+ await update(ctx);
374
+ return;
375
+ }
376
+ const input = (args ?? "").trim();
377
+ if (input === "context" || input.startsWith("context ")) {
708
378
  await handleContextCommand(
709
- contextInput.slice("context".length).trim(),
379
+ input.slice(7).trim(),
710
380
  ctx,
711
- contextVirtualizationRuntime,
712
- capabilityState.contextVirtualization?.effective === true,
713
- capabilityState.conversationHistory?.effective === true,
381
+ virtualization,
382
+ capability?.contextVirtualization.effective,
383
+ capability?.conversationHistory.effective,
714
384
  );
715
385
  return;
716
386
  }
717
- if (routingSession.supportsCognitiveRoutingRuntime() && /^profile(?:\s|$)/i.test((args ?? "").trim())) {
718
- const profileCommandContext = {
719
- isIdle: ctx?.isIdle,
720
- ui: ctx?.ui,
721
- history: (options) => routingSession.history(options) ?? readCognitiveRoutingHistory(ctx, options),
722
- };
723
- const historyCommand = /^profile\s+history(?:\s|$)/i.test((args ?? "").trim());
724
- if (historyCommand) {
725
- await handleCognitiveRoutingProfileCommand(args, profileCommandContext, routingSession.commandController());
726
- return;
727
- }
728
- if (!routingSession.hasController() && !routingSession.armed) {
729
- routingSession.beginExplicitReactivation();
730
- await applyLiveCapabilityStateForSession(ctx, { reconcileCognitiveRouting: true });
731
- }
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
- }
741
- return;
742
- }
743
- }
744
- await handleFreeflowCommand(
745
- args,
746
- ctx,
747
- async (_changed, options = {}) => {
748
- await applyLiveCapabilityStateForSession(ctx, {
749
- reconcileCognitiveRouting: options.reconcileCognitiveRouting ?? true,
750
- });
751
- },
752
- pi,
753
- routingSession.commandController(),
754
- );
387
+ await handleFreeflowCommand(args, ctx, async () => update(ctx), pi, routing);
755
388
  },
756
389
  });
757
390
  }