pi-mega-compact 0.13.1 → 0.13.2

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 (207) hide show
  1. package/dist/extensions/dashboard-server/api-contracts/embedder-health.js +11 -0
  2. package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +225 -0
  3. package/dist/extensions/dashboard-server/api-contracts/endpoints/types.js +1 -0
  4. package/dist/extensions/dashboard-server/api-contracts/endpoints.js +5 -224
  5. package/dist/extensions/dashboard-server/api-contracts/raptor.js +8 -0
  6. package/dist/extensions/dashboard-server/api-contracts.test/_helpers.js +32 -0
  7. package/dist/extensions/dashboard-server/routes-cache.test/_helpers.js +48 -0
  8. package/dist/extensions/dashboard-server/routes-embedder-health.js +67 -0
  9. package/dist/extensions/dashboard-server/routes-raptor.js +88 -0
  10. package/dist/extensions/dashboard-server/routes-setup.js +1 -1
  11. package/dist/extensions/dashboard-server/routes.js +2 -0
  12. package/dist/extensions/dashboard-server/server.js +5 -1
  13. package/dist/extensions/mega-cache-replay.test/_helpers.js +175 -0
  14. package/dist/extensions/mega-commands/dataCommands.js +151 -0
  15. package/dist/extensions/mega-commands/helpers.js +61 -0
  16. package/dist/extensions/mega-commands/historyCommands.js +97 -0
  17. package/dist/extensions/mega-commands/setupCommand.js +200 -0
  18. package/dist/extensions/mega-commands.js +14 -499
  19. package/dist/extensions/mega-compact-s38.test/_helpers.js +194 -0
  20. package/dist/extensions/mega-compact.test/_helpers.js +225 -0
  21. package/dist/extensions/mega-events/agent-handlers/agentEndHandler.js +211 -0
  22. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/cacheStripe.js +49 -0
  23. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/contextHealth.js +33 -0
  24. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/errorRetry.js +387 -0
  25. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/event.js +7 -0
  26. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/gameScoring.js +55 -0
  27. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/lengthStop.js +12 -0
  28. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/memoryReview.js +22 -0
  29. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.js +38 -0
  30. package/dist/extensions/mega-events/agent-handlers/turnEndHandler.js +36 -0
  31. package/dist/extensions/mega-events/agent-handlers.js +4 -799
  32. package/dist/extensions/mega-events/context-handler/afterCompact.js +163 -0
  33. package/dist/extensions/mega-events/context-handler/messageText.js +12 -0
  34. package/dist/extensions/mega-events/context-handler/tailResult.js +46 -0
  35. package/dist/extensions/mega-events/context-handler.js +11 -198
  36. package/dist/extensions/mega-events/separated-prompt.test/_helpers.js +25 -0
  37. package/dist/src/config.js +2 -0
  38. package/dist/src/dedup-engine.test/_helpers.js +55 -0
  39. package/dist/src/e2e.test/_helpers.js +52 -0
  40. package/dist/src/embedder.js +1 -0
  41. package/dist/src/httpEmbedder.js +6 -0
  42. package/dist/src/hyde.js +146 -0
  43. package/dist/src/importance.test/_helpers.js +5 -0
  44. package/dist/src/memoryGraph/sources/checkpoints.js +78 -0
  45. package/dist/src/memoryGraph/sources/edges.js +17 -0
  46. package/dist/src/memoryGraph/sources/flags.js +22 -0
  47. package/dist/src/memoryGraph/sources/helpers.js +14 -0
  48. package/dist/src/memoryGraph/sources/memories.js +88 -0
  49. package/dist/src/memoryGraph/sources/raptor.js +26 -0
  50. package/dist/src/memoryGraph/sources/turns.js +176 -0
  51. package/dist/src/memoryGraph/sources.js +15 -441
  52. package/dist/src/memoryGraph-gates.test/_helpers.js +120 -0
  53. package/dist/src/ratio.bench.test/_helpers.js +198 -0
  54. package/dist/src/recall/async.js +112 -0
  55. package/dist/src/recall/format.js +149 -0
  56. package/dist/src/recall/memory.js +62 -0
  57. package/dist/src/recall/reformulate.js +82 -0
  58. package/dist/src/recall/sync.js +169 -0
  59. package/dist/src/recall/types.js +1 -0
  60. package/dist/src/recall.js +13 -530
  61. package/dist/src/store/sqlite/schema/core.js +232 -0
  62. package/dist/src/store/sqlite/schema/game.js +65 -0
  63. package/dist/src/store/sqlite/schema/plan-v2.js +107 -0
  64. package/dist/src/store/sqlite/schema/turns.js +59 -0
  65. package/dist/src/store/sqlite/schema.js +8 -441
  66. package/dist/src/store/turns/contract-compliance.test/_helpers.js +21 -0
  67. package/dist/src/store/turns/sqlite-store/admin.js +171 -0
  68. package/dist/src/store/turns/sqlite-store/ctx.js +1 -0
  69. package/dist/src/store/turns/sqlite-store/read.js +111 -0
  70. package/dist/src/store/turns/sqlite-store/rows.js +42 -0
  71. package/dist/src/store/turns/sqlite-store/write.js +102 -0
  72. package/dist/src/store/turns/sqlite-store.js +31 -367
  73. package/dist/src/vectorStore/class.js +356 -0
  74. package/dist/src/vectorStore/hash.js +10 -0
  75. package/dist/src/vectorStore/types.js +2 -0
  76. package/dist/src/vectorStore.js +8 -359
  77. package/dist/src/vectorStore.test/_helpers.js +17 -0
  78. package/extensions/dashboard-client/dist/assets/{CacheTab-8HRroK9K.js → CacheTab-DPRHjYZ0.js} +2 -2
  79. package/extensions/dashboard-client/dist/assets/{CacheTab-8HRroK9K.js.map → CacheTab-DPRHjYZ0.js.map} +1 -1
  80. package/extensions/dashboard-client/dist/assets/{EventsTab-ncobZw-I.js → EventsTab-CgwYq-Qf.js} +2 -2
  81. package/extensions/dashboard-client/dist/assets/{EventsTab-ncobZw-I.js.map → EventsTab-CgwYq-Qf.js.map} +1 -1
  82. package/extensions/dashboard-client/dist/assets/{HealthTab-C6BI1EZG.js → HealthTab-BgOCjVzR.js} +2 -2
  83. package/extensions/dashboard-client/dist/assets/{HealthTab-C6BI1EZG.js.map → HealthTab-BgOCjVzR.js.map} +1 -1
  84. package/extensions/dashboard-client/dist/assets/MaintenanceTab-Yw1yFpsy.js +2 -0
  85. package/extensions/dashboard-client/dist/assets/MaintenanceTab-Yw1yFpsy.js.map +1 -0
  86. package/extensions/dashboard-client/dist/assets/MemoryMapTab-yHUX7Yzi.js +2 -0
  87. package/extensions/dashboard-client/dist/assets/MemoryMapTab-yHUX7Yzi.js.map +1 -0
  88. package/extensions/dashboard-client/dist/assets/{MetricsTab-B88XTCMk.js → MetricsTab-CcH9fP8P.js} +2 -2
  89. package/extensions/dashboard-client/dist/assets/{MetricsTab-B88XTCMk.js.map → MetricsTab-CcH9fP8P.js.map} +1 -1
  90. package/extensions/dashboard-client/dist/assets/{OverviewTab-CGZrSia-.js → OverviewTab-BOs8ZNev.js} +2 -2
  91. package/extensions/dashboard-client/dist/assets/{OverviewTab-CGZrSia-.js.map → OverviewTab-BOs8ZNev.js.map} +1 -1
  92. package/extensions/dashboard-client/dist/assets/{ReposTab-BZo5cNS1.js → ReposTab-B-MfSPvg.js} +2 -2
  93. package/extensions/dashboard-client/dist/assets/{ReposTab-BZo5cNS1.js.map → ReposTab-B-MfSPvg.js.map} +1 -1
  94. package/extensions/dashboard-client/dist/assets/{SessionsTab-C2RokJcw.js → SessionsTab-BmeN649Q.js} +2 -2
  95. package/extensions/dashboard-client/dist/assets/{SessionsTab-C2RokJcw.js.map → SessionsTab-BmeN649Q.js.map} +1 -1
  96. package/extensions/dashboard-client/dist/assets/SetupTab-nOknoph5.js +2 -0
  97. package/extensions/dashboard-client/dist/assets/SetupTab-nOknoph5.js.map +1 -0
  98. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CewYB2aA.js → TimeSavedCard-DnPg0L0z.js} +2 -2
  99. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CewYB2aA.js.map → TimeSavedCard-DnPg0L0z.js.map} +1 -1
  100. package/extensions/dashboard-client/dist/assets/{TopicsTab-DpVNIuaL.js → TopicsTab-BODHOg_b.js} +2 -2
  101. package/extensions/dashboard-client/dist/assets/{TopicsTab-DpVNIuaL.js.map → TopicsTab-BODHOg_b.js.map} +1 -1
  102. package/extensions/dashboard-client/dist/assets/{TurnsTab-KeuKKVC9.js → TurnsTab-BK0I7hKr.js} +2 -2
  103. package/extensions/dashboard-client/dist/assets/{TurnsTab-KeuKKVC9.js.map → TurnsTab-BK0I7hKr.js.map} +1 -1
  104. package/extensions/dashboard-client/dist/assets/{index-Cucgohga.js → index-mOlljTFI.js} +9 -9
  105. package/extensions/dashboard-client/dist/assets/index-mOlljTFI.js.map +1 -0
  106. package/extensions/dashboard-client/dist/assets/{useSSE-DJsqdPXC.js → useSSE-BYOtv1-3.js} +2 -2
  107. package/extensions/dashboard-client/dist/assets/{useSSE-DJsqdPXC.js.map → useSSE-BYOtv1-3.js.map} +1 -1
  108. package/extensions/dashboard-client/dist/index.html +1 -1
  109. package/extensions/dashboard-client/src/api/client.ts +16 -0
  110. package/extensions/dashboard-client/src/tabs/MaintenanceTab/ActionsCard.tsx +178 -0
  111. package/extensions/dashboard-client/src/tabs/MaintenanceTab/DbStatsCard.tsx +140 -0
  112. package/extensions/dashboard-client/src/tabs/MaintenanceTab/DebugBundleCard.tsx +109 -0
  113. package/extensions/dashboard-client/src/tabs/MaintenanceTab/HealthMitigationCard.tsx +98 -0
  114. package/extensions/dashboard-client/src/tabs/MaintenanceTab/SchemaHealthCard.tsx +108 -0
  115. package/extensions/dashboard-client/src/tabs/MaintenanceTab.tsx +12 -598
  116. package/extensions/dashboard-client/src/tabs/MemoryMapTab/MemoryMapView.tsx +476 -0
  117. package/extensions/dashboard-client/src/tabs/MemoryMapTab/RaptorTreeView.tsx +163 -0
  118. package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +56 -466
  119. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderHealthCard.tsx +109 -0
  120. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderSetup.tsx +468 -0
  121. package/extensions/dashboard-client/src/tabs/SetupTab.tsx +13 -483
  122. package/extensions/dashboard-server/api-contracts/embedder-health.ts +32 -0
  123. package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +360 -0
  124. package/extensions/dashboard-server/api-contracts/endpoints/types.ts +350 -0
  125. package/extensions/dashboard-server/api-contracts/endpoints.ts +27 -648
  126. package/extensions/dashboard-server/api-contracts/index.ts +6 -0
  127. package/extensions/dashboard-server/api-contracts/raptor.ts +49 -0
  128. package/extensions/dashboard-server/api-contracts.test/_helpers.ts +47 -0
  129. package/extensions/dashboard-server/routes-cache.test/_helpers.ts +94 -0
  130. package/extensions/dashboard-server/routes-embedder-health.ts +77 -0
  131. package/extensions/dashboard-server/routes-raptor.ts +128 -0
  132. package/extensions/dashboard-server/routes-setup.ts +1 -1
  133. package/extensions/dashboard-server/routes.ts +2 -0
  134. package/extensions/dashboard-server/server.ts +4 -0
  135. package/extensions/mega-cache-replay.test/_helpers.ts +188 -0
  136. package/extensions/mega-commands/dataCommands.ts +169 -0
  137. package/extensions/mega-commands/helpers.ts +62 -0
  138. package/extensions/mega-commands/historyCommands.ts +117 -0
  139. package/extensions/mega-commands/setupCommand.ts +235 -0
  140. package/extensions/mega-commands.ts +25 -538
  141. package/extensions/mega-compact-s38.test/_helpers.ts +201 -0
  142. package/extensions/mega-compact.test/_helpers.ts +237 -0
  143. package/extensions/mega-events/agent-handlers/agentEndHandler.ts +238 -0
  144. package/extensions/mega-events/agent-handlers/turnEndHandler/cacheStripe.ts +74 -0
  145. package/extensions/mega-events/agent-handlers/turnEndHandler/contextHealth.ts +50 -0
  146. package/extensions/mega-events/agent-handlers/turnEndHandler/errorRetry.ts +421 -0
  147. package/extensions/mega-events/agent-handlers/turnEndHandler/event.ts +16 -0
  148. package/extensions/mega-events/agent-handlers/turnEndHandler/gameScoring.ts +73 -0
  149. package/extensions/mega-events/agent-handlers/turnEndHandler/lengthStop.ts +29 -0
  150. package/extensions/mega-events/agent-handlers/turnEndHandler/memoryReview.ts +42 -0
  151. package/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.ts +61 -0
  152. package/extensions/mega-events/agent-handlers/turnEndHandler.ts +64 -0
  153. package/extensions/mega-events/agent-handlers.ts +15 -861
  154. package/extensions/mega-events/context-handler/afterCompact.ts +213 -0
  155. package/extensions/mega-events/context-handler/messageText.ts +20 -0
  156. package/extensions/mega-events/context-handler/tailResult.ts +63 -0
  157. package/extensions/mega-events/context-handler.ts +17 -235
  158. package/extensions/mega-events/separated-prompt.test/_helpers.ts +35 -0
  159. package/package.json +1 -1
  160. package/src/config.ts +4 -0
  161. package/src/dedup-engine.test/_helpers.ts +79 -0
  162. package/src/e2e.test/_helpers.ts +59 -0
  163. package/src/embedder.ts +4 -0
  164. package/src/httpEmbedder.ts +7 -0
  165. package/src/hyde.ts +162 -0
  166. package/src/importance.test/_helpers.ts +5 -0
  167. package/src/memoryGraph/sources/checkpoints.ts +99 -0
  168. package/src/memoryGraph/sources/edges.ts +23 -0
  169. package/src/memoryGraph/sources/flags.ts +26 -0
  170. package/src/memoryGraph/sources/helpers.ts +13 -0
  171. package/src/memoryGraph/sources/memories.ts +108 -0
  172. package/src/memoryGraph/sources/raptor.ts +37 -0
  173. package/src/memoryGraph/sources/turns.ts +226 -0
  174. package/src/memoryGraph/sources.ts +19 -544
  175. package/src/memoryGraph-gates.test/_helpers.ts +164 -0
  176. package/src/ratio.bench.test/_helpers.ts +268 -0
  177. package/src/recall/async.ts +142 -0
  178. package/src/recall/format.ts +173 -0
  179. package/src/recall/memory.ts +90 -0
  180. package/src/recall/reformulate.ts +111 -0
  181. package/src/recall/sync.ts +196 -0
  182. package/src/recall/types.ts +63 -0
  183. package/src/recall.ts +25 -712
  184. package/src/store/sqlite/schema/core.ts +233 -0
  185. package/src/store/sqlite/schema/game.ts +66 -0
  186. package/src/store/sqlite/schema/plan-v2.ts +108 -0
  187. package/src/store/sqlite/schema/turns.ts +60 -0
  188. package/src/store/sqlite/schema.ts +13 -441
  189. package/src/store/turns/contract-compliance.test/_helpers.ts +38 -0
  190. package/src/store/turns/sqlite-store/admin.ts +249 -0
  191. package/src/store/turns/sqlite-store/ctx.ts +14 -0
  192. package/src/store/turns/sqlite-store/read.ts +174 -0
  193. package/src/store/turns/sqlite-store/rows.ts +55 -0
  194. package/src/store/turns/sqlite-store/write.ts +193 -0
  195. package/src/store/turns/sqlite-store.ts +33 -514
  196. package/src/vectorStore/class.ts +433 -0
  197. package/src/vectorStore/hash.ts +11 -0
  198. package/src/vectorStore/types.ts +58 -0
  199. package/src/vectorStore.test/_helpers.ts +20 -0
  200. package/src/vectorStore.ts +13 -489
  201. package/extensions/dashboard-client/dist/assets/MaintenanceTab-DZi1MnZI.js +0 -2
  202. package/extensions/dashboard-client/dist/assets/MaintenanceTab-DZi1MnZI.js.map +0 -1
  203. package/extensions/dashboard-client/dist/assets/MemoryMapTab-CjUa9zzw.js +0 -2
  204. package/extensions/dashboard-client/dist/assets/MemoryMapTab-CjUa9zzw.js.map +0 -1
  205. package/extensions/dashboard-client/dist/assets/SetupTab-C_BwmebC.js +0 -2
  206. package/extensions/dashboard-client/dist/assets/SetupTab-C_BwmebC.js.map +0 -1
  207. package/extensions/dashboard-client/dist/assets/index-Cucgohga.js.map +0 -1
@@ -0,0 +1,173 @@
1
+ /**
2
+ * recall/format.ts — recall-block formatting + the S25 RAPTOR overview header
3
+ * and the S57 B3 recall-quality metrics logger.
4
+ *
5
+ * Pure helpers: given hit/cluster/memory data they produce the model-visible
6
+ * text block (or log metrics). No store mutation here.
7
+ */
8
+ import type { SearchHit, VectorStore } from "../vectorStore.js";
9
+ import { rehydrateRaptorTree, isShadowMode } from "../dedup/raptor/index.js";
10
+ import { maxCheckpointTimestamp } from "../store/sqlite.js";
11
+ import { normalizeSessionId } from "../store.js";
12
+ import { cosineSimilarity } from "../embedder.js";
13
+ import { computeRecallMetrics } from "../recallMetrics.js";
14
+ import { Logger } from "../log.js";
15
+
16
+ /** Wrap a recall block so the model reads it as restored compacted context. */
17
+ export function formatRecallBlock(hits: SearchHit[]): string {
18
+ if (hits.length === 0) return "";
19
+ const parts = hits.map((h, i) => {
20
+ const score = (h.score * 100).toFixed(0);
21
+ // S17: label a cross-repo hit with its source repo (the repoId doubles as
22
+ // that repo's stateDir, so the last path segment is the repo's display
23
+ // name). Same-repo hits (no repoId) stay unlabeled.
24
+ const repoName = h.repoId
25
+ ? ` (from repo ${h.repoId.split("/").filter(Boolean).pop() ?? h.repoId})`
26
+ : "";
27
+ // S42B: a RAPTOR cluster node hit (not a stored checkpoint) is labeled as a
28
+ // hierarchical summary and uses raptorSummary as its body. No Key files line
29
+ // (cluster nodes carry no file list).
30
+ if (h.raptorLevel !== undefined) {
31
+ return (
32
+ `### Recalled cluster summary [${i + 1}] (level ${h.raptorLevel}, relevance ${score}%)${repoName}\n` +
33
+ `${(h.raptorSummary ?? h.checkpoint.summary).trim()}\n`
34
+ );
35
+ }
36
+ return (
37
+ `### Recalled context [${i + 1}] (relevance ${score}%)${repoName}\n` +
38
+ `${h.checkpoint.summary.trim()}\n` +
39
+ (h.checkpoint.filesModified.length
40
+ ? `Key files: ${h.checkpoint.filesModified.join(", ")}.\n`
41
+ : "")
42
+ );
43
+ });
44
+ return (
45
+ "The following compacted context was recalled from earlier in this session " +
46
+ "and is relevant to the current request. Treat it as background you already know:\n\n" +
47
+ parts.join("\n")
48
+ );
49
+ }
50
+
51
+ /**
52
+ * S25 Phase-2: format the RAPTOR tree's top-level summary nodes (root + level-1
53
+ * clusters) as a hierarchical overview HEADER. Surfacing the high-level topical
54
+ * structure before the detailed checkpoint hits gives the model a map of what
55
+ * the session has covered. `nodes` are the RAPTOR summary nodes to surface,
56
+ * highest level first (root → level-1 clusters). Returns "" when empty.
57
+ */
58
+ export function formatRaptorBlock(
59
+ nodes: { summary: string; level: number; score?: number }[],
60
+ ): string {
61
+ if (nodes.length === 0) return "";
62
+ const parts = nodes.map((n, i) => {
63
+ const score =
64
+ n.score !== undefined
65
+ ? ` (relevance ${(n.score * 100).toFixed(0)}%)`
66
+ : "";
67
+ const label =
68
+ n.level === 0
69
+ ? `Session overview [${i + 1}]${score}`
70
+ : `Cluster summary [${i + 1}] (level ${n.level})${score}`;
71
+ return `### ${label}\n${n.summary.trim()}\n`;
72
+ });
73
+ return (
74
+ "The following hierarchical overview summarizes the structure of this " +
75
+ "session so far. Use it as a map of what has been covered:\n\n" +
76
+ parts.join("\n")
77
+ );
78
+ }
79
+
80
+ /** Format one memory hit for the recall block. Category + score for traceability. */
81
+ export function formatMemoryRecallBlock(
82
+ hits: Array<{ content: string; category: string | null; score: number; label?: string }>,
83
+ ): string {
84
+ if (hits.length === 0) return "";
85
+ const parts = hits.map((h, i) => {
86
+ const pct = (h.score * 100).toFixed(0);
87
+ const cat = h.category ? `[${h.category}] ` : "";
88
+ const src = h.label ? ` ${h.label}` : "";
89
+ return `### Recalled memory [${i + 1}] (relevance ${pct}%${src})\n${cat}${h.content.trim()}`;
90
+ });
91
+ return (
92
+ "The following facts about this project were saved from earlier turns " +
93
+ "and are relevant to the current request. Treat them as established:\n\n" +
94
+ parts.join("\n")
95
+ );
96
+ }
97
+
98
+ /**
99
+ * S25 Phase-2: build the hierarchical overview header for a session. Rehydrates
100
+ * the persisted RAPTOR tree, picks the root + top level-1 cluster nodes by
101
+ * cosine similarity to the query, and formats them via formatRaptorBlock.
102
+ * Returns "" when no tree, stale tree, timedOut tree, or shadow mode. Non-fatal
103
+ * (wrapped in try/catch) — the overview is a bonus; a failure must never block
104
+ * the detailed recall block.
105
+ */
106
+ export function raptorOverviewBlock(
107
+ store: VectorStore,
108
+ sessionId: string,
109
+ query: string,
110
+ ): string {
111
+ try {
112
+ const sid = normalizeSessionId(sessionId);
113
+ // S25 gate: the overview header is part of the RAPTOR serve surface, so it
114
+ // must honor the same contract as raptorSearchHits — shadow mode is
115
+ // logging-only building, not injection.
116
+ if (isShadowMode()) return "";
117
+ const tree = rehydrateRaptorTree(sid, store.stateDir);
118
+ if (!tree || !tree.rootId || tree.timedOut) return "";
119
+ // Freshness: a tree built before the session's latest checkpoint is stale.
120
+ const maxTs = maxCheckpointTimestamp(sid, store.stateDir);
121
+ if (tree.builtAt && tree.builtAt < maxTs) return "";
122
+ const root = tree.nodes.get(tree.rootId);
123
+ if (!root) return "";
124
+ const qv = store.embedder.embed(query);
125
+ // Root (level 0) first, then the top level-1 clusters by cosine to the query.
126
+ const nodes: { summary: string; level: number; score: number }[] = [
127
+ {
128
+ summary: root.summary,
129
+ level: root.level,
130
+ score: cosineSimilarity(qv, root.embedding),
131
+ },
132
+ ];
133
+ const level1 = [...tree.nodes.values()]
134
+ .filter((n) => n.level === 1 && n.summary)
135
+ .map((n) => ({
136
+ summary: n.summary,
137
+ level: n.level,
138
+ score: cosineSimilarity(qv, n.embedding),
139
+ }))
140
+ .sort((a, b) => b.score - a.score)
141
+ .slice(0, 3);
142
+ nodes.push(...level1);
143
+ return formatRaptorBlock(nodes);
144
+ } catch {
145
+ return ""; // non-fatal: overview is a bonus
146
+ }
147
+ }
148
+
149
+ /**
150
+ * B3: Compute recall-quality metrics on the injected hits and log them.
151
+ * Only called when RAG_RECALL_METRICS() is ON. Non-fatal.
152
+ */
153
+ export function scoreAndLogRecallMetrics(query: string, toInject: SearchHit[]): void {
154
+ try {
155
+ const logger = new Logger();
156
+ const metrics = computeRecallMetrics(query, toInject);
157
+ logger.info("recall_metrics", {
158
+ hitCount: toInject.length,
159
+ score: metrics.score,
160
+ pass: metrics.pass,
161
+ });
162
+ if (!metrics.pass && toInject.length > 0) {
163
+ logger.info("recall_metrics_low_quality", {
164
+ score: metrics.score,
165
+ relevance: metrics.breakdown.relevance,
166
+ coverage: metrics.breakdown.coverage,
167
+ diversity: metrics.breakdown.diversity,
168
+ });
169
+ }
170
+ } catch {
171
+ /* non-fatal: metrics never break recall */
172
+ }
173
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * recall/memory.ts — S21 memory recall: durables (decisions, rules,
3
+ * user-saved facts) live in the `memories` table. We mirror the checkpoint
4
+ * recall path: rank by cosine, format a block, respect a token cap so it can
5
+ * never net-inflate the system prompt.
6
+ */
7
+ import { estimateBlockTokens } from "../tokens.js";
8
+ import { formatMemoryRecallBlock } from "./format.js";
9
+ import type { MemoryRecallInjectOptions } from "./types.js";
10
+
11
+ /** Recall top-k durable memories, format into a token-capped block. */
12
+ export async function recallMemoriesAndInline(
13
+ opts: MemoryRecallInjectOptions,
14
+ ): Promise<{ empty: boolean; block: string; report: string[] }> {
15
+ const limit = opts.limit ?? 5;
16
+ const maxTokens = opts.recallMaxTokens ?? 0;
17
+ const { recallMemories, recallMemoriesCrossRepo } = await import(
18
+ "../memoryRecall.js"
19
+ );
20
+ const hits = await recallMemories(opts.query, opts.stateDir, {
21
+ topK: limit,
22
+ minSimilarity: opts.minSimilarity ?? 0.2,
23
+ });
24
+
25
+ // S24 cross-repo augmentation: if same-repo recall is thin, pull additional
26
+ // memories from OTHER repos via the PGlite HNSW index. Non-fatal: a failure
27
+ // degrades to the same-repo hits only.
28
+ const crossHits: Array<{ memory: any; score: number; repoId: string }> = [];
29
+ if (opts.crossRepo && hits.length < limit) {
30
+ try {
31
+ const x = await recallMemoriesCrossRepo(opts.query, opts.stateDir, {
32
+ repo: null,
33
+ limit: limit - hits.length,
34
+ crossRepoCosine: opts.crossRepoCosine ?? 0.3,
35
+ });
36
+ for (const h of x) crossHits.push(h);
37
+ } catch {
38
+ /* non-fatal — cross-repo failure → same-repo only */
39
+ }
40
+ }
41
+ if (hits.length === 0 && crossHits.length === 0)
42
+ return { empty: true, block: "", report: [] };
43
+
44
+ // Same incremental token cap pattern as checkpoint recall.
45
+ const parts: string[] = [];
46
+ const report: string[] = [];
47
+ let blockTokens = 0;
48
+ const pushHit = (
49
+ content: string,
50
+ category: string | null,
51
+ score: number,
52
+ label: string,
53
+ blockSuffix?: string,
54
+ ) => {
55
+ const part = formatMemoryRecallBlock([{ content, category, score, label: blockSuffix }]);
56
+ const partTokens = estimateBlockTokens(part);
57
+ if (maxTokens > 0 && blockTokens + partTokens > maxTokens) return false;
58
+ parts.push(part);
59
+ report.push(
60
+ ` • ${label} (${(score * 100).toFixed(0)}%): ${content.slice(0, 60).replace(/\n/g, " ")}…`,
61
+ );
62
+ blockTokens += partTokens;
63
+ return true;
64
+ };
65
+ for (const h of hits) {
66
+ if (
67
+ !pushHit(
68
+ h.memory.content,
69
+ h.memory.category,
70
+ h.score,
71
+ `memory#${h.memory.id}`,
72
+ )
73
+ )
74
+ break;
75
+ }
76
+ for (const h of crossHits) {
77
+ const repoLabel = h.repoId.split(/[\\/]/).filter(Boolean).pop() ?? h.repoId;
78
+ if (
79
+ !pushHit(
80
+ h.memory.content,
81
+ h.memory.category,
82
+ h.score,
83
+ `memory#${h.memory.id} (from ${repoLabel})`,
84
+ `from ${repoLabel}`,
85
+ )
86
+ )
87
+ break;
88
+ }
89
+ return { empty: parts.length === 0, block: parts.join("\n"), report };
90
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * recall/reformulate.ts — S57 RAG suite helpers (B1/B2).
3
+ *
4
+ * Each helper is only called when its feature flag (RAG_QUERY_REFORMULATION /
5
+ * RAG_TIERED_ROUTER) is ON; flag-OFF never reaches them. All degrade
6
+ * non-fatally to the standard path on any error.
7
+ */
8
+ import { recall as searchRecall } from "../engine.js";
9
+ import type { SearchHit, VectorStore } from "../vectorStore.js";
10
+ import { defaultEmbedder } from "../embedder.js";
11
+ import { reformulateQuery, isVagueQuery } from "../queryReformulation.js";
12
+ import type { CorpusStats, EmbedderLike, NeighborScanner } from "../queryReformulation.js";
13
+ import { VAGUE_MIN_WORDS, VAGUE_VERY_SHORT_WORDS } from "../queryReformulation/cache.js";
14
+ import { getTieredRouter } from "../tieredRouter.js";
15
+
16
+ /**
17
+ * B1: If the query is vague, reformulate it via embedding-neighbor TF-IDF
18
+ * expansion. Returns the original query when reformulation is not supported
19
+ * or the query is already specific. Non-fatal: any error → original query.
20
+ */
21
+ export function reformulateRecallQuery(
22
+ query: string,
23
+ store: VectorStore,
24
+ sessionId: string,
25
+ ): string {
26
+ try {
27
+ if (
28
+ !isVagueQuery(query, {
29
+ vagueMinWords: VAGUE_MIN_WORDS,
30
+ vagueVeryShortWords: VAGUE_VERY_SHORT_WORDS,
31
+ })
32
+ )
33
+ return query;
34
+ // Pre-compute neighbor candidates once. reformulateQuery embeds the query
35
+ // and calls `scan` internally; we return these pre-computed neighbors (same
36
+ // query) so the store needs no by-embedding lookup.
37
+ const { hits } = searchRecall(
38
+ { sessionId, query, limit: 10, skipInjected: false },
39
+ store,
40
+ );
41
+ if (hits.length < 2) return query; // too few neighbors for TF-IDF
42
+ const neighbors = hits.map((h) => ({
43
+ id: h.checkpoint.checkpointId,
44
+ score: h.score,
45
+ }));
46
+ const textById = new Map<string, string>();
47
+ for (const h of hits) textById.set(h.checkpoint.checkpointId, h.checkpoint.summary);
48
+ const scan: NeighborScanner = () => neighbors;
49
+ const corpus: CorpusStats = {
50
+ totalDocs: neighbors.length,
51
+ docFreq: (term: string) => {
52
+ let df = 0;
53
+ for (const text of textById.values()) {
54
+ if (text.toLowerCase().includes(term.toLowerCase())) df++;
55
+ }
56
+ return Math.max(df, 1);
57
+ },
58
+ };
59
+ const neighborTexts = (ids: string[]) =>
60
+ ids.map((id) => ({ id, text: textById.get(id) ?? "" }));
61
+ const { result } = reformulateQuery(
62
+ query,
63
+ defaultEmbedder() as unknown as EmbedderLike,
64
+ scan,
65
+ corpus,
66
+ neighborTexts,
67
+ );
68
+ return result.expanded.length > query.length ? result.expanded : query;
69
+ } catch {
70
+ return query; // non-fatal: fall back to original query
71
+ }
72
+ }
73
+
74
+ /**
75
+ * B2: Synchronous tiered recall. The TieredRouter.route() is async, so the
76
+ * sync path can only peek the L0 in-memory cache; on miss it falls through to
77
+ * the standard sync vector scan. The router's L1/L2 value is realized on the
78
+ * async path (recallAndInlineAsync). Non-fatal.
79
+ */
80
+ export function runTieredRecall(
81
+ query: string,
82
+ sessionId: string,
83
+ limit: number,
84
+ skip: boolean,
85
+ store: VectorStore,
86
+ ): { newHits: SearchHit[]; tier: string } {
87
+ try {
88
+ const router = getTieredRouter();
89
+ if (!router) {
90
+ const result = searchRecall(
91
+ { sessionId, query, limit, skipInjected: skip },
92
+ store,
93
+ );
94
+ return { newHits: result.newHits, tier: "off" };
95
+ }
96
+ // Peek the L0 in-memory cache synchronously; fall through on miss.
97
+ const cached = router.peekCache(sessionId, query, limit);
98
+ if (cached && cached.length > 0) return { newHits: cached, tier: "L0-cache" };
99
+ const result = searchRecall(
100
+ { sessionId, query, limit, skipInjected: skip },
101
+ store,
102
+ );
103
+ return { newHits: result.newHits, tier: "sync-fallback" };
104
+ } catch {
105
+ const result = searchRecall(
106
+ { sessionId, query, limit, skipInjected: skip },
107
+ store,
108
+ );
109
+ return { newHits: result.newHits, tier: "fallback-error" };
110
+ }
111
+ }
@@ -0,0 +1,196 @@
1
+ /**
2
+ * recall/sync.ts — the unified synchronous recall+dedupe+prepare-inject path.
3
+ *
4
+ * ONE vector store, THREE entry points, ONE dedup engine. Every way context
5
+ * gets re-injected into the window (auto-inline on resume, on-demand
6
+ * /recall-context, and the dedup sentinel) goes through `recallAndInline`.
7
+ * It always does: search -> dedupe -> inject. The only thing that differs per
8
+ * entry point is *what triggers it* and *what query it uses*.
9
+ *
10
+ * Injection respects PREVENT-PI-003: pi has no `system` message role, so we
11
+ * prepend our recall block to the system prompt via the `before_agent_start`
12
+ * hook's `systemPrompt` result (the extension wires that). This module is
13
+ * pi-agnostic: it returns an injectable text block and records injections; the
14
+ * extension decides where it lands.
15
+ */
16
+ import { recall as searchRecall } from "../engine.js";
17
+ import { vectorMarkInjected, type VectorStore } from "../vectorStore.js";
18
+ import { estimateBlockTokens } from "../tokens.js";
19
+ import { defaultEmbedder, cosineSimilarity } from "../embedder.js";
20
+ import { RAG_QUERY_REFORMULATION, RAG_TIERED_ROUTER, RAG_RECALL_METRICS, RAG_HYDE_ENABLED } from "../config.js";
21
+ import { generateHypotheticalDoc, fuseRecallHits } from "../hyde.js";
22
+ import type { RecallInjectOptions, RecallInjectResult } from "./types.js";
23
+ import type { SearchHit } from "../vectorStore.js";
24
+ import { reformulateRecallQuery, runTieredRecall } from "./reformulate.js";
25
+ import { formatRecallBlock, scoreAndLogRecallMetrics, raptorOverviewBlock } from "./format.js";
26
+
27
+ /**
28
+ * Recall and inline context from the checkpoint store.
29
+ *
30
+ * S27 contract — DB-Mirror demotion:
31
+ *
32
+ * When `MEGACOMPACT_DB_MIRROR` is ON, the `raw_transcript` table is the
33
+ * canonical, byte-stable source of truth for message reconstruction. The
34
+ * `dedup_mirror` provides space-efficient storage with ref_count tracking
35
+ * (see src/mirror/dedup.ts). The legacy JSON checkpoint is retained as a
36
+ * DR snapshot only (see src/store.ts checkpoint helpers).
37
+ *
38
+ * The recall function continues to work from the VectorStore (checkpoint
39
+ * summaries + embeddings) for fast semantic search — this path is unaffected
40
+ * by the mirror flag. If full transcript reconstruction is ever needed
41
+ * (replay, export, debug), prefer reading from `raw_transcript + dedup_mirror`
42
+ * via `listRawTranscriptRange()` + `dedupTranscript()` instead of the legacy
43
+ * JSON checkpoint. Falls back to legacy checkpoint if mirror is empty
44
+ * (pre-migration sessions).
45
+ *
46
+ * Pi-agnostic: no pi runtime imports (src/ invariant).
47
+ */
48
+ export function recallAndInline(
49
+ opts: RecallInjectOptions,
50
+ store: VectorStore,
51
+ ): RecallInjectResult {
52
+ // ── S27 Recall Demotion ─────────────────────────────────────────────
53
+ //
54
+ // When MEGACOMPACT_DB_MIRROR is ON, the raw_transcript + dedup_mirror
55
+ // tables are preferred for byte-stable reconstruction. The current
56
+ // recall path (VectorStore search → format → inject) is unaffected —
57
+ // it provides fast semantic search over checkpoint summaries.
58
+ //
59
+ // If full transcript reconstruction is ever needed (replay, export,
60
+ // debug), call reconstructFromMirror(db, sessionId, fromSeq, toSeq)
61
+ // from src/mirror/dedup.ts instead of reading from the legacy JSON
62
+ // checkpoint. Falls back to legacy checkpoint if mirror is empty
63
+ // (pre-migration sessions).
64
+ //
65
+ // Invariant: raw_transcript + dedup_mirror are additive and never
66
+ // lose data. The legacy JSON checkpoint remains as a DR snapshot.
67
+ // ─────────────────────────────────────────────────────────────────────
68
+
69
+ const limit = opts.limit ?? 3;
70
+ const skip = opts.skipInjected ?? true;
71
+ const maxTokens = opts.recallMaxTokens ?? 0; // 0 = unbounded (legacy behavior)
72
+ const doWindowDedupe = opts.windowDedupe ?? false;
73
+ const dedupSim = opts.dedupSim ?? 0.9;
74
+
75
+ // F4: thread skipInjected through to searchRecall instead of hardcoding false
76
+ // and re-implementing the filter here. newHits is already deduped when skip
77
+ // is true (default); equals hits when skip is false (openclaw command path).
78
+ //
79
+ // S57 B1: optionally expand a vague query via TF-IDF neighbor terms.
80
+ // S57 B2: optionally route via the TieredRouter's sync L0 cache peek.
81
+ // Both flags default OFF — recallQuery === opts.query and the single
82
+ // searchRecall call reproduce the pre-S57 byte-identical path.
83
+ const recallQuery = RAG_QUERY_REFORMULATION()
84
+ ? reformulateRecallQuery(opts.query, store, opts.sessionId)
85
+ : opts.query;
86
+
87
+ // F1: hoist one embedder instance (shared by HyDE below + inline dedupe later).
88
+ // defaultEmbedder() is deterministic but creating it per call wastes allocations.
89
+ const embedder = defaultEmbedder();
90
+
91
+ // S43 re-plan: HyDE — when HttpEmbedder is active and RAG_HYDE_ENABLED,
92
+ // generate a hypothetical answer doc, embed it, run a second searchRecall,
93
+ // and RRF-fuse with the raw-query results. Non-fatal: any error → raw-only.
94
+ // TrigramEmbedder (kind !== "http") skips this — S57 B1 reformulation above
95
+ // already handles the no-LLM path.
96
+ let hydeHits: SearchHit[] | null = null;
97
+ if (RAG_HYDE_ENABLED() && embedder.kind === "http") {
98
+ const hyde = generateHypotheticalDoc(opts.query, embedder);
99
+ if (hyde) {
100
+ try {
101
+ const r2 = searchRecall(
102
+ { sessionId: opts.sessionId, query: hyde, limit, skipInjected: skip },
103
+ store,
104
+ );
105
+ hydeHits = r2.newHits;
106
+ } catch {
107
+ hydeHits = null;
108
+ }
109
+ }
110
+ }
111
+
112
+ let newHits: SearchHit[];
113
+ if (RAG_TIERED_ROUTER()) {
114
+ newHits = runTieredRecall(recallQuery, opts.sessionId, limit, skip, store).newHits;
115
+ } else {
116
+ const result = searchRecall(
117
+ { sessionId: opts.sessionId, query: recallQuery, limit, skipInjected: skip },
118
+ store,
119
+ );
120
+ newHits = result.newHits;
121
+ }
122
+
123
+ // S43 re-plan: fuse the hypothetical-doc hits with the raw-query hits via RRF
124
+ // when HyDE produced results. The downstream inline dedupe + metrics + format
125
+ // operate on this fused set.
126
+ if (hydeHits && hydeHits.length > 0) {
127
+ newHits = fuseRecallHits(newHits, hydeHits, limit);
128
+ }
129
+ // Precompute live-window embeddings once for inline dedupe (Fix C). Trigram
130
+ // embedder is local + cheap; never a network call (PREVENT-PI-004).
131
+ let liveEmbeddings: number[][] = [];
132
+ if (doWindowDedupe && opts.liveWindow && opts.liveWindow.length > 0) {
133
+ liveEmbeddings = opts.liveWindow.map((m) => embedder.embed(m));
134
+ }
135
+
136
+ // F3: build the hit list first; format ONCE at the end so the block carries
137
+ // exactly one preamble and [1..n] numbering, and the token cap counts body
138
+ // tokens (one preamble at format time, not N). We accumulate summaries and
139
+ // break mid-stream when the cap would be exceeded.
140
+ const toInject: SearchHit[] = [];
141
+ let blockTokens = 0;
142
+
143
+ for (const h of newHits) {
144
+ // Inline dedupe: skip a hit already resident in the live window (Fix C).
145
+ if (doWindowDedupe && liveEmbeddings.length > 0) {
146
+ const hitVec = embedder.embed(h.checkpoint.summary);
147
+ if (liveEmbeddings.some((v) => cosineSimilarity(v, hitVec) >= dedupSim))
148
+ continue;
149
+ }
150
+
151
+ const partTokens = estimateBlockTokens(h.checkpoint.summary);
152
+ // Token cap: never push a chunk that would overrun the ceiling.
153
+ if (maxTokens > 0 && blockTokens + partTokens > maxTokens) break;
154
+
155
+ toInject.push(h);
156
+ blockTokens += partTokens;
157
+ vectorMarkInjected(store, opts.sessionId, h.checkpoint.checkpointId);
158
+ }
159
+
160
+ // S57 B3: optionally score + log recall quality metrics (flag-OFF: skipped,
161
+ // byte-identical). Scores the injected hits against the ORIGINAL query so the
162
+ // metric measures whether the (possibly expanded) search results stay
163
+ // relevant to what the user actually asked.
164
+ if (RAG_RECALL_METRICS()) scoreAndLogRecallMetrics(opts.query, toInject);
165
+
166
+ // F3: format once — one preamble, correct [1..n] numbering.
167
+ const recallBlock = toInject.length > 0 ? formatRecallBlock(toInject) : "";
168
+ const report = toInject.map(
169
+ (h) =>
170
+ ` • ${h.checkpoint.checkpointId} (${h.checkpoint.summary.slice(0, 60).replace(/\n/g, " ")}…)`,
171
+ );
172
+
173
+ // S25 Phase-2 (RAPTOR_INJECT_SUMMARIES): prepend a hierarchical overview
174
+ // header built from the tree's top-level summary nodes (root + the
175
+ // highest-scoring level-1 cluster summaries). This gives the model a topical
176
+ // map of the session before the detailed checkpoint hits. Default ON via
177
+ // the store's config; `opts.raptorSummaries` (when explicitly set) overrides.
178
+ // Skipped when no tree exists, the tree is stale/timedOut, or shadow mode is on.
179
+ let overview = "";
180
+ const injectSummaries =
181
+ opts.raptorSummaries ?? store.cfg.RAPTOR_INJECT_SUMMARIES;
182
+ if (injectSummaries && recallBlock) {
183
+ overview = raptorOverviewBlock(store, opts.sessionId, opts.query);
184
+ }
185
+ const block =
186
+ overview && recallBlock
187
+ ? overview + "\n" + recallBlock
188
+ : overview || recallBlock;
189
+
190
+ return {
191
+ toInject,
192
+ report,
193
+ block,
194
+ empty: block.length === 0,
195
+ };
196
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * recall/types.ts — shared types for the Layer-5 recall/inject pipeline.
3
+ *
4
+ * Lives under src/recall/ so recall.ts (the public pointer) stays a thin shell;
5
+ * every public recall entry point ships its options/result contracts in one
6
+ * cohesive types module.
7
+ */
8
+ import type { SearchHit } from "../vectorStore.js";
9
+
10
+ export type RecallSource = "resume" | "command" | "sentinel";
11
+
12
+ export interface RecallInjectOptions {
13
+ sessionId: string;
14
+ query: string;
15
+ limit?: number;
16
+ source: RecallSource;
17
+ /** Skip checkpoints already injected this session (recall dedup). */
18
+ skipInjected?: boolean;
19
+ /** Token ceiling for the re-injected block (Fix C). Recall stops adding once
20
+ * the block would exceed this, so the read path can never net-inflate. */
21
+ recallMaxTokens?: number;
22
+ /** Inline-dedupe hits against the live window (Fix C): drop a hit whose
23
+ * summary is ≥ `dedupSim` similar to a live message. */
24
+ windowDedupe?: boolean;
25
+ /** Live window text (from the session manager) used for inline dedupe. */
26
+ liveWindow?: string[];
27
+ /** Similarity threshold for inline dedupe (defaults to 0.9). */
28
+ dedupSim?: number;
29
+ /** S18: index dir of the machine-wide injected-set. When set on a cross-repo
30
+ * recall, a foreign checkpoint already injected (in any session) is skipped
31
+ * and a fresh injection is recorded globally. */
32
+ globalIndexDir?: string;
33
+ /** S25 Phase-2: also inject top-level RAPTOR summary nodes (root + level-1
34
+ * clusters) as a hierarchical overview HEADER on the recall block. Defaults
35
+ * to the `RAPTOR_INJECT_SUMMARIES` config flag. The overview helps the model
36
+ * see the session's topical structure before the detailed checkpoint hits. */
37
+ raptorSummaries?: boolean;
38
+ }
39
+
40
+ export interface RecallInjectResult {
41
+ /** Blocks that are ready to inline (already deduped against the window). */
42
+ toInject: SearchHit[];
43
+ /** Human-readable lines for status/notify reporting. */
44
+ report: string[];
45
+ /** The concatenated, model-visible recall block (empty when nothing new). */
46
+ block: string;
47
+ /** True when nothing new was inlined. */
48
+ empty: boolean;
49
+ }
50
+
51
+ export interface MemoryRecallInjectOptions {
52
+ query: string;
53
+ stateDir: string;
54
+ limit?: number;
55
+ /** Token ceiling; defaults to the same `recallMaxTokens` used for checkpoints. */
56
+ recallMaxTokens?: number;
57
+ /** Cosine threshold; default 0.2. */
58
+ minSimilarity?: number;
59
+ /** When true, augment same-repo recall with cross-repo PGlite NN (S24). */
60
+ crossRepo?: boolean;
61
+ /** Stricter cosine floor for cross-repo memory hits (S24). Default 0.3. */
62
+ crossRepoCosine?: number;
63
+ }