@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,155 +0,0 @@
1
- import type { ConversationSearchEntry } from "./types.js";
2
- import {
3
- normalizeText,
4
- splitTextIntoPassages,
5
- throwIfAborted,
6
- tokenize,
7
- unique,
8
- type TextPassage,
9
- } from "./passages.js";
10
-
11
- export const BM25_K1 = 1.2;
12
- export const BM25_B = 0.75;
13
-
14
- export type RankedPassage = {
15
- entry: ConversationSearchEntry;
16
- passage: TextPassage;
17
- exactPhrase: boolean;
18
- matchedTerms: string[];
19
- score: number;
20
- };
21
-
22
- type RankedTextPassage = Omit<RankedPassage, "entry">;
23
-
24
- type PassageInput = {
25
- entry?: ConversationSearchEntry;
26
- passage: TextPassage;
27
- exactPhrase: boolean;
28
- matchedTerms: string[];
29
- };
30
-
31
- function bm25Score(
32
- tokens: readonly string[],
33
- terms: readonly string[],
34
- documentFrequency: ReadonlyMap<string, number>,
35
- documentCount: number,
36
- averageLength: number,
37
- ): number {
38
- const counts = new Map<string, number>();
39
- for (const token of tokens) counts.set(token, (counts.get(token) ?? 0) + 1);
40
- const length = Math.max(1, tokens.length);
41
- let score = 0;
42
- for (const term of terms) {
43
- const termFrequency = counts.get(term) ?? 0;
44
- if (termFrequency === 0) continue;
45
- const df = documentFrequency.get(term) ?? 0;
46
- const inverseDocumentFrequency = Math.log(1 + (documentCount - df + 0.5) / (df + 0.5));
47
- const normalization = BM25_K1 * (1 - BM25_B + BM25_B * (length / Math.max(1, averageLength)));
48
- score += inverseDocumentFrequency * ((termFrequency * (BM25_K1 + 1)) / (termFrequency + normalization));
49
- }
50
- return score;
51
- }
52
-
53
- function comparePassages(left: RankedPassage, right: RankedPassage): number {
54
- if (left.exactPhrase !== right.exactPhrase) return left.exactPhrase ? -1 : 1;
55
- if (left.score !== right.score) return right.score - left.score;
56
- if (left.matchedTerms.length !== right.matchedTerms.length) {
57
- return right.matchedTerms.length - left.matchedTerms.length;
58
- }
59
- if (left.entry.position !== right.entry.position) return right.entry.position - left.entry.position;
60
- return left.passage.start - right.passage.start;
61
- }
62
-
63
- function compareTextPassages(left: RankedTextPassage, right: RankedTextPassage): number {
64
- if (left.exactPhrase !== right.exactPhrase) return left.exactPhrase ? -1 : 1;
65
- if (left.score !== right.score) return right.score - left.score;
66
- if (left.matchedTerms.length !== right.matchedTerms.length) {
67
- return right.matchedTerms.length - left.matchedTerms.length;
68
- }
69
- return left.passage.start - right.passage.start;
70
- }
71
-
72
- function rankInputs<T extends PassageInput>(
73
- inputs: T[],
74
- terms: string[],
75
- signal?: AbortSignal,
76
- ): Array<T & { score: number }> {
77
- const documentFrequency = new Map<string, number>();
78
- for (const input of inputs) {
79
- throwIfAborted(signal);
80
- for (const term of unique(input.passage.tokens)) {
81
- documentFrequency.set(term, (documentFrequency.get(term) ?? 0) + 1);
82
- }
83
- }
84
- const averageLength =
85
- inputs.length === 0 ? 1 : inputs.reduce((total, input) => total + input.passage.length, 0) / inputs.length;
86
- const matching: T[] = [];
87
- for (const input of inputs) {
88
- throwIfAborted(signal);
89
- if (input.matchedTerms.length > 0 || input.exactPhrase) matching.push(input);
90
- }
91
- return matching
92
- .map((input) => ({
93
- ...input,
94
- score: bm25Score(input.passage.tokens, terms, documentFrequency, inputs.length, averageLength),
95
- }))
96
- .sort((left, right) => {
97
- if (left.entry && right.entry) {
98
- return comparePassages(
99
- { ...left, entry: left.entry } as RankedPassage,
100
- { ...right, entry: right.entry } as RankedPassage,
101
- );
102
- }
103
- return compareTextPassages(left as RankedTextPassage & T, right as RankedTextPassage & T);
104
- });
105
- }
106
-
107
- export function rankMatchingPassages(
108
- entries: readonly ConversationSearchEntry[],
109
- query: string,
110
- signal?: AbortSignal,
111
- ): RankedPassage[] {
112
- throwIfAborted(signal);
113
- const normalizedQuery = normalizeText(query);
114
- const terms = unique(tokenize(query));
115
- if (!normalizedQuery || terms.length === 0) return [];
116
- const inputs: PassageInput[] = [];
117
- for (const entry of entries) {
118
- throwIfAborted(signal);
119
- for (const passage of splitTextIntoPassages(entry.text, signal)) {
120
- const normalizedPassage = normalizeText(passage.text);
121
- inputs.push({
122
- entry,
123
- passage,
124
- exactPhrase: normalizedPassage.includes(normalizedQuery),
125
- matchedTerms: terms.filter((term) => passage.tokens.includes(term)),
126
- });
127
- }
128
- }
129
- return rankInputs(inputs, terms, signal) as RankedPassage[];
130
- }
131
-
132
- export function strongestTextPassage(text: string, query: string): RankedTextPassage | undefined {
133
- const normalizedQuery = normalizeText(query);
134
- const terms = unique(tokenize(query));
135
- if (!normalizedQuery || terms.length === 0) return undefined;
136
- const inputs: PassageInput[] = splitTextIntoPassages(text).map((passage) => ({
137
- passage,
138
- exactPhrase: normalizeText(passage.text).includes(normalizedQuery),
139
- matchedTerms: terms.filter((term) => passage.tokens.includes(term)),
140
- }));
141
- return rankInputs(inputs, terms)[0] as RankedTextPassage | undefined;
142
- }
143
-
144
- export function focusedWindow(text: string, focus: string, maxCharacters: number): string | undefined {
145
- const strongest = strongestTextPassage(text, focus);
146
- if (!strongest) return undefined;
147
- const center = Math.floor((strongest.passage.start + strongest.passage.end) / 2);
148
- const proposedStart = Math.max(0, Math.min(center - Math.floor(maxCharacters / 2), text.length - maxCharacters));
149
- const previousLineBreak = proposedStart > 0 ? text.lastIndexOf("\n", proposedStart - 1) : -1;
150
- const start = previousLineBreak >= 0 ? previousLineBreak + 1 : proposedStart;
151
- const proposedEnd = Math.min(text.length, start + maxCharacters);
152
- const previousEndLineBreak = proposedEnd < text.length ? text.lastIndexOf("\n", proposedEnd) : -1;
153
- const end = previousEndLineBreak > start ? previousEndLineBreak + 1 : proposedEnd;
154
- return text.slice(start, end);
155
- }
@@ -1,250 +0,0 @@
1
- import { FreeflowContextRuntime } from "../freeflow-context/runtime.js";
2
- import { contextRefForEntry, entryIdFromContextRef } from "../freeflow-context/types.js";
3
- import type { FreeflowContextSnapshot, ProjectedContextSource } from "../freeflow-context/types.js";
4
- import { focusedWindow } from "./ranking.js";
5
- import { searchConversationEntries, type ConversationSearchOptions } from "./search.js";
6
- import { tokenize, unique } from "./passages.js";
7
- import type { ConversationSearchEntry } from "./types.js";
8
-
9
- const MAX_QUERY_CHARACTERS = 500;
10
- const MAX_QUERY_TERMS = 32;
11
- const MAX_TOOL_NAMES = 8;
12
- const MAX_TOOL_NAME_CHARACTERS = 128;
13
- const MAX_REF_CHARACTERS = 256;
14
- const MAX_FOCUS_CHARACTERS = 500;
15
- const MAX_RETRIEVE_REFS = 3;
16
- const MAX_SOURCE_CHARACTERS = 8_000;
17
- const MAX_TOTAL_RETRIEVED_CHARACTERS = 24_000;
18
- const VALID_KINDS = new Set(["user", "assistant", "toolResult", "summary"]);
19
-
20
- function normalizedTerms(value: string): string[] {
21
- return unique(tokenize(value));
22
- }
23
-
24
- function failure(operation: "search" | "retrieve", reason: string, message?: string) {
25
- return {
26
- status: "rejected" as const,
27
- operation,
28
- reason: reason.slice(0, 64),
29
- ...(message ? { message: message.slice(0, 500) } : {}),
30
- };
31
- }
32
-
33
- function unavailable(operation: "search" | "retrieve", reason = "conversation_history_unavailable") {
34
- return { status: "unavailable" as const, operation, reason: reason.slice(0, 64) };
35
- }
36
-
37
- function sourceMetadata(source: ProjectedContextSource) {
38
- return {
39
- ref: source.ref,
40
- kind: source.kind,
41
- ...(source.toolNames
42
- ? {
43
- toolNames: source.toolNames.slice(0, MAX_TOOL_NAMES),
44
- ...(source.toolNames.length > MAX_TOOL_NAMES ? { toolNamesTruncated: true } : {}),
45
- }
46
- : {}),
47
- ...(source.isError === undefined ? {} : { isError: source.isError }),
48
- timestamp: source.timestamp.slice(0, 64),
49
- };
50
- }
51
-
52
- export class ConversationHistoryRuntime {
53
- private ctx: any;
54
- private readonly freeflowContext: FreeflowContextRuntime;
55
- private readonly isSourceFullyProjected: (entryId: string) => boolean;
56
- private snapshot: FreeflowContextSnapshot | undefined;
57
- private available = true;
58
- private unavailableReason: string | undefined;
59
-
60
- constructor(
61
- ctx: any,
62
- isSourceFullyProjected: (entryId: string) => boolean = () => true,
63
- freeflowContext = new FreeflowContextRuntime(ctx),
64
- ) {
65
- this.ctx = ctx;
66
- this.isSourceFullyProjected = isSourceFullyProjected;
67
- this.freeflowContext = freeflowContext;
68
- }
69
-
70
- setContext(ctx: any): void {
71
- this.ctx = ctx;
72
- this.freeflowContext.setContext(ctx);
73
- }
74
-
75
- capture(contextVirtualizationEnabled = false): boolean {
76
- try {
77
- this.freeflowContext.setContext(this.ctx);
78
- this.snapshot = this.freeflowContext.captureSnapshot({
79
- contextVirtualizationEnabled,
80
- isSourceFullyProjected: this.isSourceFullyProjected,
81
- });
82
- this.available = true;
83
- this.unavailableReason = undefined;
84
- return true;
85
- } catch (error) {
86
- this.snapshot = undefined;
87
- this.available = false;
88
- this.unavailableReason = error instanceof Error ? error.message : String(error);
89
- return false;
90
- }
91
- }
92
-
93
- async search(params: Record<string, unknown>, signal?: AbortSignal): Promise<any> {
94
- if (signal?.aborted) return unavailable("search", "conversation_history_search_cancelled");
95
- if (!this.available || !this.snapshot) return unavailable("search", this.unavailableReason);
96
- const validation = this.validateSearch(params);
97
- if (validation) return validation;
98
-
99
- const query = (params.query as string).trim();
100
- const hidden = [...this.snapshot.entries.values()].filter(
101
- (entry) =>
102
- !this.snapshot?.visibleSourceIds.has(entry.source.source.entryId) &&
103
- !this.snapshot?.materializedSourceIds.has(entry.source.source.entryId),
104
- );
105
- if (hidden.length === 0) {
106
- if (this.snapshot.skippedEntries === 0) return failure("search", "no_hidden_conversation_history");
107
- return {
108
- status: "ok",
109
- operation: "search",
110
- query,
111
- coverage: "partial",
112
- skippedEntries: this.snapshot.skippedEntries,
113
- returned: 0,
114
- truncated: false,
115
- hits: [],
116
- };
117
- }
118
-
119
- let result;
120
- try {
121
- result = searchConversationEntries(
122
- hidden as ConversationSearchEntry[],
123
- {
124
- query,
125
- kinds: params.kinds as ConversationSearchOptions["kinds"],
126
- toolNames: Array.isArray(params.toolNames)
127
- ? params.toolNames.map((name) => (typeof name === "string" ? name.trim() : ""))
128
- : undefined,
129
- limit: params.limit as number | undefined,
130
- },
131
- signal,
132
- );
133
- } catch (error) {
134
- if (signal?.aborted || (error instanceof Error && error.name === "AbortError")) {
135
- return unavailable("search", "conversation_history_search_cancelled");
136
- }
137
- return unavailable("search");
138
- }
139
- return {
140
- status: "ok",
141
- operation: "search",
142
- query,
143
- coverage: this.snapshot.skippedEntries > 0 ? "partial" : "complete",
144
- ...(this.snapshot.skippedEntries > 0 ? { skippedEntries: this.snapshot.skippedEntries } : {}),
145
- returned: result.returned,
146
- truncated: result.truncated,
147
- hits: result.hits,
148
- };
149
- }
150
-
151
- async retrieve(params: Record<string, unknown>, signal?: AbortSignal): Promise<any> {
152
- if (signal?.aborted) return unavailable("retrieve", "conversation_history_retrieve_cancelled");
153
- if (!this.available || !this.snapshot) return unavailable("retrieve", this.unavailableReason);
154
- const refs = params.refs;
155
- if (!Array.isArray(refs) || refs.length < 1 || refs.length > MAX_RETRIEVE_REFS) {
156
- return failure("retrieve", "refs_count_invalid");
157
- }
158
- if (refs.some((ref) => typeof ref !== "string" || ref.length > MAX_REF_CHARACTERS)) {
159
- return failure("retrieve", "ref_invalid");
160
- }
161
- const canonicalRefs = refs.map((ref) => {
162
- const entryId = entryIdFromContextRef(ref);
163
- return entryId ? contextRefForEntry(entryId) : undefined;
164
- });
165
- if (canonicalRefs.some((ref) => ref === undefined)) return failure("retrieve", "ref_invalid");
166
- const uniqueRefs = canonicalRefs as string[];
167
- if (new Set(uniqueRefs).size !== uniqueRefs.length) return failure("retrieve", "duplicate_ref");
168
- const focus = params.focus;
169
- if (
170
- focus !== undefined &&
171
- (typeof focus !== "string" || focus.trim().length === 0 || focus.length > MAX_FOCUS_CHARACTERS)
172
- ) {
173
- return failure("retrieve", "focus_invalid");
174
- }
175
-
176
- const items = [];
177
- let total = 0;
178
- for (const ref of uniqueRefs) {
179
- if (signal?.aborted) return unavailable("retrieve", "conversation_history_retrieve_cancelled");
180
- const entryId = entryIdFromContextRef(ref);
181
- const source = entryId ? this.snapshot.entries.get(contextRefForEntry(entryId)) : undefined;
182
- if (!source) return failure("retrieve", "reference_unresolved");
183
- if (this.snapshot.visibleSourceIds.has(entryId) || this.snapshot.materializedSourceIds.has(entryId)) {
184
- return failure("retrieve", "source_visible");
185
- }
186
- const sourceCharacters = source.text.length;
187
- let content = source.text;
188
- let completeness: "complete" | "partial" = "complete";
189
- if (sourceCharacters > MAX_SOURCE_CHARACTERS) {
190
- if (typeof focus !== "string") return failure("retrieve", "focus_required_for_oversized_source");
191
- const focused = focusedWindow(source.text, focus.trim(), MAX_SOURCE_CHARACTERS);
192
- if (focused === undefined) return failure("retrieve", "focus_no_match");
193
- content = focused;
194
- completeness = "partial";
195
- }
196
- if (total + content.length > MAX_TOTAL_RETRIEVED_CHARACTERS)
197
- return failure("retrieve", "retrieval_budget_exceeded");
198
- total += content.length;
199
- items.push({
200
- ...sourceMetadata(source),
201
- content,
202
- completeness,
203
- sourceCharacters,
204
- returnedCharacters: content.length,
205
- });
206
- }
207
- return {
208
- status: "ok",
209
- operation: "retrieve",
210
- ...(typeof focus === "string" ? { focus: focus.trim() } : {}),
211
- items,
212
- };
213
- }
214
-
215
- private validateSearch(params: Record<string, unknown>): any | undefined {
216
- if (typeof params.query !== "string" || params.query.trim().length === 0)
217
- return failure("search", "query_required");
218
- if (params.query.length > MAX_QUERY_CHARACTERS) return failure("search", "query_too_long");
219
- if (normalizedTerms(params.query).length > MAX_QUERY_TERMS) return failure("search", "query_terms_too_many");
220
- if (params.kinds !== undefined) {
221
- if (!Array.isArray(params.kinds) || params.kinds.length < 1 || params.kinds.length > 4)
222
- return failure("search", "kinds_invalid");
223
- if (
224
- new Set(params.kinds).size !== params.kinds.length ||
225
- params.kinds.some((kind) => typeof kind !== "string" || !VALID_KINDS.has(kind))
226
- ) {
227
- return failure("search", "kinds_invalid");
228
- }
229
- }
230
- if (params.toolNames !== undefined) {
231
- if (!Array.isArray(params.toolNames) || params.toolNames.length < 1 || params.toolNames.length > MAX_TOOL_NAMES)
232
- return failure("search", "tool_names_invalid");
233
- const normalizedNames = params.toolNames.map((name) => (typeof name === "string" ? name.trim() : ""));
234
- if (
235
- params.toolNames.some((name) => typeof name !== "string") ||
236
- new Set(normalizedNames.map((name) => name.toLocaleLowerCase())).size !== normalizedNames.length ||
237
- normalizedNames.some((name) => name.length === 0 || name.length > MAX_TOOL_NAME_CHARACTERS)
238
- ) {
239
- return failure("search", "tool_names_invalid");
240
- }
241
- }
242
- if (
243
- params.limit !== undefined &&
244
- (!Number.isInteger(params.limit) || (params.limit as number) < 1 || (params.limit as number) > 20)
245
- ) {
246
- return failure("search", "limit_invalid");
247
- }
248
- return undefined;
249
- }
250
- }
@@ -1,79 +0,0 @@
1
- import { normalizeText, snippet, throwIfAborted, tokenize, unique } from "./passages.js";
2
- import { rankMatchingPassages } from "./ranking.js";
3
- import type {
4
- ConversationSearchEntry,
5
- ConversationSearchHit,
6
- ConversationSearchOptions,
7
- ConversationSearchResult,
8
- } from "./types.js";
9
-
10
- export type {
11
- ConversationSearchEntry,
12
- ConversationSearchHit,
13
- ConversationSearchOptions,
14
- ConversationSearchResult,
15
- } from "./types.js";
16
-
17
- const MAX_DISPLAYED_TOOL_NAMES = 8;
18
-
19
- function matchesFilters(entry: ConversationSearchEntry, options: ConversationSearchOptions): boolean {
20
- if (options.kinds && !options.kinds.includes(entry.kind)) return false;
21
- if (options.toolNames) {
22
- const names = new Set((entry.toolNames ?? []).map((name) => name.toLocaleLowerCase()));
23
- if (!options.toolNames.some((name) => names.has(name.toLocaleLowerCase()))) return false;
24
- }
25
- return true;
26
- }
27
-
28
- export function searchConversationEntries(
29
- entries: readonly ConversationSearchEntry[],
30
- options: ConversationSearchOptions,
31
- signal?: AbortSignal,
32
- ): ConversationSearchResult {
33
- throwIfAborted(signal);
34
- const normalizedQuery = normalizeText(options.query);
35
- const terms = unique(tokenize(options.query));
36
- const limit = Math.min(20, Math.max(1, Math.trunc(options.limit ?? 8)));
37
- if (!normalizedQuery || terms.length === 0) return { returned: 0, truncated: false, hits: [] };
38
-
39
- const candidates = entries.filter((entry) => matchesFilters(entry, options));
40
- const rankedPassages = rankMatchingPassages(candidates, options.query, signal);
41
- const byEntry = new Map<string, (typeof rankedPassages)[number]>();
42
- for (const passage of rankedPassages) {
43
- throwIfAborted(signal);
44
- if (!byEntry.has(passage.entry.ref)) byEntry.set(passage.entry.ref, passage);
45
- }
46
-
47
- const ranked = [...byEntry.values()];
48
- const hits = ranked.slice(0, limit).map((passage) => {
49
- const matchType = passage.exactPhrase
50
- ? "exact-phrase"
51
- : passage.matchedTerms.length === terms.length
52
- ? "all-terms"
53
- : "partial-terms";
54
- return {
55
- ref: passage.entry.ref,
56
- kind: passage.entry.kind,
57
- ...(passage.entry.toolNames
58
- ? {
59
- toolNames: passage.entry.toolNames.slice(0, MAX_DISPLAYED_TOOL_NAMES),
60
- ...(passage.entry.toolNames.length > MAX_DISPLAYED_TOOL_NAMES ? { toolNamesTruncated: true } : {}),
61
- }
62
- : {}),
63
- ...(passage.entry.isError === undefined ? {} : { isError: passage.entry.isError }),
64
- timestamp: passage.entry.timestamp.slice(0, 64),
65
- snippet: snippet(passage.passage.text, normalizedQuery, terms),
66
- match: {
67
- type: matchType,
68
- matchedTerms: passage.matchedTerms,
69
- queryTermCount: terms.length,
70
- },
71
- } satisfies ConversationSearchHit;
72
- });
73
-
74
- return {
75
- returned: hits.length,
76
- truncated: ranked.length > limit,
77
- hits,
78
- };
79
- }
@@ -1,31 +0,0 @@
1
- import type { ProjectedContextSource } from "../freeflow-context/types.js";
2
-
3
- export type ConversationSearchEntry = ProjectedContextSource;
4
-
5
- export type ConversationSearchOptions = {
6
- query: string;
7
- kinds?: readonly ConversationSearchEntry["kind"][];
8
- toolNames?: readonly string[];
9
- limit?: number;
10
- };
11
-
12
- export type ConversationSearchHit = {
13
- ref: string;
14
- kind: ConversationSearchEntry["kind"];
15
- toolNames?: string[];
16
- toolNamesTruncated?: boolean;
17
- isError?: boolean;
18
- timestamp: string;
19
- snippet: string;
20
- match: {
21
- type: "exact-phrase" | "all-terms" | "partial-terms";
22
- matchedTerms: string[];
23
- queryTermCount: number;
24
- };
25
- };
26
-
27
- export type ConversationSearchResult = {
28
- returned: number;
29
- truncated: boolean;
30
- hits: ConversationSearchHit[];
31
- };
@@ -1,139 +0,0 @@
1
- import { entryIdFromContextRef } from "./types.js";
2
- import type { ContextSourceKind, ResolvedContextEntry, ResolvedContextSource } from "./types.js";
3
-
4
- export function isToolResultEntry(entry: any): boolean {
5
- return entry?.type === "message" && entry?.message?.role === "toolResult";
6
- }
7
-
8
- export class ContextSourceResolver {
9
- private ctx: any;
10
-
11
- constructor(ctx: any) {
12
- this.ctx = ctx;
13
- }
14
-
15
- setContext(ctx: any): void {
16
- this.ctx = ctx;
17
- }
18
-
19
- sessionId(): string {
20
- const value = this.ctx?.sessionManager?.getSessionId?.() ?? this.ctx?.sessionId ?? this.ctx?.cwd ?? "current";
21
- return String(value);
22
- }
23
-
24
- branchLeafId(): string | null | undefined {
25
- try {
26
- return this.ctx?.sessionManager?.getLeafId?.();
27
- } catch {
28
- return undefined;
29
- }
30
- }
31
-
32
- branchEntries(): any[] {
33
- const entries = this.ctx?.sessionManager?.getBranch?.() ?? this.ctx?.sessionManager?.getEntries?.() ?? [];
34
- return Array.isArray(entries) ? entries : [];
35
- }
36
-
37
- activeContextEntries(): any[] {
38
- try {
39
- const entries = this.ctx?.sessionManager?.buildContextEntries?.() ?? this.branchEntries();
40
- return Array.isArray(entries) ? entries : [];
41
- } catch {
42
- return this.branchEntries();
43
- }
44
- }
45
-
46
- activeContextEntriesStrict(): any[] {
47
- const manager = this.ctx?.sessionManager;
48
- if (typeof manager?.buildContextEntries !== "function") return this.branchEntries();
49
- const entries = manager.buildContextEntries();
50
- if (!Array.isArray(entries)) throw new Error("active_context_entries_unavailable");
51
- return entries;
52
- }
53
-
54
- resolvedEntriesById(entries = this.branchEntries(), sessionId = this.sessionId()): Map<string, ResolvedContextEntry> {
55
- const result = new Map<string, ResolvedContextEntry>();
56
- for (const entry of entries) {
57
- const resolved = this.entryFromSessionEntry(entry, sessionId);
58
- if (resolved) result.set(resolved.source.entryId, resolved);
59
- }
60
- return result;
61
- }
62
-
63
- toolResultSources(entries = this.branchEntries(), sessionId = this.sessionId()): Map<string, ResolvedContextSource> {
64
- const grouped = new Map<string, ResolvedContextSource[]>();
65
- for (const entry of entries) {
66
- const source = this.sourceFromEntry(entry, sessionId);
67
- if (!source) continue;
68
- const group = grouped.get(source.source.toolCallId!) ?? [];
69
- group.push(source);
70
- grouped.set(source.source.toolCallId!, group);
71
- }
72
-
73
- const unique = new Map<string, ResolvedContextSource>();
74
- for (const [toolCallId, sources] of grouped) {
75
- if (sources.length === 1) unique.set(toolCallId, sources[0]);
76
- }
77
- return unique;
78
- }
79
-
80
- sourcesByEntryId(entries = this.branchEntries(), sessionId = this.sessionId()): Map<string, ResolvedContextSource> {
81
- const result = new Map<string, ResolvedContextSource>();
82
- for (const source of this.toolResultSources(entries, sessionId).values()) {
83
- result.set(source.source.entryId, source);
84
- }
85
- return result;
86
- }
87
-
88
- activeToolResultIds(): Set<string> {
89
- const ids = new Set<string>();
90
- for (const entry of this.activeContextEntries()) {
91
- if (isToolResultEntry(entry) && typeof entry.id === "string") ids.add(entry.id);
92
- }
93
- return ids;
94
- }
95
-
96
- resolveCurrent(ref: unknown): ResolvedContextEntry | undefined {
97
- const entryId = entryIdFromContextRef(ref);
98
- if (!entryId) return undefined;
99
- const entry = this.branchEntries().find((candidate) => candidate?.id === entryId);
100
- return entry ? this.entryFromSessionEntry(entry, this.sessionId()) : undefined;
101
- }
102
-
103
- private sourceFromEntry(entry: any, sessionId: string): ResolvedContextSource | undefined {
104
- const resolved = this.entryFromSessionEntry(entry, sessionId);
105
- return resolved?.kind === "toolResult" ? (resolved as ResolvedContextSource) : undefined;
106
- }
107
-
108
- private entryFromSessionEntry(entry: any, sessionId: string): ResolvedContextEntry | undefined {
109
- if (!entry || typeof entry.id !== "string") return undefined;
110
- const kind = this.kindForEntry(entry);
111
- if (!kind) return undefined;
112
- const message = entry.message;
113
- const toolCallId =
114
- kind === "toolResult" && typeof message?.toolCallId === "string" ? message.toolCallId : undefined;
115
- return {
116
- kind,
117
- entry,
118
- ...(message ? { message } : {}),
119
- source: {
120
- sessionId,
121
- entryId: entry.id,
122
- ...(toolCallId ? { toolCallId } : {}),
123
- ...(typeof message?.toolName === "string" ? { toolName: message.toolName } : {}),
124
- },
125
- };
126
- }
127
-
128
- private kindForEntry(entry: any): ContextSourceKind | undefined {
129
- if (entry.type === "custom") return "custom";
130
- if (entry.type === "compaction" || entry.type === "branch_summary") return "summary";
131
- if (entry.type !== "message") return undefined;
132
- if (entry.message?.role === "user") return "user";
133
- if (entry.message?.role === "assistant") return "assistant";
134
- if (entry.message?.role === "toolResult") return "toolResult";
135
- if (entry.message?.role === "custom") return "custom";
136
- if (entry.message?.role === "branchSummary" || entry.message?.role === "compactionSummary") return "summary";
137
- return undefined;
138
- }
139
- }