@hassangameryt/freeflow 0.7.3 → 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 (276) 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 +26 -31
  7. package/capabilities/cognitive-routing/SKILL.md +106 -161
  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 +5 -2
  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/{session-sources/events.js → cognitive-routing/event-store.js} +103 -40
  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-v2 → cognitive-routing}/projection.js +229 -56
  29. package/pi-extension/dist/cognitive-routing/provenance.js +81 -0
  30. package/pi-extension/dist/cognitive-routing/runtime.js +396 -0
  31. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/schemas.js +43 -7
  32. package/pi-extension/dist/cognitive-routing/session.js +221 -0
  33. package/pi-extension/dist/{session-sources → cognitive-routing}/sources.js +99 -85
  34. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/state.js +37 -378
  35. package/pi-extension/dist/cognitive-routing/status.js +80 -0
  36. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/tools.js +12 -12
  37. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/types.js +5 -11
  38. package/pi-extension/dist/compaction/carry.js +191 -0
  39. package/pi-extension/dist/compaction/config.js +22 -0
  40. package/pi-extension/dist/compaction/controller.js +471 -0
  41. package/pi-extension/dist/compaction/harness.js +77 -0
  42. package/pi-extension/dist/compaction/results.js +158 -0
  43. package/pi-extension/dist/compaction/thresholds.js +35 -0
  44. package/pi-extension/dist/compaction/tool.js +57 -0
  45. package/pi-extension/dist/host/branch.js +13 -0
  46. package/pi-extension/dist/host/canonical.js +12 -0
  47. package/pi-extension/dist/host/catalog.js +77 -0
  48. package/pi-extension/dist/host/config.js +374 -0
  49. package/pi-extension/dist/host/projection-tags.js +60 -0
  50. package/pi-extension/dist/host/prompts.js +82 -0
  51. package/pi-extension/dist/{session-sources → host}/read-only-session.js +112 -1
  52. package/pi-extension/dist/{runtime → host}/request-history.js +121 -39
  53. package/pi-extension/dist/host/runtime-state.js +189 -0
  54. package/pi-extension/dist/{settings/settings-ui.js → host/settings/freeflow-command.js} +620 -410
  55. package/pi-extension/dist/{settings/settings-tui.js → host/settings/settings-view.js} +186 -73
  56. package/pi-extension/dist/host/staging.js +28 -0
  57. package/pi-extension/dist/host/status.js +101 -0
  58. package/pi-extension/dist/index.js +423 -173
  59. package/pi-extension/dist/provider-support/cache/anchor.js +21 -0
  60. package/pi-extension/dist/provider-support/cache/anthropic.js +102 -0
  61. package/pi-extension/dist/provider-support/cache/health.js +73 -0
  62. package/pi-extension/dist/provider-support/cache/index.js +58 -0
  63. package/pi-extension/dist/provider-support/cache/keepalive.js +210 -0
  64. package/pi-extension/dist/provider-support/cache/monitor.js +17 -0
  65. package/pi-extension/dist/provider-support/cache/openai-responses.js +48 -0
  66. package/pi-extension/dist/provider-support/effort.js +13 -0
  67. package/pi-extension/dist/provider-support/index.js +44 -3
  68. package/pi-extension/dist/provider-support/{astra → openai}/adapter.js +46 -29
  69. package/pi-extension/dist/provider-support/{astra → openai}/history.js +10 -8
  70. package/pi-extension/dist/provider-support/{astra → openai}/index.js +8 -4
  71. package/pi-extension/dist/provider-support/{astra → openai}/session-state.js +29 -38
  72. package/pi-extension/dist/provider-support/routes.js +17 -0
  73. package/pi-extension/dist/tool-execution/apply-patch/grammar.js +23 -0
  74. package/pi-extension/dist/tool-execution/apply-patch/match.js +38 -0
  75. package/pi-extension/dist/tool-execution/apply-patch/parser.js +177 -0
  76. package/pi-extension/dist/tool-execution/apply-patch/plan.js +201 -0
  77. package/pi-extension/dist/tool-execution/apply-patch/tool.js +84 -0
  78. package/pi-extension/dist/tool-execution/background.js +238 -0
  79. package/pi-extension/dist/tool-execution/bash-guard.js +23 -0
  80. package/pi-extension/dist/tool-execution/config.js +22 -0
  81. package/pi-extension/dist/tool-execution/edit-messages.js +120 -0
  82. package/pi-extension/dist/tool-execution/file-state.js +138 -0
  83. package/pi-extension/dist/tool-execution/file-tracking.js +143 -0
  84. package/pi-extension/dist/tool-execution/prompt.js +54 -0
  85. package/pi-extension/dist/tool-execution/tools.js +16 -0
  86. package/pi-extension/freeflow/README.md +9 -0
  87. package/plugin.json +1 -1
  88. package/runtime/prompts/cognitive-routing.md +4 -4
  89. package/runtime/prompts/core.md +22 -75
  90. package/runtime/prompts/interaction-contract.md +15 -8
  91. package/runtime/prompts/tool-execution.md +38 -0
  92. package/runtime/prompts/working-method.md +108 -0
  93. package/skills/action-selection/SKILL.md +39 -47
  94. package/skills/action-selection/references/trajectory-stalls.md +2 -0
  95. package/skills/bypass/SKILL.md +2 -2
  96. package/skills/commit-work/SKILL.md +31 -83
  97. package/skills/commit-work/references/git-state-edges.md +1 -1
  98. package/skills/commit-work/references/simple-push.md +2 -0
  99. package/skills/commit-work/references/staging-decisions.md +1 -1
  100. package/skills/decision-gate/SKILL.md +31 -50
  101. package/skills/design-for-depth/SKILL.md +44 -53
  102. package/skills/design-for-depth/references/design-pressure-signals.md +46 -144
  103. package/skills/design-for-depth/references/implementation-decisions.md +1 -1
  104. package/skills/design-for-depth/references/interface-design-loop.md +5 -36
  105. package/skills/design-for-depth/references/module-and-dependency-design.md +2 -32
  106. package/skills/design-for-depth/references/state-and-failure-boundaries.md +2 -13
  107. package/skills/diagnose-failure/SKILL.md +39 -24
  108. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +2 -0
  109. package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +2 -2
  110. package/skills/discuss/SKILL.md +35 -11
  111. package/skills/discuss/references/discussion-continuity.md +7 -27
  112. package/skills/evaluate-skill/SKILL.md +49 -80
  113. package/skills/evaluate-skill/references/evaluation-definition-schema.md +35 -5
  114. package/skills/evaluate-skill/references/evaluation-design.md +14 -14
  115. package/skills/evaluate-skill/references/execution-and-evidence.md +26 -5
  116. package/skills/evaluate-skill/references/review-and-revision.md +3 -1
  117. package/skills/evaluate-skill/scripts/lib/definitions.mjs +49 -8
  118. package/skills/evaluate-skill/scripts/lib/pi.mjs +14 -7
  119. package/skills/evaluate-skill/scripts/lib/review.mjs +196 -0
  120. package/skills/evaluate-skill/scripts/lib/runner.mjs +45 -8
  121. package/skills/evaluate-skill/scripts/lib/sandbox.mjs +2 -0
  122. package/skills/evaluate-skill/scripts/lib/trials.mjs +103 -0
  123. package/skills/evaluate-skill/scripts/lib/view.mjs +67 -1
  124. package/skills/evaluate-skill/scripts/pi-command-tool.mjs +144 -0
  125. package/skills/evaluate-skill/scripts/pi-guard.mjs +30 -13
  126. package/skills/evaluate-skill/scripts/skill-eval.mjs +114 -34
  127. package/skills/execute-work/SKILL.md +41 -9
  128. package/skills/execute-work/references/code-practices.md +2 -0
  129. package/skills/execute-work/references/execute-work-edges.md +11 -1
  130. package/skills/handoff/SKILL.md +1 -1
  131. package/skills/handoff/references/handoff-templates.md +1 -1
  132. package/skills/review-artifact/SKILL.md +42 -41
  133. package/skills/review-artifact/references/adjudicate-artifact-review.md +1 -1
  134. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +1 -1
  135. package/skills/review-work/SKILL.md +38 -32
  136. package/skills/review-work/references/adjudicate-work-review.md +1 -1
  137. package/skills/review-work/references/independent-work-reviewer-contract.md +1 -1
  138. package/skills/review-work/references/security-risk-lens.md +2 -4
  139. package/skills/setup-freeflow/SKILL.md +3 -1
  140. package/skills/setup-freeflow/references/activation-contract.md +7 -13
  141. package/skills/setup-freeflow/references/host-setup.md +3 -3
  142. package/skills/track-work/SKILL.md +113 -161
  143. package/skills/track-work/references/working-record-format.md +189 -350
  144. package/skills/track-work/scripts/lib/document.mjs +62 -6
  145. package/skills/track-work/scripts/lib/format.mjs +16 -1
  146. package/skills/track-work/scripts/lib/store.mjs +9 -8
  147. package/skills/track-work/scripts/lib/transitions.mjs +79 -4
  148. package/skills/track-work/scripts/working-record.mjs +34 -12
  149. package/skills/verify-work/SKILL.md +29 -20
  150. package/skills/verify-work/references/browser-runtime-evidence.md +1 -5
  151. package/skills/verify-work/references/integration-evidence.md +2 -0
  152. package/skills/verify-work/references/performance-evidence.md +3 -1
  153. package/skills/verify-work/references/test-design.md +3 -1
  154. package/skills/workflow/SKILL.md +65 -21
  155. package/skills/workflow/references/domain-skill-composition.md +5 -3
  156. package/skills/write-docs/SKILL.md +83 -0
  157. package/skills/write-docs/references/documentation-shapes.md +119 -0
  158. package/skills/write-docs/scripts/docs-check.mjs +139 -0
  159. package/skills/write-plan/SKILL.md +40 -40
  160. package/skills/write-plan/references/plan-shapes.md +1 -1
  161. package/skills/write-skill/SKILL.md +107 -147
  162. package/skills/write-skill/references/activation-boundaries.md +2 -0
  163. package/skills/write-skill/references/agent-first-instructions.md +22 -10
  164. package/skills/write-skill/references/progressive-disclosure.md +4 -2
  165. package/skills/write-skill/references/skill-dependency-graphs.md +2 -0
  166. package/skills/write-skill/scripts/lib/similarity.mjs +56 -0
  167. package/skills/write-skill/scripts/skill-author.mjs +18 -2
  168. package/skills/write-spec/SKILL.md +34 -41
  169. package/skills/write-spec/references/artifact-standards.md +1 -1
  170. package/skills/write-spec/references/decision-records.md +2 -0
  171. package/skills/write-spec/references/spec-shapes.md +1 -1
  172. package/capabilities/context-virtualization/SKILL.md +0 -103
  173. package/capabilities/conversation-history/SKILL.md +0 -53
  174. package/hooks/tests/check-host-adapters.test.mjs +0 -82
  175. package/hooks/tests/check-runtime-context-hook.sh +0 -212
  176. package/pi-extension/dist/cognitive-routing-v2/runtime.js +0 -2181
  177. package/pi-extension/dist/context-virtualization/commands.js +0 -89
  178. package/pi-extension/dist/context-virtualization/projector.js +0 -42
  179. package/pi-extension/dist/context-virtualization/runtime.js +0 -378
  180. package/pi-extension/dist/context-virtualization/state.js +0 -57
  181. package/pi-extension/dist/context-virtualization/types.js +0 -10
  182. package/pi-extension/dist/conversation-history/passages.js +0 -66
  183. package/pi-extension/dist/conversation-history/ranking.js +0 -105
  184. package/pi-extension/dist/conversation-history/runtime.js +0 -226
  185. package/pi-extension/dist/conversation-history/search.js +0 -56
  186. package/pi-extension/dist/conversation-history/types.js +0 -1
  187. package/pi-extension/dist/freeflow-context/resolver.js +0 -120
  188. package/pi-extension/dist/freeflow-context/runtime.js +0 -80
  189. package/pi-extension/dist/freeflow-context/source-projector.js +0 -95
  190. package/pi-extension/dist/freeflow-context/tool.js +0 -314
  191. package/pi-extension/dist/freeflow-context/types.js +0 -12
  192. package/pi-extension/dist/freeflow-context/visibility.js +0 -28
  193. package/pi-extension/dist/integrations/mcp-config.js +0 -29
  194. package/pi-extension/dist/runtime/runtime-context.js +0 -754
  195. package/pi-extension/dist/runtime/runtime-identity.js +0 -5
  196. package/pi-extension/dist/session-sources/provenance.js +0 -32
  197. package/pi-extension/dist/ui/text-component.js +0 -57
  198. package/pi-extension/src/cognitive-routing-v2/budget.ts +0 -42
  199. package/pi-extension/src/cognitive-routing-v2/config.ts +0 -173
  200. package/pi-extension/src/cognitive-routing-v2/projection.ts +0 -375
  201. package/pi-extension/src/cognitive-routing-v2/render.ts +0 -277
  202. package/pi-extension/src/cognitive-routing-v2/runtime.ts +0 -2241
  203. package/pi-extension/src/cognitive-routing-v2/schemas.ts +0 -180
  204. package/pi-extension/src/cognitive-routing-v2/state.ts +0 -839
  205. package/pi-extension/src/cognitive-routing-v2/tools.ts +0 -62
  206. package/pi-extension/src/cognitive-routing-v2/types.ts +0 -254
  207. package/pi-extension/src/context-virtualization/commands.ts +0 -111
  208. package/pi-extension/src/context-virtualization/projector.ts +0 -62
  209. package/pi-extension/src/context-virtualization/runtime.ts +0 -428
  210. package/pi-extension/src/context-virtualization/state.ts +0 -79
  211. package/pi-extension/src/context-virtualization/types.ts +0 -92
  212. package/pi-extension/src/conversation-history/passages.ts +0 -81
  213. package/pi-extension/src/conversation-history/ranking.ts +0 -155
  214. package/pi-extension/src/conversation-history/runtime.ts +0 -250
  215. package/pi-extension/src/conversation-history/search.ts +0 -79
  216. package/pi-extension/src/conversation-history/types.ts +0 -31
  217. package/pi-extension/src/freeflow-context/resolver.ts +0 -139
  218. package/pi-extension/src/freeflow-context/runtime.ts +0 -100
  219. package/pi-extension/src/freeflow-context/source-projector.ts +0 -106
  220. package/pi-extension/src/freeflow-context/tool.ts +0 -366
  221. package/pi-extension/src/freeflow-context/types.ts +0 -70
  222. package/pi-extension/src/freeflow-context/visibility.ts +0 -53
  223. package/pi-extension/src/index.ts +0 -390
  224. package/pi-extension/src/integrations/mcp-config.ts +0 -32
  225. package/pi-extension/src/provider-support/astra/adapter.ts +0 -108
  226. package/pi-extension/src/provider-support/astra/history.ts +0 -145
  227. package/pi-extension/src/provider-support/astra/index.ts +0 -12
  228. package/pi-extension/src/provider-support/astra/session-state.ts +0 -113
  229. package/pi-extension/src/provider-support/index.ts +0 -5
  230. package/pi-extension/src/runtime/request-history.ts +0 -243
  231. package/pi-extension/src/runtime/runtime-context.ts +0 -860
  232. package/pi-extension/src/runtime/runtime-identity.ts +0 -8
  233. package/pi-extension/src/session-sources/events.ts +0 -230
  234. package/pi-extension/src/session-sources/provenance.ts +0 -39
  235. package/pi-extension/src/session-sources/read-only-session.ts +0 -170
  236. package/pi-extension/src/session-sources/sources.ts +0 -334
  237. package/pi-extension/src/settings/settings-tui.ts +0 -501
  238. package/pi-extension/src/settings/settings-ui.ts +0 -1652
  239. package/pi-extension/src/ui/text-component.ts +0 -66
  240. package/pi-extension/tests/cache-reuse/history.test.js +0 -117
  241. package/pi-extension/tests/cache-reuse/native.test.js +0 -91
  242. package/pi-extension/tests/cache-reuse/settings.test.js +0 -211
  243. package/pi-extension/tests/cognitive-routing-v2/communication.test.js +0 -577
  244. package/pi-extension/tests/cognitive-routing-v2/compaction-input.test.js +0 -183
  245. package/pi-extension/tests/cognitive-routing-v2/delegation.test.js +0 -557
  246. package/pi-extension/tests/cognitive-routing-v2/native.test.js +0 -474
  247. package/pi-extension/tests/cognitive-routing-v2/performance.test.js +0 -87
  248. package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +0 -300
  249. package/pi-extension/tests/cognitive-routing-v2/projection.test.js +0 -241
  250. package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +0 -1034
  251. package/pi-extension/tests/cognitive-routing-v2/render.test.js +0 -244
  252. package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +0 -423
  253. package/pi-extension/tests/cognitive-routing-v2/stabilization.test.js +0 -521
  254. package/pi-extension/tests/cognitive-routing-v2/state.test.js +0 -586
  255. package/pi-extension/tests/context-virtualization/extension.test.js +0 -775
  256. package/pi-extension/tests/context-virtualization/projector.test.js +0 -110
  257. package/pi-extension/tests/context-virtualization/runtime.test.js +0 -230
  258. package/pi-extension/tests/conversation-history/runtime.test.js +0 -98
  259. package/pi-extension/tests/conversation-history/search.test.js +0 -97
  260. package/pi-extension/tests/fixtures/pi-host.js +0 -4
  261. package/pi-extension/tests/fixtures/routing-native.js +0 -191
  262. package/pi-extension/tests/integration/mode-free-core.test.js +0 -220
  263. package/pi-extension/tests/integration/pi-extension.test.js +0 -1447
  264. package/pi-extension/tests/integration/prompt-architecture.test.js +0 -447
  265. package/pi-extension/tests/integration/unaffected-regressions.test.js +0 -478
  266. package/pi-extension/tests/provider-support/astra-boundaries.test.js +0 -75
  267. package/pi-extension/tests/provider-support/astra.test.js +0 -306
  268. package/pi-extension/tests/provider-support/cli.test.js +0 -92
  269. package/pi-extension/tests/provider-support/native.test.js +0 -183
  270. package/pi-extension/tests/settings/settings-tui.test.js +0 -243
  271. package/pi-extension/tsconfig.json +0 -16
  272. package/runtime/prompts/context-virtualization.md +0 -3
  273. package/runtime/prompts/conversation-history.md +0 -3
  274. package/skills/design-for-depth/references/software-design-philosophy.md +0 -202
  275. /package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/budget.js +0 -0
  276. /package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/render.js +0 -0
@@ -1,23 +1,26 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
- import { activeReadOnlySessionBranch, readOnlySessionSnapshot } from "../session-sources/read-only-session.js";
2
+ import { persistedBranchMatches } from "./read-only-session.js";
3
+ import { occurrenceIdentity } from "./projection-tags.js";
4
+ import { cachedBranch as activeBranch } from "./branch.js";
3
5
  const ENTRY = "freeflow-request-history-v1";
4
6
  const TRANSIENT = new Set([
5
7
  "freeflow-runtime-state",
6
8
  "freeflow-cognitive-routing-runtime-state",
7
9
  "freeflow-routing-v2-state",
10
+ "freeflow-routing-attention",
8
11
  "freeflow-routing-budget",
9
12
  "freeflow-routing-communication",
10
13
  ]);
14
+ // Situational notices describe a condition, not standing state; once absent, the next occurrence is new.
15
+ const SITUATIONAL = new Set(["freeflow-routing-attention"]);
11
16
  const hash = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
17
+ /** Whole-message fingerprints computed so far; the request-path budget test counts them per request. */
18
+ export const requestHistoryCounters = { fingerprints: 0 };
12
19
  function fingerprint(message) {
20
+ requestHistoryCounters.fingerprints++;
13
21
  const { usage: _usage, timestamp: _timestamp, details: _details, ...body } = message;
14
22
  return hash(body);
15
23
  }
16
- function prefixes(messages) {
17
- const result = [hash([])];
18
- for (const message of messages) result.push(hash([result.at(-1), fingerprint(message)]));
19
- return result;
20
- }
21
24
  function owned(message) {
22
25
  return message?.role === "custom" && TRANSIENT.has(message.customType);
23
26
  }
@@ -50,17 +53,60 @@ function parse(value) {
50
53
  /** Preserve only Freeflow-generated occurrences. Native content remains owned by Pi and its projectors. */
51
54
  export class RequestHistory {
52
55
  pi;
56
+ monitor;
53
57
  acknowledged = new Map();
58
+ // Pi reuses immutable native entry objects across requests. A replacement object with the
59
+ // same ID must be validated again; a known entry need not be rehashed and reparsed.
60
+ validatedFrames = new Map();
61
+ prefixCache;
62
+ branchCache;
63
+ frameHash = (entry) => {
64
+ const cached = this.validatedFrames.get(entry.id);
65
+ return cached?.entry === entry ? cached.hash : hash(entry);
66
+ };
67
+ // Unedited native entries and Freeflow's renderings of them never change under their identity, so their
68
+ // fingerprints are computed once per session.
69
+ fingerprints = new Map();
70
+ fingerprintOf = (message) => {
71
+ const id = occurrenceIdentity(message);
72
+ if (id === undefined) return fingerprint(message);
73
+ let value = this.fingerprints.get(id);
74
+ if (value === undefined) this.fingerprints.set(id, (value = fingerprint(message)));
75
+ return value;
76
+ };
77
+ prefixes(messages) {
78
+ const previous = this.prefixCache;
79
+ const identities = [];
80
+ const values = [hash([])];
81
+ let samePrefix = true;
82
+ for (let i = 0; i < messages.length; i++) {
83
+ const message = messages[i];
84
+ const entryId = occurrenceIdentity(message);
85
+ // Generated and host-edited messages have no stable identity and must be hashed anew.
86
+ const body = entryId === undefined ? fingerprint(message) : undefined;
87
+ const identity = entryId === undefined ? `body:${body}` : `entry:${entryId}`;
88
+ identities.push(identity);
89
+ samePrefix = samePrefix && previous?.identities[i] === identity;
90
+ values.push(samePrefix ? previous.values[i + 1] : hash([values[i], body ?? this.fingerprintOf(message)]));
91
+ }
92
+ this.prefixCache = { identities, values };
93
+ return values;
94
+ }
54
95
  identity;
55
96
  fault = false;
56
97
  serial = Promise.resolve();
57
98
  revision = 0;
58
- constructor(pi) {
99
+ constructor(pi, monitor) {
59
100
  this.pi = pi;
101
+ this.monitor = monitor;
60
102
  }
61
103
  reset() {
62
104
  this.revision++;
63
105
  this.acknowledged.clear();
106
+ this.validatedFrames.clear();
107
+ this.prefixCache = undefined;
108
+ this.branchCache = undefined;
109
+ this.fingerprints.clear();
64
110
  this.identity = undefined;
65
111
  this.fault = false;
66
112
  }
@@ -71,9 +117,7 @@ export class RequestHistory {
71
117
  try {
72
118
  return await this.prepare(messages, view, ctx, revision, notice);
73
119
  } catch {
74
- try {
75
- ctx.ui?.setStatus?.("freeflow-cache", "Freeflow context replay unavailable · current view retained");
76
- } catch {}
120
+ this.monitor?.set("context-replay", "Freeflow context replay unavailable · current view retained");
77
121
  // Keep the already-qualified projected view on failure, never raw hidden history.
78
122
  return messages;
79
123
  }
@@ -87,42 +131,58 @@ export class RequestHistory {
87
131
  const identity = `${reader.getSessionId()}:${reader.getSessionFile?.() ?? "memory"}`;
88
132
  if (this.identity !== identity) {
89
133
  this.acknowledged.clear();
134
+ this.validatedFrames.clear();
135
+ this.prefixCache = undefined;
136
+ this.branchCache = undefined;
137
+ this.fingerprints.clear();
90
138
  this.fault = false;
91
139
  this.identity = identity;
92
140
  }
93
141
  if (this.fault) throw new Error("Uncertain Freeflow context append");
94
- const branch = reader.getBranch(),
142
+ const branch = activeBranch(reader),
95
143
  leaf = reader.getLeafId();
96
- let parent = null,
97
- generation = "root";
98
- const ids = new Set();
99
- for (const entry of branch) {
100
- if (entry.parentId !== parent || ids.has(entry.id)) throw new Error("Invalid context ancestry");
144
+ const cachedBranch = this.branchCache;
145
+ // Pi appends immutable native entry objects. A sibling/edited branch breaks this exact prefix.
146
+ const extendsCache =
147
+ !!cachedBranch &&
148
+ cachedBranch.length <= branch.length &&
149
+ (cachedBranch.length === 0 ||
150
+ (branch[0] === cachedBranch.first && branch[cachedBranch.length - 1] === cachedBranch.last));
151
+ const start = extendsCache ? cachedBranch.length : 0;
152
+ let parent = extendsCache ? cachedBranch.leaf : null;
153
+ let generation = extendsCache ? cachedBranch.generation : "root";
154
+ const ids = extendsCache ? cachedBranch.ids : new Set();
155
+ const newIds = new Set();
156
+ for (let i = start; i < branch.length; i++) {
157
+ const entry = branch[i];
158
+ if (entry.parentId !== parent || ids.has(entry.id) || newIds.has(entry.id))
159
+ throw new Error("Invalid context ancestry");
101
160
  parent = entry.id;
102
- ids.add(entry.id);
103
- if (entry.type === "compaction" || entry.type === "branch_summary") generation = entry.id;
161
+ newIds.add(entry.id);
162
+ // Compaction replaces earlier history. A branch summary follows its branch point and keeps the path
163
+ // before it intact, so generated state there keeps its positions (frames on the abandoned path are
164
+ // not ancestors and drop out on their own).
165
+ if (entry.type === "compaction") generation = entry.id;
104
166
  }
105
167
  if (parent !== leaf) throw new Error("Context leaf changed");
106
- const entries = branch.filter((e) => e.type === "custom" && e.customType === ENTRY);
168
+ const newEntries = branch.slice(start).filter((e) => e.type === "custom" && e.customType === ENTRY);
107
169
  if (
108
- entries.some((e) => this.acknowledged.get(e.id) !== hash(e)) &&
170
+ newEntries.some((e) => this.acknowledged.get(e.id) !== this.frameHash(e)) &&
109
171
  reader.getSessionFile?.() &&
110
172
  branch.some((e) => e.message?.role === "assistant")
111
173
  ) {
112
- const snapshot = await readOnlySessionSnapshot(reader.getSessionFile());
113
- if (
114
- snapshot.sessionId !== reader.getSessionId() ||
115
- JSON.stringify(activeReadOnlySessionBranch(snapshot, leaf)) !== JSON.stringify(branch)
116
- )
117
- throw new Error("Context history readback differs");
174
+ if (!(await persistedBranchMatches(reader, leaf, branch))) throw new Error("Context history readback differs");
118
175
  }
119
176
  if (revision !== this.revision || reader.getLeafId() !== leaf) throw new Error("Context changed while preparing");
120
- const frames = [],
121
- byId = new Map();
122
- for (const entry of entries) {
123
- const frame = parse(entry.data);
177
+ const frames = extendsCache ? (newEntries.length ? [...cachedBranch.frames] : cachedBranch.frames) : [];
178
+ const byId = extendsCache ? (newEntries.length ? new Map(cachedBranch.byId) : cachedBranch.byId) : new Map();
179
+ for (const entry of newEntries) {
180
+ const cached = this.validatedFrames.get(entry.id);
181
+ const same = cached?.entry === entry;
182
+ const frame = same ? cached.frame : parse(entry.data);
124
183
  if (
125
- (frame.basis !== entry.parentId && !(reader.getHeader?.()?.parentSession && !ids.has(frame.basis))) ||
184
+ (frame.basis !== entry.parentId &&
185
+ !(reader.getHeader?.()?.parentSession && !ids.has(frame.basis) && !newIds.has(frame.basis))) ||
126
186
  byId.has(frame.id)
127
187
  )
128
188
  throw new Error("Context frame anchor differs");
@@ -131,13 +191,26 @@ export class RequestHistory {
131
191
  throw new Error("Context frame parent missing");
132
192
  frames.push(frame);
133
193
  byId.set(frame.id, frame);
134
- this.acknowledged.set(entry.id, hash(entry));
194
+ const entryHash = same ? cached.hash : hash(entry);
195
+ this.acknowledged.set(entry.id, entryHash);
196
+ if (!same) this.validatedFrames.set(entry.id, { entry, hash: entryHash, frame });
135
197
  }
198
+ for (const id of newIds) ids.add(id);
199
+ this.branchCache = {
200
+ first: branch[0],
201
+ last: branch.at(-1),
202
+ length: branch.length,
203
+ leaf,
204
+ generation,
205
+ ids,
206
+ frames,
207
+ byId,
208
+ };
136
209
  const base = messages.filter((m) => !owned(m));
137
210
  const current = messages
138
211
  .filter(owned)
139
212
  .filter((message) => !notice || message.customType !== "freeflow-routing-budget");
140
- const hashes = prefixes(base);
213
+ const hashes = this.prefixes(base);
141
214
  const candidates = frames.filter((f) => f.generation === generation && hashes[f.length] === f.prefix);
142
215
  const prior = candidates.reduce((best, f) => (!best || f.length >= best.length ? f : best), undefined);
143
216
  const states = { ...prior?.states },
@@ -152,6 +225,13 @@ export class RequestHistory {
152
225
  if (kind === "freeflow-routing-communication") seenRetained.add(signature);
153
226
  else states[kind] = signature;
154
227
  }
228
+ const present = new Set(current.map((message) => message.customType));
229
+ let cleared = false;
230
+ for (const kind of SITUATIONAL)
231
+ if (states[kind] && !present.has(kind)) {
232
+ delete states[kind];
233
+ cleared = true;
234
+ }
155
235
  const injections = [];
156
236
  const chain = [];
157
237
  for (let cursor = prior; cursor; cursor = cursor.parent ? byId.get(cursor.parent) : undefined)
@@ -183,7 +263,9 @@ export class RequestHistory {
183
263
  }
184
264
  }
185
265
  }
186
- if (!prior || prior.length !== base.length || additions.length) {
266
+ // A frame that only advances the length adds no injection its parent lacks and matches no
267
+ // branch its parent does not, so frames are written only when generated state changes.
268
+ if (!prior || additions.length || cleared) {
187
269
  const frame = {
188
270
  version: 1,
189
271
  id: randomUUID(),
@@ -199,18 +281,18 @@ export class RequestHistory {
199
281
  };
200
282
  try {
201
283
  this.pi.appendEntry(ENTRY, frame);
202
- const entry = reader.getBranch().find((e) => e.customType === ENTRY && e.data?.id === frame.id);
284
+ const entry = activeBranch(reader).find((e) => e.customType === ENTRY && e.data?.id === frame.id);
203
285
  if (!entry || entry.parentId !== leaf || JSON.stringify(entry.data) !== JSON.stringify(frame))
204
286
  throw new Error("Context append not acknowledged");
205
- this.acknowledged.set(entry.id, hash(entry));
287
+ const entryHash = hash(entry);
288
+ this.acknowledged.set(entry.id, entryHash);
289
+ this.validatedFrames.set(entry.id, { entry, hash: entryHash, frame });
206
290
  } catch (error) {
207
291
  this.fault = true;
208
292
  throw error;
209
293
  }
210
294
  }
211
- try {
212
- ctx.ui?.setStatus?.("freeflow-cache", undefined);
213
- } catch {}
295
+ this.monitor?.set("context-replay", undefined);
214
296
  return result;
215
297
  }
216
298
  }
@@ -0,0 +1,189 @@
1
+ import { hasUsableMandatoryPrompts } from "./prompts.js";
2
+ /**
3
+ * The Freeflow Runtime State message: what is active this request, placed so unchanged state keeps its cached
4
+ * position and changed state never rewrites earlier input. Also drops bootstrap messages that older Freeflow
5
+ * versions saved in sessions.
6
+ */
7
+ const FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE = "freeflow-runtime-state";
8
+ const COGNITIVE_ROUTING_RUNTIME_STATE_MESSAGE_TYPE = "freeflow-cognitive-routing-runtime-state";
9
+ const WORKFLOW_BOOTSTRAP_MESSAGE_TYPE = "freeflow-workflow-bootstrap";
10
+ const COGNITIVE_ROUTING_BOOTSTRAP_MESSAGE_TYPE = "freeflow-cognitive-routing-bootstrap";
11
+ const FREEFLOW_BOOTSTRAP_MESSAGE_TYPE = "freeflow-bootstrap";
12
+ const BOOTSTRAP_COMPONENTS = Object.freeze({
13
+ workflow: "workflow",
14
+ cognitiveRouting: "cognitive-routing",
15
+ });
16
+ function bootstrapEntryComponents(entry) {
17
+ const isCustomMessage = entry?.role === "custom" || entry?.type === "custom_message" || entry?.type === "custom";
18
+ if (!isCustomMessage) return [];
19
+ if (entry.customType === FREEFLOW_BOOTSTRAP_MESSAGE_TYPE) {
20
+ if (Array.isArray(entry.details?.components)) return entry.details.components;
21
+ return [
22
+ ...(entry.content?.includes("<!-- freeflow-bootstrap:workflow -->") ? [BOOTSTRAP_COMPONENTS.workflow] : []),
23
+ ...(entry.content?.includes("<!-- freeflow-bootstrap:cognitive-routing -->")
24
+ ? [BOOTSTRAP_COMPONENTS.cognitiveRouting]
25
+ : []),
26
+ ];
27
+ }
28
+ if (entry.customType === WORKFLOW_BOOTSTRAP_MESSAGE_TYPE) {
29
+ return [BOOTSTRAP_COMPONENTS.workflow];
30
+ }
31
+ if (entry.customType === COGNITIVE_ROUTING_BOOTSTRAP_MESSAGE_TYPE) {
32
+ return [BOOTSTRAP_COMPONENTS.cognitiveRouting];
33
+ }
34
+ return [];
35
+ }
36
+ export function filterBootstrapMessage(message) {
37
+ return bootstrapEntryComponents(message).length === 0 ? message : undefined;
38
+ }
39
+ function publicCognitiveRoutingControl(controlMode) {
40
+ if (controlMode === "automatic") return "automatic";
41
+ if (["manual-helper", "manual-executor", "manual-coordinator"].includes(controlMode)) return "manual";
42
+ return "unavailable";
43
+ }
44
+ function publicCognitiveRoutingProfile(activeProfile, effective) {
45
+ if (effective !== true) return "unavailable";
46
+ return ["helper", "executor", "coordinator"].includes(activeProfile) ? activeProfile : "unavailable";
47
+ }
48
+ function publicCapabilityStatus(capability) {
49
+ if (capability?.effective === true) return "active";
50
+ if (!capability) return "unavailable";
51
+ const blockingCode = capability.blockingReason?.code;
52
+ if (blockingCode && blockingCode !== "disabled") return "unavailable";
53
+ return "inactive";
54
+ }
55
+ function publicCognitiveRoutingStatus(capability, runtime) {
56
+ if (runtime?.runtimeStatus === "inactive") return "inactive";
57
+ return publicCapabilityStatus(capability);
58
+ }
59
+ function publicCognitiveRoutingProjectionMode(capability, runtime, projectionFailure) {
60
+ if (capability?.projection !== true) return "disabled";
61
+ if (capability?.effective !== true) return "unavailable";
62
+ if (String(runtime?.controlMode).startsWith("manual-")) return "manual-bypass";
63
+ if (projectionFailure) return "blocked";
64
+ if (runtime?.runtimeStatus === "inactive" || runtime?.runtimeStatus === "blocked") return "unavailable";
65
+ if (runtime?.effective === true && runtime.controlMode === "automatic") return "enabled";
66
+ return "pending";
67
+ }
68
+ export function freeflowRuntimeStateMessage(
69
+ capabilityState,
70
+ cognitiveRoutingRuntime = undefined,
71
+ freeflowContext = undefined,
72
+ options = {},
73
+ ) {
74
+ const cognitiveRoutingEffective = capabilityState?.cognitiveRouting?.effective === true;
75
+ const profile = publicCognitiveRoutingProfile(
76
+ cognitiveRoutingRuntime?.activeProfile,
77
+ cognitiveRoutingEffective && cognitiveRoutingRuntime?.effective === true,
78
+ );
79
+ const control =
80
+ profile === "unavailable" ? "unavailable" : publicCognitiveRoutingControl(cognitiveRoutingRuntime?.controlMode);
81
+ const projectionMode = publicCognitiveRoutingProjectionMode(
82
+ capabilityState?.cognitiveRouting,
83
+ cognitiveRoutingRuntime,
84
+ options.projectionFailure,
85
+ );
86
+ const mandatoryPromptAvailable = capabilityState?.enabled !== true || hasUsableMandatoryPrompts(freeflowContext);
87
+ const freeflowStatus = capabilityState?.configured
88
+ ? capabilityState.enabled
89
+ ? mandatoryPromptAvailable
90
+ ? "active"
91
+ : "unavailable"
92
+ : "inactive"
93
+ : capabilityState?.configExists
94
+ ? "config error"
95
+ : "setup needed";
96
+ return {
97
+ role: "custom",
98
+ customType: FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE,
99
+ content: [
100
+ "# Freeflow Runtime State",
101
+ "",
102
+ "This is extension-generated runtime state. Use it to interpret the stable Freeflow guidance.",
103
+ "",
104
+ `Freeflow: ${freeflowStatus}`,
105
+ "",
106
+ "Capabilities:",
107
+ `- Cognitive Routing: ${publicCognitiveRoutingStatus(capabilityState?.cognitiveRouting, cognitiveRoutingRuntime)}`,
108
+ `- Tool Execution: ${publicCapabilityStatus(capabilityState?.toolExecution)}`,
109
+ "",
110
+ "Cognitive Routing:",
111
+ `- Control: \`${control}\``,
112
+ // Under Automatic control the routing Runtime State owns the active profile, so switches do not churn this state.
113
+ ...(control === "automatic" ? [] : [`- Profile: \`${profile}\``]),
114
+ `- Delegation: \`${cognitiveRoutingRuntime?.delegation ?? capabilityState?.cognitiveRouting?.delegation ?? "executor"}\``,
115
+ `- Projection: \`${projectionMode}\``,
116
+ ].join("\n"),
117
+ display: false,
118
+ details: { source: "provider-request-runtime-state" },
119
+ };
120
+ }
121
+ function withoutFreeflowRuntimeState(messages) {
122
+ return (Array.isArray(messages) ? messages : []).filter(
123
+ (message) =>
124
+ message?.customType !== FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE &&
125
+ message?.customType !== COGNITIVE_ROUTING_RUNTIME_STATE_MESSAGE_TYPE,
126
+ );
127
+ }
128
+ function firstUserMessageIndex(messages) {
129
+ for (let index = 0; index < messages.length; index += 1) {
130
+ if (messages[index]?.role === "user") return index;
131
+ }
132
+ return -1;
133
+ }
134
+ function insertRuntimeStateBeforeFirstUser(messages, runtimeState) {
135
+ const firstUserIndex = firstUserMessageIndex(messages);
136
+ if (firstUserIndex < 0) return [...messages, runtimeState];
137
+ return [...messages.slice(0, firstUserIndex), runtimeState, ...messages.slice(firstUserIndex)];
138
+ }
139
+ export function withFreeflowRuntimeState(
140
+ messages,
141
+ capabilityState,
142
+ cognitiveRoutingRuntime = undefined,
143
+ freeflowContext = undefined,
144
+ options = {},
145
+ ) {
146
+ const source = Array.isArray(messages) ? messages : [];
147
+ const runtimeState = freeflowRuntimeStateMessage(capabilityState, cognitiveRoutingRuntime, freeflowContext, {
148
+ projectionFailure: options.projectionFailure,
149
+ });
150
+ const runtimeStateMessages = source.filter(
151
+ (message) =>
152
+ message?.customType === FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE ||
153
+ message?.customType === COGNITIVE_ROUTING_RUNTIME_STATE_MESSAGE_TYPE,
154
+ );
155
+ const withoutRuntimeState = withoutFreeflowRuntimeState(source);
156
+ const expectedRuntimeStateIndex = firstUserMessageIndex(withoutRuntimeState);
157
+ const runtimeStateIndex = source.findIndex((message) => message?.customType === FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE);
158
+ const unchanged =
159
+ options.force !== true &&
160
+ runtimeStateMessages.length === 1 &&
161
+ runtimeStateMessages[0]?.customType === FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE &&
162
+ runtimeStateMessages[0]?.content === runtimeState.content &&
163
+ runtimeStateIndex === (expectedRuntimeStateIndex < 0 ? withoutRuntimeState.length : expectedRuntimeStateIndex);
164
+ if (unchanged) return source;
165
+ if (options.anchor) return insertAnchoredRuntimeState(withoutRuntimeState, runtimeState, options.anchor);
166
+ return insertRuntimeStateBeforeFirstUser(withoutRuntimeState, runtimeState);
167
+ }
168
+ // Unchanged state keeps its position so the cached prefix survives; changed state is placed before the
169
+ // latest user message so earlier input is not rewritten, and then stays at that position.
170
+ function insertAnchoredRuntimeState(messages, runtimeState, anchor) {
171
+ let index;
172
+ if (anchor.content === runtimeState.content && anchor.index !== undefined && anchor.index <= messages.length)
173
+ index = anchor.index;
174
+ else if (anchor.content === undefined) {
175
+ const first = firstUserMessageIndex(messages);
176
+ index = first < 0 ? messages.length : first;
177
+ } else {
178
+ let latest = messages.length;
179
+ for (let i = messages.length - 1; i >= 0; i -= 1)
180
+ if (messages[i]?.role === "user") {
181
+ latest = i;
182
+ break;
183
+ }
184
+ index = latest;
185
+ }
186
+ anchor.content = runtimeState.content;
187
+ anchor.index = index;
188
+ return [...messages.slice(0, index), runtimeState, ...messages.slice(index)];
189
+ }