@hassangameryt/freeflow 0.7.2 → 0.8.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 (267) 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/NOTICE +14 -0
  6. package/README.md +151 -136
  7. package/capabilities/cognitive-routing/SKILL.md +110 -96
  8. package/capabilities/cognitive-routing/references/both-mode.md +36 -0
  9. package/capabilities/cognitive-routing/references/executor-mode.md +48 -0
  10. package/capabilities/cognitive-routing/references/helper-mode.md +47 -0
  11. package/capabilities/compaction/SKILL.md +49 -0
  12. package/capabilities/compaction/references/summary-format.md +239 -0
  13. package/command-surface.json +4 -0
  14. package/gemini-extension.json +1 -1
  15. package/hooks/shared/runtime-context.mjs +28 -30
  16. package/licenses/Apache-2.0.txt +55 -0
  17. package/package.json +22 -9
  18. package/pi-extension/README.md +41 -0
  19. package/pi-extension/dist/cognitive-routing/assembler.js +393 -0
  20. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/config.js +40 -21
  21. package/pi-extension/dist/cognitive-routing/economics.js +45 -0
  22. package/pi-extension/dist/cognitive-routing/event-schema.js +388 -0
  23. package/pi-extension/dist/cognitive-routing/event-store.js +280 -0
  24. package/pi-extension/dist/cognitive-routing/gate.js +241 -0
  25. package/pi-extension/dist/cognitive-routing/handlers.js +899 -0
  26. package/pi-extension/dist/cognitive-routing/handoffs.js +350 -0
  27. package/pi-extension/dist/cognitive-routing/model-control.js +467 -0
  28. package/pi-extension/dist/cognitive-routing/projection.js +503 -0
  29. package/pi-extension/dist/cognitive-routing/provenance.js +81 -0
  30. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/render.js +31 -10
  31. package/pi-extension/dist/cognitive-routing/runtime.js +396 -0
  32. package/pi-extension/dist/cognitive-routing/schemas.js +209 -0
  33. package/pi-extension/dist/cognitive-routing/session.js +221 -0
  34. package/pi-extension/dist/cognitive-routing/sources.js +291 -0
  35. package/pi-extension/dist/cognitive-routing/state.js +487 -0
  36. package/pi-extension/dist/cognitive-routing/status.js +80 -0
  37. package/pi-extension/dist/cognitive-routing/tools.js +60 -0
  38. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/types.js +10 -12
  39. package/pi-extension/dist/compaction/carry.js +191 -0
  40. package/pi-extension/dist/compaction/config.js +22 -0
  41. package/pi-extension/dist/compaction/controller.js +471 -0
  42. package/pi-extension/dist/compaction/harness.js +77 -0
  43. package/pi-extension/dist/compaction/results.js +158 -0
  44. package/pi-extension/dist/compaction/thresholds.js +35 -0
  45. package/pi-extension/dist/compaction/tool.js +57 -0
  46. package/pi-extension/dist/host/branch.js +13 -0
  47. package/pi-extension/dist/host/canonical.js +12 -0
  48. package/pi-extension/dist/host/catalog.js +77 -0
  49. package/pi-extension/dist/host/config.js +374 -0
  50. package/pi-extension/dist/host/projection-tags.js +60 -0
  51. package/pi-extension/dist/host/prompts.js +82 -0
  52. package/pi-extension/dist/{session-sources → host}/read-only-session.js +112 -1
  53. package/pi-extension/dist/host/request-history.js +298 -0
  54. package/pi-extension/dist/host/runtime-state.js +189 -0
  55. package/pi-extension/dist/host/settings/freeflow-command.js +1570 -0
  56. package/pi-extension/dist/{settings/settings-tui.js → host/settings/settings-view.js} +186 -73
  57. package/pi-extension/dist/host/staging.js +28 -0
  58. package/pi-extension/dist/host/status.js +101 -0
  59. package/pi-extension/dist/index.js +441 -170
  60. package/pi-extension/dist/provider-support/cache/anchor.js +21 -0
  61. package/pi-extension/dist/provider-support/cache/anthropic.js +102 -0
  62. package/pi-extension/dist/provider-support/cache/health.js +73 -0
  63. package/pi-extension/dist/provider-support/cache/index.js +58 -0
  64. package/pi-extension/dist/provider-support/cache/keepalive.js +210 -0
  65. package/pi-extension/dist/provider-support/cache/monitor.js +17 -0
  66. package/pi-extension/dist/provider-support/cache/openai-responses.js +48 -0
  67. package/pi-extension/dist/provider-support/effort.js +13 -0
  68. package/pi-extension/dist/provider-support/index.js +45 -0
  69. package/pi-extension/dist/provider-support/openai/adapter.js +127 -0
  70. package/pi-extension/dist/provider-support/openai/history.js +127 -0
  71. package/pi-extension/dist/provider-support/openai/index.js +15 -0
  72. package/pi-extension/dist/provider-support/openai/session-state.js +105 -0
  73. package/pi-extension/dist/provider-support/routes.js +17 -0
  74. package/pi-extension/dist/tool-execution/apply-patch/grammar.js +23 -0
  75. package/pi-extension/dist/tool-execution/apply-patch/match.js +38 -0
  76. package/pi-extension/dist/tool-execution/apply-patch/parser.js +177 -0
  77. package/pi-extension/dist/tool-execution/apply-patch/plan.js +201 -0
  78. package/pi-extension/dist/tool-execution/apply-patch/tool.js +84 -0
  79. package/pi-extension/dist/tool-execution/background.js +238 -0
  80. package/pi-extension/dist/tool-execution/bash-guard.js +23 -0
  81. package/pi-extension/dist/tool-execution/config.js +22 -0
  82. package/pi-extension/dist/tool-execution/edit-messages.js +120 -0
  83. package/pi-extension/dist/tool-execution/file-state.js +138 -0
  84. package/pi-extension/dist/tool-execution/file-tracking.js +143 -0
  85. package/pi-extension/dist/tool-execution/prompt.js +54 -0
  86. package/pi-extension/dist/tool-execution/tools.js +16 -0
  87. package/pi-extension/freeflow/README.md +9 -0
  88. package/plugin.json +1 -1
  89. package/runtime/prompts/cognitive-routing.md +4 -4
  90. package/runtime/prompts/core.md +22 -75
  91. package/runtime/prompts/interaction-contract.md +15 -8
  92. package/runtime/prompts/tool-execution.md +38 -0
  93. package/runtime/prompts/working-method.md +108 -0
  94. package/skills/action-selection/SKILL.md +39 -47
  95. package/skills/action-selection/references/trajectory-stalls.md +2 -0
  96. package/skills/bypass/SKILL.md +2 -2
  97. package/skills/commit-work/SKILL.md +31 -83
  98. package/skills/commit-work/references/git-state-edges.md +1 -1
  99. package/skills/commit-work/references/simple-push.md +2 -0
  100. package/skills/commit-work/references/staging-decisions.md +1 -1
  101. package/skills/decision-gate/SKILL.md +31 -50
  102. package/skills/design-for-depth/SKILL.md +44 -53
  103. package/skills/design-for-depth/references/design-pressure-signals.md +46 -144
  104. package/skills/design-for-depth/references/implementation-decisions.md +4 -4
  105. package/skills/design-for-depth/references/interface-design-loop.md +5 -36
  106. package/skills/design-for-depth/references/module-and-dependency-design.md +2 -32
  107. package/skills/design-for-depth/references/state-and-failure-boundaries.md +2 -13
  108. package/skills/diagnose-failure/SKILL.md +39 -24
  109. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +2 -0
  110. package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +2 -2
  111. package/skills/discuss/SKILL.md +37 -13
  112. package/skills/discuss/references/discussion-continuity.md +7 -27
  113. package/skills/evaluate-skill/SKILL.md +49 -80
  114. package/skills/evaluate-skill/references/evaluation-definition-schema.md +35 -5
  115. package/skills/evaluate-skill/references/evaluation-design.md +14 -14
  116. package/skills/evaluate-skill/references/execution-and-evidence.md +26 -5
  117. package/skills/evaluate-skill/references/review-and-revision.md +3 -1
  118. package/skills/evaluate-skill/scripts/lib/definitions.mjs +49 -8
  119. package/skills/evaluate-skill/scripts/lib/pi.mjs +14 -7
  120. package/skills/evaluate-skill/scripts/lib/review.mjs +196 -0
  121. package/skills/evaluate-skill/scripts/lib/runner.mjs +45 -8
  122. package/skills/evaluate-skill/scripts/lib/sandbox.mjs +2 -0
  123. package/skills/evaluate-skill/scripts/lib/trials.mjs +103 -0
  124. package/skills/evaluate-skill/scripts/lib/view.mjs +67 -1
  125. package/skills/evaluate-skill/scripts/pi-command-tool.mjs +144 -0
  126. package/skills/evaluate-skill/scripts/pi-guard.mjs +30 -13
  127. package/skills/evaluate-skill/scripts/skill-eval.mjs +114 -34
  128. package/skills/execute-work/SKILL.md +41 -9
  129. package/skills/execute-work/references/code-practices.md +2 -0
  130. package/skills/execute-work/references/execute-work-edges.md +11 -1
  131. package/skills/handoff/SKILL.md +1 -1
  132. package/skills/handoff/references/handoff-templates.md +1 -1
  133. package/skills/review-artifact/SKILL.md +42 -41
  134. package/skills/review-artifact/references/adjudicate-artifact-review.md +1 -1
  135. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +1 -1
  136. package/skills/review-work/SKILL.md +38 -32
  137. package/skills/review-work/references/adjudicate-work-review.md +1 -1
  138. package/skills/review-work/references/independent-work-reviewer-contract.md +1 -1
  139. package/skills/review-work/references/security-risk-lens.md +2 -4
  140. package/skills/setup-freeflow/SKILL.md +3 -1
  141. package/skills/setup-freeflow/references/activation-contract.md +7 -13
  142. package/skills/setup-freeflow/references/host-setup.md +3 -3
  143. package/skills/track-work/SKILL.md +113 -161
  144. package/skills/track-work/references/working-record-format.md +189 -350
  145. package/skills/track-work/scripts/lib/document.mjs +62 -6
  146. package/skills/track-work/scripts/lib/format.mjs +16 -1
  147. package/skills/track-work/scripts/lib/store.mjs +9 -8
  148. package/skills/track-work/scripts/lib/transitions.mjs +79 -4
  149. package/skills/track-work/scripts/working-record.mjs +34 -12
  150. package/skills/verify-work/SKILL.md +29 -20
  151. package/skills/verify-work/references/browser-runtime-evidence.md +1 -5
  152. package/skills/verify-work/references/integration-evidence.md +2 -0
  153. package/skills/verify-work/references/performance-evidence.md +3 -1
  154. package/skills/verify-work/references/test-design.md +3 -1
  155. package/skills/workflow/SKILL.md +65 -21
  156. package/skills/workflow/references/domain-skill-composition.md +5 -3
  157. package/skills/write-docs/SKILL.md +83 -0
  158. package/skills/write-docs/references/documentation-shapes.md +119 -0
  159. package/skills/write-docs/scripts/docs-check.mjs +139 -0
  160. package/skills/write-plan/SKILL.md +40 -40
  161. package/skills/write-plan/references/plan-shapes.md +1 -1
  162. package/skills/write-skill/SKILL.md +107 -147
  163. package/skills/write-skill/references/activation-boundaries.md +2 -0
  164. package/skills/write-skill/references/agent-first-instructions.md +22 -10
  165. package/skills/write-skill/references/progressive-disclosure.md +4 -2
  166. package/skills/write-skill/references/skill-dependency-graphs.md +2 -0
  167. package/skills/write-skill/scripts/lib/similarity.mjs +56 -0
  168. package/skills/write-skill/scripts/skill-author.mjs +18 -2
  169. package/skills/write-spec/SKILL.md +34 -41
  170. package/skills/write-spec/references/artifact-standards.md +1 -1
  171. package/skills/write-spec/references/decision-records.md +2 -0
  172. package/skills/write-spec/references/spec-shapes.md +1 -1
  173. package/capabilities/context-virtualization/SKILL.md +0 -103
  174. package/capabilities/conversation-history/SKILL.md +0 -53
  175. package/hooks/tests/check-host-adapters.test.mjs +0 -82
  176. package/hooks/tests/check-runtime-context-hook.sh +0 -212
  177. package/pi-extension/dist/cognitive-routing-v2/projection.js +0 -321
  178. package/pi-extension/dist/cognitive-routing-v2/runtime.js +0 -1585
  179. package/pi-extension/dist/cognitive-routing-v2/schemas.js +0 -107
  180. package/pi-extension/dist/cognitive-routing-v2/state.js +0 -612
  181. package/pi-extension/dist/cognitive-routing-v2/tools.js +0 -91
  182. package/pi-extension/dist/context-virtualization/commands.js +0 -89
  183. package/pi-extension/dist/context-virtualization/projector.js +0 -42
  184. package/pi-extension/dist/context-virtualization/runtime.js +0 -356
  185. package/pi-extension/dist/context-virtualization/state.js +0 -57
  186. package/pi-extension/dist/context-virtualization/types.js +0 -10
  187. package/pi-extension/dist/conversation-history/passages.js +0 -66
  188. package/pi-extension/dist/conversation-history/ranking.js +0 -105
  189. package/pi-extension/dist/conversation-history/runtime.js +0 -226
  190. package/pi-extension/dist/conversation-history/search.js +0 -56
  191. package/pi-extension/dist/conversation-history/types.js +0 -1
  192. package/pi-extension/dist/freeflow-context/resolver.js +0 -120
  193. package/pi-extension/dist/freeflow-context/runtime.js +0 -80
  194. package/pi-extension/dist/freeflow-context/source-projector.js +0 -95
  195. package/pi-extension/dist/freeflow-context/tool.js +0 -313
  196. package/pi-extension/dist/freeflow-context/types.js +0 -12
  197. package/pi-extension/dist/freeflow-context/visibility.js +0 -28
  198. package/pi-extension/dist/integrations/mcp-config.js +0 -29
  199. package/pi-extension/dist/runtime/runtime-context.js +0 -735
  200. package/pi-extension/dist/runtime/runtime-identity.js +0 -5
  201. package/pi-extension/dist/session-sources/events.js +0 -195
  202. package/pi-extension/dist/session-sources/sources.js +0 -233
  203. package/pi-extension/dist/settings/settings-ui.js +0 -1160
  204. package/pi-extension/dist/ui/text-component.js +0 -57
  205. package/pi-extension/src/cognitive-routing-v2/budget.ts +0 -42
  206. package/pi-extension/src/cognitive-routing-v2/config.ts +0 -143
  207. package/pi-extension/src/cognitive-routing-v2/projection.ts +0 -365
  208. package/pi-extension/src/cognitive-routing-v2/render.ts +0 -256
  209. package/pi-extension/src/cognitive-routing-v2/runtime.ts +0 -1634
  210. package/pi-extension/src/cognitive-routing-v2/schemas.ts +0 -109
  211. package/pi-extension/src/cognitive-routing-v2/state.ts +0 -622
  212. package/pi-extension/src/cognitive-routing-v2/tools.ts +0 -93
  213. package/pi-extension/src/cognitive-routing-v2/types.ts +0 -222
  214. package/pi-extension/src/context-virtualization/commands.ts +0 -111
  215. package/pi-extension/src/context-virtualization/projector.ts +0 -62
  216. package/pi-extension/src/context-virtualization/runtime.ts +0 -406
  217. package/pi-extension/src/context-virtualization/state.ts +0 -79
  218. package/pi-extension/src/context-virtualization/types.ts +0 -92
  219. package/pi-extension/src/conversation-history/passages.ts +0 -81
  220. package/pi-extension/src/conversation-history/ranking.ts +0 -155
  221. package/pi-extension/src/conversation-history/runtime.ts +0 -250
  222. package/pi-extension/src/conversation-history/search.ts +0 -79
  223. package/pi-extension/src/conversation-history/types.ts +0 -31
  224. package/pi-extension/src/freeflow-context/resolver.ts +0 -139
  225. package/pi-extension/src/freeflow-context/runtime.ts +0 -100
  226. package/pi-extension/src/freeflow-context/source-projector.ts +0 -106
  227. package/pi-extension/src/freeflow-context/tool.ts +0 -365
  228. package/pi-extension/src/freeflow-context/types.ts +0 -70
  229. package/pi-extension/src/freeflow-context/visibility.ts +0 -53
  230. package/pi-extension/src/index.ts +0 -368
  231. package/pi-extension/src/integrations/mcp-config.ts +0 -32
  232. package/pi-extension/src/runtime/runtime-context.ts +0 -838
  233. package/pi-extension/src/runtime/runtime-identity.ts +0 -8
  234. package/pi-extension/src/session-sources/events.ts +0 -208
  235. package/pi-extension/src/session-sources/read-only-session.ts +0 -170
  236. package/pi-extension/src/session-sources/sources.ts +0 -263
  237. package/pi-extension/src/settings/settings-tui.ts +0 -501
  238. package/pi-extension/src/settings/settings-ui.ts +0 -1422
  239. package/pi-extension/src/ui/text-component.ts +0 -66
  240. package/pi-extension/tests/cognitive-routing-v2/communication.test.js +0 -484
  241. package/pi-extension/tests/cognitive-routing-v2/compaction-input.test.js +0 -183
  242. package/pi-extension/tests/cognitive-routing-v2/native.test.js +0 -441
  243. package/pi-extension/tests/cognitive-routing-v2/performance.test.js +0 -87
  244. package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +0 -245
  245. package/pi-extension/tests/cognitive-routing-v2/projection.test.js +0 -241
  246. package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +0 -236
  247. package/pi-extension/tests/cognitive-routing-v2/render.test.js +0 -230
  248. package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +0 -153
  249. package/pi-extension/tests/cognitive-routing-v2/stabilization.test.js +0 -521
  250. package/pi-extension/tests/cognitive-routing-v2/state.test.js +0 -275
  251. package/pi-extension/tests/context-virtualization/extension.test.js +0 -765
  252. package/pi-extension/tests/context-virtualization/projector.test.js +0 -110
  253. package/pi-extension/tests/context-virtualization/runtime.test.js +0 -216
  254. package/pi-extension/tests/conversation-history/runtime.test.js +0 -98
  255. package/pi-extension/tests/conversation-history/search.test.js +0 -97
  256. package/pi-extension/tests/fixtures/pi-host.js +0 -4
  257. package/pi-extension/tests/fixtures/routing-native.js +0 -188
  258. package/pi-extension/tests/integration/mode-free-core.test.js +0 -219
  259. package/pi-extension/tests/integration/pi-extension.test.js +0 -968
  260. package/pi-extension/tests/integration/prompt-architecture.test.js +0 -431
  261. package/pi-extension/tests/integration/unaffected-regressions.test.js +0 -478
  262. package/pi-extension/tests/settings/settings-tui.test.js +0 -243
  263. package/pi-extension/tsconfig.json +0 -16
  264. package/runtime/prompts/context-virtualization.md +0 -3
  265. package/runtime/prompts/conversation-history.md +0 -3
  266. package/skills/design-for-depth/references/software-design-philosophy.md +0 -202
  267. /package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/budget.js +0 -0
@@ -1,31 +1,103 @@
1
- import { RoutingRuntime } from "./cognitive-routing-v2/runtime.js";
2
- import { applyRoutingToolVisibility, registerRoutingTools } from "./cognitive-routing-v2/tools.js";
3
- import { ConversationHistoryRuntime } from "./conversation-history/runtime.js";
4
- import { FreeflowContextRuntime } from "./freeflow-context/runtime.js";
5
- import { CONTEXT_VIRTUALIZATION_TOOL_NAME, registerFreeflowContextTool } from "./freeflow-context/tool.js";
6
- import { handleContextCommand } from "./context-virtualization/commands.js";
7
- import { ContextVirtualizationRuntime } from "./context-virtualization/runtime.js";
8
- import { handleFreeflowCommand } from "./settings/settings-ui.js";
9
- import { isPiFlowHost } from "./runtime/runtime-identity.js";
1
+ import { calculateContextTokens, compact as piCompact, estimateTokens } from "@earendil-works/pi-coding-agent";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { RequestHistory } from "./host/request-history.js";
5
+ import { registerProviderSupport } from "./provider-support/index.js";
6
+ import { CacheHealth } from "./provider-support/cache/health.js";
7
+ import { CacheMonitor } from "./provider-support/cache/monitor.js";
8
+ import { RoutingRuntime } from "./cognitive-routing/runtime.js";
9
+ import { applyRoutingToolVisibility, registerRoutingTools } from "./cognitive-routing/tools.js";
10
+ import { handleFreeflowCommand } from "./host/settings/freeflow-command.js";
11
+ import { toolExecutionSections, toolExecutionTail } from "./tool-execution/prompt.js";
12
+ import { FileTracking } from "./tool-execution/file-tracking.js";
13
+ import { registerApplyPatch } from "./tool-execution/apply-patch/tool.js";
14
+ import { applyToolExecutionTools } from "./tool-execution/tools.js";
15
+ import { BackgroundJobs, NOTICE_PREFIX, NOTICE_TYPE, registerBackgroundTools } from "./tool-execution/background.js";
16
+ import { backgroundRefusal } from "./tool-execution/bash-guard.js";
17
+ import { CompactionController, USER_REQUEST } from "./compaction/controller.js";
18
+ import { applyCompactionTools, registerCompactionTool } from "./compaction/tool.js";
19
+ import { strictest } from "./compaction/thresholds.js";
20
+ import { tagProjectedMessages } from "./host/projection-tags.js";
21
+ import { trustStartedSession } from "./host/read-only-session.js";
22
+ import { takeStage } from "./host/staging.js";
10
23
  import {
11
24
  CONTRIBUTOR_COMMANDS,
12
25
  WORKFLOW_COMMANDS,
13
26
  freeflowModelSkillPaths,
14
- freeflowSkillPath,
27
+ STABLE_FREEFLOW_SURFACE,
28
+ skillPrompt,
29
+ } from "./host/catalog.js";
30
+ import {
31
+ readCapabilityState,
32
+ restoreSessionOverrides,
33
+ setSessionCoreOverride,
34
+ writeStagedSessionOverrides,
35
+ } from "./host/config.js";
36
+ import {
15
37
  getRuntimeContext,
16
38
  hasUsableMandatoryPrompts,
17
39
  isPromptAvailable,
18
- readCapabilityState,
19
40
  refreshRuntimeContext,
20
- restoreSessionOverrides,
21
- runtimeContext,
22
- filterBootstrapMessage,
23
- setFreeflowStatus,
24
- skillPrompt,
25
- withFreeflowRuntimeState,
26
- } from "./runtime/runtime-context.js";
41
+ stableRuntimeContext,
42
+ } from "./host/prompts.js";
43
+ import { filterBootstrapMessage, withFreeflowRuntimeState } from "./host/runtime-state.js";
44
+ import { setFreeflowStatus } from "./host/status.js";
45
+ import { cachedBranch } from "./host/branch.js";
46
+ const COMPACTION_NOTICE_TYPE = "freeflow-compaction-notice";
47
+ /** Estimated size of the full native history plus the system prompt: what a request without projection carries. */
48
+ /**
49
+ * The context a turn ended with, as Pi estimates it: the turn's own usage plus the tool results after it. Pi's
50
+ * getContextUsage rebuilds the session projection to find the same numbers. Undefined when the turn has no usable usage
51
+ * (aborted, failed, or none reported), where Pi's own estimate applies.
52
+ */
53
+ function turnContextTokens(turn) {
54
+ const message = turn?.message;
55
+ if (message?.role !== "assistant" || message.stopReason === "aborted" || message.stopReason === "error") return;
56
+ if (!message.usage || calculateContextTokens(message.usage) <= 0) return;
57
+ return (turn.toolResults ?? []).reduce(
58
+ (sum, result) => sum + estimateTokens(result),
59
+ calculateContextTokens(message.usage),
60
+ );
61
+ }
62
+ /** Entry types that add no message to Pi's session projection. */
63
+ const NO_MESSAGE = new Set(["custom", "label", "session_info", "model_change", "thinking_level_change"]);
64
+ const historyEstimate = new WeakMap();
65
+ /**
66
+ * The full history's size, as every message's estimate plus the system prompt. Kept per session and extended by the
67
+ * messages appended since, because the branch only grows between compactions; a compaction, summary or custom
68
+ * message (which Pi's projection may place or replace) recomputes it from Pi's projection.
69
+ */
70
+ function fullHistoryTokens(ctx) {
71
+ const manager = ctx.sessionManager;
72
+ const system = Math.ceil((ctx.getSystemPrompt?.() ?? "").length / 4);
73
+ if (!manager?.buildSessionProjection) return system;
74
+ const branch = cachedBranch(manager);
75
+ const previous = historyEstimate.get(manager);
76
+ let tokens;
77
+ if (previous && branch === previous.branch) tokens = previous.tokens;
78
+ else if (
79
+ previous &&
80
+ previous.branch.length > 0 &&
81
+ branch.length >= previous.branch.length &&
82
+ branch[previous.branch.length - 1] === previous.branch.at(-1)
83
+ ) {
84
+ const added = branch.slice(previous.branch.length);
85
+ if (added.every((entry) => entry.type === "message" || NO_MESSAGE.has(entry.type)))
86
+ tokens = added.reduce(
87
+ (sum, entry) => sum + (entry.type === "message" ? estimateTokens(entry.message) : 0),
88
+ previous.tokens,
89
+ );
90
+ }
91
+ if (tokens === undefined)
92
+ tokens = (manager.buildSessionProjection()?.messages ?? []).reduce(
93
+ (sum, message) => sum + estimateTokens(message),
94
+ 0,
95
+ );
96
+ historyEstimate.set(manager, { branch, tokens });
97
+ return tokens + system;
98
+ }
27
99
  async function sendSkillCommand(pi, ctx, skill, args) {
28
- const state = await readCapabilityState(ctx.cwd, ctx, pi.host);
100
+ const state = await readCapabilityState(ctx.cwd, ctx);
29
101
  if (skill === "setup-freeflow" && !state.configured) {
30
102
  await pi.sendUserMessage(skillPrompt(skill, args), { expandPromptTemplates: true });
31
103
  return;
@@ -52,44 +124,70 @@ function freeflowCompletions(prefix, routingAvailable) {
52
124
  : query.startsWith("profile ") && routingAvailable
53
125
  ? [
54
126
  ["profile coordinator", "coordinator", "Hold Coordinator manually"],
55
- ["profile executor", "executor", "Hold Executor manually"],
127
+ ["profile helper", "helper", "Hold Helper manually when enabled"],
128
+ ["profile executor", "executor", "Hold Executor manually when enabled"],
56
129
  ["profile auto", "auto", "Return to automatic Coordinator reconciliation"],
57
130
  ["profile history", "history", "Read routing observations"],
58
131
  ]
59
- : query.startsWith("context ")
60
- ? [
61
- ["context status", "status", "Show Freeflow Context state"],
62
- ["context list", "list", "List archived context projections"],
63
- ["context restore", "restore", "Restore one or more context references"],
64
- ["context reset all", "reset all", "Reset projection decisions on the active branch"],
65
- ]
66
- : [
67
- ["settings", "settings", "Open personal override settings"],
68
- ["status", "status", "Show effective Freeflow state"],
69
- ["context", "context", "Inspect Freeflow Context"],
70
- ...(routingAvailable
71
- ? [
72
- ["profile", "profile", "Hold or release Cognitive Routing profile control"],
73
- ["resume", "resume", "Resume the current saved routing responsibility"],
74
- ]
75
- : []),
76
- ["enable", "enable", "Enable Freeflow for this repository"],
77
- ["disable", "disable", "Disable Freeflow for this repository"],
78
- ];
132
+ : [
133
+ ["settings", "settings", "Open settings; Tab switches Session, Personal and Repository"],
134
+ ["status", "status", "Show effective Freeflow state"],
135
+ ["compact", "compact", "Prepare, compact and recover now"],
136
+ ...(routingAvailable
137
+ ? [
138
+ ["profile", "profile", "Hold or release Cognitive Routing profile control"],
139
+ ["resume", "resume", "Resume the current saved routing responsibility"],
140
+ ]
141
+ : []),
142
+ ["enable", "enable", "Enable Freeflow for this repository"],
143
+ ["disable", "disable", "Disable Freeflow for this repository"],
144
+ ];
79
145
  return choices
80
146
  .filter(([value]) => value.startsWith(query))
81
147
  .map(([value, label, description]) => ({ value, label, description }));
82
148
  }
83
149
  export default function freeflow(pi) {
150
+ // Cache diagnostics are reported to /freeflow status; the footer only shows current settings.
151
+ const cacheMonitor = new CacheMonitor();
152
+ // Only an explicit master-switch disable turns provider support off; unconfigured repositories keep it.
153
+ const providerSupport = registerProviderSupport(
154
+ pi,
155
+ () => !(capability?.configured === true && capability.enabled === false),
156
+ cacheMonitor,
157
+ );
84
158
  const api = pi;
85
- const routing = new RoutingRuntime(api);
159
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
160
+ const routing = new RoutingRuntime(api, [packageRoot]);
161
+ // A Coordinator waiting on its worker will resume, so its prompt cache is worth keeping warm.
162
+ providerSupport.keepAlive.setHoldSource(() => routing.suspendedCoordinator());
163
+ // A worker may share the Coordinator's model; each profile keeps its own warmed request.
164
+ providerSupport.keepAlive.setRequesterSource(() => routing.observationScope().profile);
165
+ const requestHistory = new RequestHistory(api, cacheMonitor);
166
+ // Where the Freeflow Runtime State was last placed on paths that bypass request history.
167
+ const runtimeStateAnchor = {};
168
+ const resetHistory = () => {
169
+ requestHistory.reset();
170
+ delete runtimeStateAnchor.content;
171
+ delete runtimeStateAnchor.index;
172
+ };
86
173
  let capability;
174
+ const files = new FileTracking();
175
+ const toolExecutionEffective = () => capability?.toolExecution?.effective === true;
176
+ const compactionEffective = () => capability?.compaction?.effective === true;
177
+ const cacheHealth = new CacheHealth();
87
178
  let prompts;
88
- let context;
89
- let virtualization;
90
- let history;
91
179
  let refreshState = true;
92
- let sessionContext;
180
+ // Pi's event contexts read the host model and effort live. Its interactive shortcut contexts copy them at the
181
+ // key press, so after a switch they still name the previous model; routing must never judge a switch by them.
182
+ let liveHost;
183
+ const withLiveModel = (ctx) =>
184
+ Object.defineProperties(
185
+ { ...ctx },
186
+ {
187
+ model: { enumerable: true, get: () => liveHost?.model ?? ctx.model },
188
+ thinkingLevel: { enumerable: true, get: () => api.getThinkingLevel?.() ?? ctx.thinkingLevel },
189
+ },
190
+ );
93
191
  let surfaceGeneration = 0;
94
192
  const unavailable = (state, message) => ({
95
193
  ...state,
@@ -98,183 +196,352 @@ export default function freeflow(pi) {
98
196
  });
99
197
  async function loadSurface(ctx) {
100
198
  const generation = surfaceGeneration;
101
- const next = await readCapabilityState(ctx.cwd, ctx, pi.host);
102
- const loaded = await getRuntimeContext(next);
199
+ const next = await readCapabilityState(ctx.cwd, ctx);
200
+ const loaded = await getRuntimeContext(STABLE_FREEFLOW_SURFACE);
103
201
  if (generation !== surfaceGeneration) throw new Error("Discarded surface preparation for a replaced session.");
104
202
  if (!hasUsableMandatoryPrompts(loaded)) {
105
- for (const key of ["cognitiveRouting", "contextVirtualization", "conversationHistory"])
203
+ for (const key of ["cognitiveRouting", "toolExecution"])
106
204
  next[key] = unavailable(next[key], "Mandatory Freeflow prompts are unavailable.");
107
205
  }
108
206
  if (next.cognitiveRouting.effective && !isPromptAvailable(loaded.cognitiveRoutingPrompt))
109
207
  next.cognitiveRouting = unavailable(next.cognitiveRouting, "Routing bootstrap cue is unavailable.");
110
- if (next.contextVirtualization.effective && !isPromptAvailable(loaded.contextVirtualizationPrompt))
111
- next.contextVirtualization = unavailable(
112
- next.contextVirtualization,
113
- "Context Virtualization guidance is unavailable.",
114
- );
115
- if (next.conversationHistory.effective && !isPromptAvailable(loaded.conversationHistoryPrompt))
116
- next.conversationHistory = unavailable(next.conversationHistory, "Conversation History guidance is unavailable.");
117
- // The existing reduction engine is not the future Context Control port. Do not pretend
118
- // its independent transform ordering has been qualified with the new projection.
119
- if (
120
- next.cognitiveRouting.effective &&
121
- next.cognitiveRouting.projection &&
122
- (next.contextVirtualization.effective || next.conversationHistory.effective)
123
- )
124
- next.cognitiveRouting = unavailable(
125
- next.cognitiveRouting,
126
- "New routing projection with legacy context transforms is not qualified. Disable that combination explicitly.",
127
- );
128
- if (next.cognitiveRouting.enabled && isPiFlowHost(pi.host))
129
- next.cognitiveRouting = unavailable(
130
- next.cognitiveRouting,
131
- "The redesigned PiFlow adapter requires separate qualification.",
132
- );
133
208
  capability = next;
134
209
  prompts = loaded;
135
- sessionContext = ctx;
136
210
  return next;
137
211
  }
138
- function contextTools() {
139
- registerFreeflowContextTool(
140
- api,
141
- () => virtualization,
142
- () => history,
143
- {
144
- contextVirtualization: capability?.contextVirtualization?.effective === true,
145
- conversationHistory: capability?.conversationHistory?.effective === true,
146
- },
147
- );
148
- if (api.getActiveTools && api.setActiveTools) {
149
- const current = new Set(api.getActiveTools());
150
- if (capability?.contextVirtualization?.effective || capability?.conversationHistory?.effective)
151
- current.add(CONTEXT_VIRTUALIZATION_TOOL_NAME);
152
- else current.delete(CONTEXT_VIRTUALIZATION_TOOL_NAME);
153
- const next = [...current];
154
- if (JSON.stringify(next) !== JSON.stringify(api.getActiveTools())) api.setActiveTools(next);
155
- }
156
- }
157
212
  function status(ctx) {
158
213
  applyRoutingToolVisibility(api, routing, capability?.cognitiveRouting?.effective === true);
214
+ applyToolExecutionTools(api, toolExecutionEffective());
215
+ applyCompactionTools(api, compactionEffective());
159
216
  setFreeflowStatus(ctx, capability, routing.state(), prompts);
160
217
  }
218
+ // Control and setting changes made between prompts are staged; their net effect is written as a prompt starts.
219
+ function writeStagedControl(ctx) {
220
+ const staged = takeStage(ctx.sessionManager);
221
+ routing.writeStaged(staged.events);
222
+ writeStagedSessionOverrides(staged.overrides, api, ctx.sessionManager);
223
+ }
161
224
  async function update(ctx) {
162
225
  const next = await loadSurface(ctx);
163
226
  await routing.refresh(ctx, next.cognitiveRouting);
164
- contextTools();
165
227
  status(ctx);
166
228
  refreshState = true;
167
229
  }
230
+ /**
231
+ * Turn Cognitive Routing on for this session when it is off: clear a session "off" (such as the one an outside model
232
+ * change sets), and switch it on for the session if configuration leaves it off. Re-enabling hands control to the
233
+ * automatic Coordinator. Configuration that still blocks routing (no profiles, invalid presets) is reported by the
234
+ * control that follows.
235
+ */
236
+ async function turnRoutingOn(ctx) {
237
+ if (capability?.cognitiveRouting?.effective === true || ctx.isIdle?.() === false) return;
238
+ if (capability?.cognitiveRouting?.enabledSource === "session")
239
+ await setSessionCoreOverride("cognitiveRouting.enabled", null, ctx, api);
240
+ if ((await readCapabilityState(ctx.cwd, ctx)).cognitiveRouting?.enabled !== true)
241
+ await setSessionCoreOverride("cognitiveRouting.enabled", true, ctx, api);
242
+ await update(ctx);
243
+ }
168
244
  registerRoutingTools(api, routing);
169
- registerFreeflowContextTool(
170
- api,
171
- () => virtualization,
172
- () => history,
173
- { contextVirtualization: false, conversationHistory: false },
174
- );
245
+ registerApplyPatch(api, {
246
+ effective: toolExecutionEffective,
247
+ files: (ctx) => files.forPatch(ctx),
248
+ written: (paths, ctx) => files.written(paths, ctx),
249
+ });
250
+ const backgroundHost = {
251
+ effective: toolExecutionEffective,
252
+ shellPath: () => api.getSettings?.()?.shellPath,
253
+ // An exit notice starts a turn when idle; mid-run it is steered in after the current turn's tool results.
254
+ send: (content, details, triggerTurn) => {
255
+ const notice = () =>
256
+ Promise.resolve(
257
+ api.sendMessage(
258
+ { customType: NOTICE_TYPE, content, display: true, details },
259
+ triggerTurn ? { triggerTurn: true, deliverAs: "steer" } : { triggerTurn: false },
260
+ ),
261
+ ).catch(() => {});
262
+ // When idle, the notice starts the run as a user message: a run an extension message starts loses Freeflow's
263
+ // system sections from its second request (Pi #10267). If a run started meanwhile, it is steered in instead.
264
+ if (!triggerTurn || liveHost?.isIdle?.() !== true) return notice();
265
+ try {
266
+ return Promise.resolve(api.sendUserMessage(content)).catch(notice);
267
+ } catch {
268
+ return notice();
269
+ }
270
+ },
271
+ };
272
+ const background = new BackgroundJobs(backgroundHost);
273
+ registerBackgroundTools(api, background, backgroundHost);
274
+ const compaction = new CompactionController({
275
+ effective: compactionEffective,
276
+ routingProfile: () => {
277
+ const state = routing.state();
278
+ return state.effective ? state.activeProfile : undefined;
279
+ },
280
+ routingAssignment: () => routing.currentAssignment(),
281
+ assignmentResults: () => routing.assignmentResults(),
282
+ background: () => background.running().map(({ id, label, outputPath }) => ({ id, label, outputPath })),
283
+ noticePrefix: NOTICE_PREFIX,
284
+ carryEnabled: () => capability?.compaction?.carry !== false,
285
+ nativeRefs: () => capability?.cognitiveRouting?.effective === true,
286
+ resolveRef: (ref) => routing.evidenceSource(ref),
287
+ coordinatorUnderProjection: () =>
288
+ capability?.cognitiveRouting?.effective === true &&
289
+ capability.cognitiveRouting.projection === true &&
290
+ routing.state().effective === true &&
291
+ routing.state().activeProfile === "coordinator",
292
+ summarize: async (preparation, instructions, signal, ctx) => {
293
+ const auth = await ctx.modelRegistry?.getApiKeyAndHeaders?.(ctx.model);
294
+ if (!ctx.model || !auth?.ok) throw new Error("No request auth for the summarizer");
295
+ const model = auth.baseUrl ? { ...ctx.model, baseUrl: auth.baseUrl } : ctx.model;
296
+ return piCompact(
297
+ preparation,
298
+ model,
299
+ auth.apiKey,
300
+ auth.headers,
301
+ instructions,
302
+ signal,
303
+ api.getThinkingLevel?.(),
304
+ undefined,
305
+ auth.env,
306
+ );
307
+ },
308
+ measure: (ctx, turn) => {
309
+ // Every model that may receive the full history: the active one and, under routing, each profile's.
310
+ const routingCapability = capability?.cognitiveRouting;
311
+ const models = [ctx.model];
312
+ if (routingCapability?.effective)
313
+ for (const profile of Object.values(routingCapability.profiles ?? {}))
314
+ models.push(ctx.modelRegistry?.find?.(profile.provider, profile.model));
315
+ const limits = strictest(models, api.getSettings?.());
316
+ if (!limits) return undefined;
317
+ let tokens = turnContextTokens(turn) ?? ctx.getContextUsage?.()?.tokens ?? 0;
318
+ // A Coordinator under projection sends a reduced view; the next worker request carries the whole history.
319
+ if (
320
+ routingCapability?.effective &&
321
+ routingCapability.projection &&
322
+ routing.state().activeProfile === "coordinator"
323
+ )
324
+ tokens = Math.max(tokens, fullHistoryTokens(ctx));
325
+ return { tokens, thresholds: limits };
326
+ },
327
+ });
328
+ registerCompactionTool(api, compaction);
329
+ // The user's /freeflow compact makes compaction due now: the agent prepares, compacts and recovers.
330
+ async function compactNow(ctx) {
331
+ if (!compactionEffective()) {
332
+ ctx.ui.notify("Freeflow compaction is off; Pi's /compact still works.", "warning");
333
+ return;
334
+ }
335
+ compaction.requestByUser(ctx);
336
+ // The user asked, so this is their message. A user message also starts an ordinary run, which keeps Freeflow's
337
+ // system sections; a run started by an extension message loses them from its second request (Pi #10267).
338
+ const index = compaction.indexText(ctx);
339
+ const text = `${USER_REQUEST}${index ? `\n\n${index}` : ""}`;
340
+ await Promise.resolve(
341
+ api.sendUserMessage(text, ctx.isIdle?.() === false ? { deliverAs: "steer" } : undefined),
342
+ ).catch(() => {});
343
+ }
175
344
  pi.on("resources_discover", async (event, ctx) => {
176
345
  const state = capability ?? (await loadSurface(ctx ?? { cwd: event?.cwd ?? process.cwd() }));
177
- if (!state.configured) return { skillPaths: [freeflowSkillPath("setup-freeflow")] };
178
- return { skillPaths: state.enabled && hasUsableMandatoryPrompts(prompts) ? freeflowModelSkillPaths(state) : [] };
346
+ return {
347
+ skillPaths: freeflowModelSkillPaths({ ...STABLE_FREEFLOW_SURFACE, toolExecution: state.toolExecution }),
348
+ };
179
349
  });
180
350
  pi.on("session_start", async (event, ctx) => {
351
+ liveHost = ctx;
181
352
  const generation = ++surfaceGeneration;
353
+ resetHistory();
354
+ // Where Pi has just parsed this session file, later checks read only the bytes appended since.
355
+ trustStartedSession(event?.reason, ctx.sessionManager);
182
356
  routing.unbind();
183
357
  capability = undefined;
184
358
  prompts = undefined;
185
359
  refreshState = true;
186
360
  restoreSessionOverrides(ctx);
187
- const initial = await readCapabilityState(ctx.cwd, ctx, pi.host);
188
- await refreshRuntimeContext(initial);
361
+ await refreshRuntimeContext(STABLE_FREEFLOW_SURFACE);
189
362
  if (generation !== surfaceGeneration) return;
190
363
  await loadSurface(ctx);
191
- context = new FreeflowContextRuntime(ctx);
192
- virtualization = new ContextVirtualizationRuntime(pi, ctx, context);
193
- history = new ConversationHistoryRuntime(
194
- ctx,
195
- (entryId) => virtualization?.isSourceFullyProjected(entryId) ?? true,
196
- context,
197
- );
198
- await virtualization.recover(ctx);
364
+ cacheHealth.reset();
365
+ files.reset();
366
+ compaction.reset();
199
367
  await routing.bind(ctx, capability.cognitiveRouting, event?.reason !== "reload");
200
- contextTools();
201
368
  status(ctx);
202
369
  });
203
370
  pi.on("session_shutdown", async () => {
204
371
  surfaceGeneration++;
372
+ // Background processes belong to this extension instance; none may report into the next session.
373
+ await background.stopAll();
374
+ files.reset();
375
+ compaction.reset();
376
+ resetHistory();
205
377
  routing.unbind();
206
- context = undefined;
207
- virtualization = undefined;
208
- history = undefined;
209
378
  capability = undefined;
210
379
  prompts = undefined;
211
- sessionContext = undefined;
212
380
  });
213
381
  pi.on("before_agent_start", async (event, ctx) => {
214
382
  await update(ctx);
215
- await routing.beforeRun(ctx);
216
- status(ctx);
217
- const text = runtimeContext(prompts, capability);
218
- return { systemPrompt: text ? `${event.systemPrompt}\n\n${text}` : event.systemPrompt };
383
+ try {
384
+ await routing.beforeRun(ctx);
385
+ } finally {
386
+ // Pi reports a handler error and still sends the prompt, so the fixed guidance section and the staged
387
+ // control must never be skipped: a missing section would change the cached prompt head.
388
+ // After the run's own reconciliation, so everything staged lands ahead of the prompt in one pass.
389
+ writeStagedControl(ctx);
390
+ status(ctx);
391
+ // Pi 0.87 records changed sections at their native transcript position.
392
+ // Returning systemPrompt would force one replacement head for every request.
393
+ const sections = event.systemPromptOptions.sections;
394
+ const toolExecution = capability?.toolExecution?.effective === true;
395
+ // Pi's own sections are replaced in place. Freeflow's are re-inserted at the end in a fixed order: its guidance,
396
+ // then Tool Execution's environment facts and guidance.
397
+ if (toolExecution)
398
+ Object.assign(sections, toolExecutionSections(event.systemPrompt, api.getSettings?.()?.shellPath));
399
+ for (const name of ["freeflow_guidance", "environment", "tool_execution"]) delete sections[name];
400
+ sections.freeflow_guidance = stableRuntimeContext(prompts);
401
+ if (toolExecution)
402
+ Object.assign(sections, toolExecutionTail(api.getSettings?.()?.shellPath, prompts?.toolExecutionPrompt));
403
+ }
404
+ // Like Claude Code's next-turn reminder: files the model read that changed while it was not running a tool.
405
+ const changed = toolExecutionEffective() ? await files.changedNotice(ctx, false) : undefined;
406
+ if (changed)
407
+ return { message: { customType: "freeflow-files", content: `${NOTICE_PREFIX} ${changed}`, display: true } };
219
408
  });
220
- pi.on("message_end", (event) => routing.messageEnd(event.message));
221
- pi.on("tool_call", (event, ctx) => {
409
+ pi.on("agent_start", () => routing.runStarted());
410
+ pi.on("message_end", async (event, ctx) => {
411
+ const message = event.message;
222
412
  if (
223
- event.toolName === CONTEXT_VIRTUALIZATION_TOOL_NAME &&
224
- !capability?.contextVirtualization?.effective &&
225
- !capability?.conversationHistory?.effective
226
- )
227
- return { block: true, reason: "Freeflow Context is disabled." };
228
- return routing.preflight(event, ctx);
413
+ capability?.enabled === true &&
414
+ message?.role === "assistant" &&
415
+ message.usage &&
416
+ message.stopReason !== "error"
417
+ ) {
418
+ const scope = routing.observationScope();
419
+ const lifetime = ctx.model?.promptCache?.[process.env.PI_CACHE_RETENTION === "long" ? "long" : "short"];
420
+ const warning = cacheHealth.observe({
421
+ provider: message.provider,
422
+ model: message.model,
423
+ thinking: ctx.thinkingLevel,
424
+ at: Date.now(),
425
+ input: message.usage.input ?? 0,
426
+ cacheRead: message.usage.cacheRead ?? 0,
427
+ cacheWrite: message.usage.cacheWrite ?? 0,
428
+ ...(scope.control === "automatic" && scope.profile !== "solo" ? { lane: scope.profile } : {}),
429
+ ...(typeof lifetime === "number" ? { ttlMs: lifetime * 1000 } : {}),
430
+ });
431
+ if (warning) ctx.ui?.notify?.(warning, "warning");
432
+ }
433
+ routing.messageEnd(event.message);
434
+ // A run started by an extension's message (a background notice, for one) gets the same preparation as a prompt.
435
+ if (routing.startedByMessage(event.message)) {
436
+ await update(ctx);
437
+ try {
438
+ await routing.beforeRun(ctx, false);
439
+ } finally {
440
+ writeStagedControl(ctx);
441
+ status(ctx);
442
+ }
443
+ }
229
444
  });
445
+ pi.on("tool_call", async (event, ctx) => {
446
+ const gate = routing.preflight(event, ctx);
447
+ if (gate?.block || !toolExecutionEffective()) return gate;
448
+ const refusal = event.toolName === "bash" ? backgroundRefusal(event.input?.command) : undefined;
449
+ if (refusal) return { block: true, reason: refusal };
450
+ return (await files.toolCall(event, ctx)) ?? gate;
451
+ });
452
+ pi.on("tool_result", async (event, ctx) => (toolExecutionEffective() ? files.toolResult(event, ctx) : undefined));
230
453
  pi.on("turn_end", async (event, ctx) => {
231
454
  await routing.turnEnd(event, ctx);
455
+ files.clearPending();
232
456
  status(ctx);
457
+ const compacting = await compaction.turnEnd(event, ctx);
458
+ if (compacting) return compacting;
459
+ const notice = compaction.observe(ctx, event);
460
+ if (notice) {
461
+ // Mid-run the notice joins the next request; when the run is ending it waits for the next prompt rather than
462
+ // starting work nobody asked for.
463
+ const midRun = (event.message?.content ?? []).some((block) => block?.type === "toolCall");
464
+ await Promise.resolve(
465
+ api.sendMessage(
466
+ { customType: COMPACTION_NOTICE_TYPE, content: notice.text, display: true, details: { level: notice.level } },
467
+ { deliverAs: midRun ? "steer" : "nextTurn" },
468
+ ),
469
+ ).catch(() => {});
470
+ }
471
+ return undefined;
472
+ });
473
+ // A Freeflow compaction fires no session_compact, so its resets run here, before the new cycle's first request.
474
+ pi.on("turn_start", async (_event, ctx) => {
475
+ const compacted = compaction.takeCompacted();
476
+ if (!compacted) return;
477
+ await restore(ctx, false);
478
+ await files.compacted(compacted.carriedFiles, ctx);
233
479
  });
234
480
  pi.on("agent_settled", async (_event, ctx) => {
235
481
  await routing.settled(ctx);
236
482
  await update(ctx);
237
483
  });
238
- pi.on("model_select", async (_event, ctx) => {
239
- await routing.nativeChange(ctx);
240
- status(ctx);
241
- });
242
- pi.on("thinking_level_select", async (_event, ctx) => {
243
- await routing.nativeChange(ctx);
484
+ // A model or effort picked in Pi itself (/model, Shift+Tab) ends automatic routing. Routing then turns off for this
485
+ // session through its Session switch, so settings and the footer show it off, and any routing control turns it on.
486
+ const externalChange = async (ctx) => {
487
+ cacheHealth.noteSwitch();
488
+ if (await routing.nativeChange(ctx)) {
489
+ await setSessionCoreOverride("cognitiveRouting.enabled", false, ctx, api);
490
+ await update(ctx);
491
+ }
244
492
  status(ctx);
245
- });
246
- pi.on("context", async (event, ctx) => {
493
+ };
494
+ pi.on("model_select", async (_event, ctx) => externalChange(ctx));
495
+ pi.on("thinking_level_select", async (_event, ctx) => externalChange(ctx));
496
+ pi.on("context_with_system", async (event, ctx) => {
497
+ writeStagedControl(ctx);
247
498
  if (!capability) await loadSurface(ctx);
499
+ // Entry identity lets attribution and request history skip hashing unchanged native history.
500
+ tagProjectedMessages(
501
+ event.messages,
502
+ ctx.sessionManager?.buildSessionProjection?.(),
503
+ cachedBranch(ctx.sessionManager) ?? [],
504
+ );
248
505
  let messages = event.messages.map(filterBootstrapMessage).filter(Boolean);
249
- if (virtualization) {
250
- virtualization.setContext(ctx);
251
- const projected = await virtualization.project(messages, capability.contextVirtualization.effective);
252
- messages = projected.messages;
253
- }
254
- if (history && capability.conversationHistory.effective) {
255
- history.setContext(ctx);
256
- history.capture(capability.contextVirtualization.effective);
257
- }
258
506
  messages = withFreeflowRuntimeState(messages, capability, routing.state(), prompts, {
259
507
  force: refreshState,
508
+ anchor: runtimeStateAnchor,
260
509
  projectionFailure: routing.state().projectionFailure,
261
510
  });
262
511
  refreshState = false;
263
- return { messages: await routing.context(ctx, messages) };
512
+ // Disabled Freeflow pays no per-request provenance work; re-enabling rebuilds it from session history.
513
+ const projected = capability?.enabled === true ? await routing.context(ctx, messages) : messages;
514
+ const state = routing.state();
515
+ if (projected.some((message) => message.details?.routingRequestBlocked === true)) {
516
+ // Routing must not expose hidden worker history on failure, but Pi's full-
517
+ // transcript hook still requires the leading system prompt/tool state.
518
+ const head = event.messages[0]?.role === "system" ? [event.messages[0]] : [];
519
+ return { messages: [...head, ...projected] };
520
+ }
521
+ const view = routing.projectionEnabled && state.activeProfile === "coordinator" ? "coordinator" : "ordinary";
522
+ return {
523
+ messages: await requestHistory.assemble(projected, view, ctx, (assembled) =>
524
+ routing.budgetNotice(assembled, ctx),
525
+ ),
526
+ };
264
527
  });
265
528
  const restore = async (ctx, navigation = true) => {
529
+ cacheHealth.noteBreak();
530
+ resetHistory();
531
+ // Navigation selects another branch, whose reads differ; compaction keeps the branch and its file state.
532
+ if (navigation) files.invalidate();
266
533
  restoreSessionOverrides(ctx);
267
534
  refreshState = true;
268
- if (virtualization) {
269
- virtualization.setContext(ctx);
270
- await virtualization.recover(ctx);
271
- }
272
- history?.setContext(ctx);
273
535
  await routing.ancestryChanged(ctx, navigation);
274
536
  await update(ctx);
275
537
  };
276
538
  pi.on("session_tree", async (_event, ctx) => restore(ctx));
277
- pi.on("session_compact", async (_event, ctx) => restore(ctx, false));
539
+ // Pi compacting on its own (threshold, overflow, /compact) keeps its summarizer and kept tail, with Freeflow's additions.
540
+ pi.on("session_before_compact", async (event, ctx) => compaction.fallback(event, ctx));
541
+ pi.on("session_compact", async (_event, ctx) => {
542
+ await restore(ctx, false);
543
+ background.restate();
544
+ });
278
545
  pi.on("session_compact_failed", async (_event, ctx) => {
279
546
  refreshState = true;
280
547
  status(ctx);
@@ -286,24 +553,27 @@ export default function freeflow(pi) {
286
553
  typeof api.setThinkingLevel === "function"
287
554
  ) {
288
555
  pi.registerShortcut("ctrl+shift+r", {
289
- description: "Cycle the Coordinator/Executor manual hold",
290
- handler: async (ctx) => {
556
+ description: "Cycle enabled Cognitive Routing manual holds",
557
+ handler: async (shortcutCtx) => {
558
+ const ctx = withLiveModel(shortcutCtx);
291
559
  if (!ctx.isIdle()) {
292
560
  ctx.ui.notify("Wait for Pi to become idle before changing control.", "warning");
293
561
  return;
294
562
  }
295
- const target = routing.state().activeProfile === "coordinator" ? "executor" : "coordinator";
296
- ctx.ui.notify(JSON.stringify(await routing.setManualProfile(target)), "info");
563
+ await turnRoutingOn(ctx);
564
+ ctx.ui.notify(JSON.stringify(await routing.cycleManualProfile()), "info");
297
565
  await update(ctx);
298
566
  },
299
567
  });
300
568
  pi.registerShortcut("ctrl+shift+a", {
301
- description: "Release manual hold to Automatic Coordinator",
302
- handler: async (ctx) => {
569
+ description: "Turn Cognitive Routing on, or release a manual hold, to the automatic Coordinator",
570
+ handler: async (shortcutCtx) => {
571
+ const ctx = withLiveModel(shortcutCtx);
303
572
  if (!ctx.isIdle()) {
304
573
  ctx.ui.notify("Wait for Pi to become idle before changing control.", "warning");
305
574
  return;
306
575
  }
576
+ await turnRoutingOn(ctx);
307
577
  ctx.ui.notify(JSON.stringify(await routing.setAutomaticControl()), "info");
308
578
  await update(ctx);
309
579
  },
@@ -329,7 +599,7 @@ export default function freeflow(pi) {
329
599
  handler: (args, ctx) => sendSkillCommand(pi, ctx, skill, args),
330
600
  });
331
601
  pi.registerCommand("freeflow", {
332
- description: "Freeflow settings, status, profile control, saved-operation resume, and context tools",
602
+ description: "Freeflow settings, status, profile control, and saved-operation resume",
333
603
  getArgumentCompletions: (prefix) =>
334
604
  freeflowCompletions(
335
605
  prefix,
@@ -338,22 +608,23 @@ export default function freeflow(pi) {
338
608
  typeof api.setThinkingLevel === "function",
339
609
  ),
340
610
  handler: async (args, ctx) => {
341
- if (await routing.command(args ?? "", ctx)) {
342
- await update(ctx);
611
+ if ((args ?? "").trim().toLowerCase() === "compact") {
612
+ await compactNow(ctx);
343
613
  return;
344
614
  }
345
- const input = (args ?? "").trim();
346
- if (input === "context" || input.startsWith("context ")) {
347
- await handleContextCommand(
348
- input.slice(7).trim(),
349
- ctx,
350
- virtualization,
351
- capability?.contextVirtualization.effective,
352
- capability?.conversationHistory.effective,
353
- );
615
+ // A routing control (a hold or `auto`) turns routing on first when it is off.
616
+ if (/^profile\s+(?!history\b)\S/i.test((args ?? "").trim())) await turnRoutingOn(ctx);
617
+ if (await routing.command(args ?? "", ctx)) {
618
+ await update(ctx);
354
619
  return;
355
620
  }
356
- await handleFreeflowCommand(args, ctx, async () => update(ctx), pi, routing);
621
+ await handleFreeflowCommand(args, ctx, async () => update(ctx), pi, routing, {
622
+ status: () => ({
623
+ cacheHealth: [...cacheHealth.status(), ...cacheMonitor.lines()],
624
+ backgroundRunning: background.running().length,
625
+ compaction: compaction.statusText(ctx),
626
+ }),
627
+ });
357
628
  },
358
629
  });
359
630
  }