pi-mega-compact 0.13.5 → 0.13.7

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 (234) hide show
  1. package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +50 -0
  2. package/dist/extensions/dashboard-server/api-contracts/rag-metrics.js +1 -0
  3. package/dist/extensions/dashboard-server/api-contracts/rag-settings.js +3 -3
  4. package/dist/extensions/dashboard-server/api-contracts/wiki.js +1 -0
  5. package/dist/extensions/dashboard-server/index-reader.js +3 -1
  6. package/dist/extensions/dashboard-server/resolve-snapshot.js +81 -0
  7. package/dist/extensions/dashboard-server/routes-core.js +8 -3
  8. package/dist/extensions/dashboard-server/routes-rag-metrics.js +60 -0
  9. package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +167 -0
  10. package/dist/extensions/dashboard-server/routes-rag-settings.js +136 -77
  11. package/dist/extensions/dashboard-server/routes-repo.js +5 -1
  12. package/dist/extensions/dashboard-server/routes-sessions.js +6 -3
  13. package/dist/extensions/dashboard-server/routes-wiki-helpers.js +97 -0
  14. package/dist/extensions/dashboard-server/routes-wiki.js +330 -0
  15. package/dist/extensions/dashboard-server/routes.js +2 -0
  16. package/dist/extensions/dashboard-server/server.js +17 -3
  17. package/dist/extensions/mega-events/context-handler/afterCompact.js +35 -2
  18. package/dist/extensions/mega-pipeline/recall.js +31 -0
  19. package/dist/extensions/mega-runtime/capture-model.js +5 -2
  20. package/dist/extensions/mega-turn-store.js +2 -1
  21. package/dist/src/config/turns.js +10 -0
  22. package/dist/src/config.js +2 -0
  23. package/dist/src/pricing.js +8 -0
  24. package/dist/src/recall/async.js +10 -1
  25. package/dist/src/recall/format.js +10 -1
  26. package/dist/src/recall/hydeTelemetry.js +22 -0
  27. package/dist/src/recall/sync.js +28 -2
  28. package/dist/src/store/turns/hydeStore.js +32 -0
  29. package/dist/src/store/turns/index.js +1 -0
  30. package/dist/src/store/turns/schema.js +102 -1
  31. package/dist/src/store/turns/sqlite-store/rows.js +23 -0
  32. package/dist/src/store/turns/sqlite-store/write.js +7 -3
  33. package/dist/src/topics/durability.js +187 -0
  34. package/dist/src/topics/index.js +2 -1
  35. package/dist/src/topics/store.js +28 -9
  36. package/dist/src/wiki/curation-helpers.js +71 -0
  37. package/dist/src/wiki/curation.js +147 -0
  38. package/dist/src/wiki/incremental.js +126 -0
  39. package/dist/src/wiki/index.js +5 -0
  40. package/extensions/dashboard-client/dist/assets/AreaChart-j2VOSRp6.js +70 -0
  41. package/extensions/dashboard-client/dist/assets/{SessionsTab-C30AwyoF.js.map → AreaChart-j2VOSRp6.js.map} +1 -1
  42. package/extensions/dashboard-client/dist/assets/CacheTab-Dr1HeMfc.js +2 -0
  43. package/extensions/dashboard-client/dist/assets/CacheTab-Dr1HeMfc.js.map +1 -0
  44. package/extensions/dashboard-client/dist/assets/EventsTab-DRG2k7FS.js +2 -0
  45. package/extensions/dashboard-client/dist/assets/EventsTab-DRG2k7FS.js.map +1 -0
  46. package/extensions/dashboard-client/dist/assets/HealthTab-DGE9PpW3.js +2 -0
  47. package/extensions/dashboard-client/dist/assets/HealthTab-DGE9PpW3.js.map +1 -0
  48. package/extensions/dashboard-client/dist/assets/MaintenanceTab-DeYRhF4q.js +2 -0
  49. package/extensions/dashboard-client/dist/assets/MaintenanceTab-DeYRhF4q.js.map +1 -0
  50. package/extensions/dashboard-client/dist/assets/MemoryMapTab-DSnSO3nG.js +2 -0
  51. package/extensions/dashboard-client/dist/assets/MemoryMapTab-DSnSO3nG.js.map +1 -0
  52. package/extensions/dashboard-client/dist/assets/MetricsTab-RIzNc1Uj.js +2 -0
  53. package/extensions/dashboard-client/dist/assets/MetricsTab-RIzNc1Uj.js.map +1 -0
  54. package/extensions/dashboard-client/dist/assets/OverviewTab-_1BJ4Bmh.js +2 -0
  55. package/extensions/dashboard-client/dist/assets/OverviewTab-_1BJ4Bmh.js.map +1 -0
  56. package/extensions/dashboard-client/dist/assets/ReposTab-DkU7ywZB.js +2 -0
  57. package/extensions/dashboard-client/dist/assets/ReposTab-DkU7ywZB.js.map +1 -0
  58. package/extensions/dashboard-client/dist/assets/SessionsTab-Bq_F_aOk.js +2 -0
  59. package/extensions/dashboard-client/dist/assets/SessionsTab-Bq_F_aOk.js.map +1 -0
  60. package/extensions/dashboard-client/dist/assets/SetupTab-BkBD2Y97.js +2 -0
  61. package/extensions/dashboard-client/dist/assets/SetupTab-BkBD2Y97.js.map +1 -0
  62. package/extensions/dashboard-client/dist/assets/TimeSavedCard-BXzFKU7C.js +2 -0
  63. package/extensions/dashboard-client/dist/assets/TimeSavedCard-BXzFKU7C.js.map +1 -0
  64. package/extensions/dashboard-client/dist/assets/TurnsTab-Bjpp3vBH.js +5 -0
  65. package/extensions/dashboard-client/dist/assets/TurnsTab-Bjpp3vBH.js.map +1 -0
  66. package/extensions/dashboard-client/dist/assets/WikiTab-dBN2EA5G.js +2 -0
  67. package/extensions/dashboard-client/dist/assets/WikiTab-dBN2EA5G.js.map +1 -0
  68. package/extensions/dashboard-client/dist/assets/button-Bt0b5QVp.js +2 -0
  69. package/extensions/dashboard-client/dist/assets/button-Bt0b5QVp.js.map +1 -0
  70. package/extensions/dashboard-client/dist/assets/card-D5CafXtg.js +2 -0
  71. package/extensions/dashboard-client/dist/assets/card-D5CafXtg.js.map +1 -0
  72. package/extensions/dashboard-client/dist/assets/index-CUA6EDRF.css +1 -0
  73. package/extensions/dashboard-client/dist/assets/index-GPHRnEN7.js +137 -0
  74. package/extensions/dashboard-client/dist/assets/index-GPHRnEN7.js.map +1 -0
  75. package/extensions/dashboard-client/dist/assets/pricing-DkYp8-vi.js +2 -0
  76. package/extensions/dashboard-client/dist/assets/pricing-DkYp8-vi.js.map +1 -0
  77. package/extensions/dashboard-client/dist/assets/switch-CY5Gwj1o.js +2 -0
  78. package/extensions/dashboard-client/dist/assets/switch-CY5Gwj1o.js.map +1 -0
  79. package/extensions/dashboard-client/dist/assets/toggle-C9HpkIfH.js +2 -0
  80. package/extensions/dashboard-client/dist/assets/toggle-C9HpkIfH.js.map +1 -0
  81. package/extensions/dashboard-client/dist/assets/{useSSE-p1pOI_-P.js → useSSE-2JFrLDlE.js} +2 -2
  82. package/extensions/dashboard-client/dist/assets/{useSSE-p1pOI_-P.js.map → useSSE-2JFrLDlE.js.map} +1 -1
  83. package/extensions/dashboard-client/dist/index.html +2 -2
  84. package/extensions/dashboard-client/package-lock.json +1995 -191
  85. package/extensions/dashboard-client/package.json +12 -1
  86. package/extensions/dashboard-client/postcss.config.js +6 -0
  87. package/extensions/dashboard-client/src/App.tsx +127 -88
  88. package/extensions/dashboard-client/src/api/client.ts +109 -10
  89. package/extensions/dashboard-client/src/components/ActiveReposTable.tsx +15 -7
  90. package/extensions/dashboard-client/src/components/ActiveSessionsTable.tsx +67 -55
  91. package/extensions/dashboard-client/src/components/CacheHitRateTrendCard.tsx +17 -13
  92. package/extensions/dashboard-client/src/components/CacheHitsCard.tsx +35 -30
  93. package/extensions/dashboard-client/src/components/CacheStatusPerModel.tsx +33 -28
  94. package/extensions/dashboard-client/src/components/EventCategoryFilter.tsx +5 -6
  95. package/extensions/dashboard-client/src/components/EventStream.tsx +17 -13
  96. package/extensions/dashboard-client/src/components/HydeDetailPanel.tsx +173 -0
  97. package/extensions/dashboard-client/src/components/LegendCard.tsx +16 -9
  98. package/extensions/dashboard-client/src/components/PerfCards.tsx +28 -20
  99. package/extensions/dashboard-client/src/components/PerfChart.tsx +9 -6
  100. package/extensions/dashboard-client/src/components/ProviderCacheCard.tsx +15 -7
  101. package/extensions/dashboard-client/src/components/RagDashboard.tsx +253 -0
  102. package/extensions/dashboard-client/src/components/RepoAllSessionsCard.tsx +15 -10
  103. package/extensions/dashboard-client/src/components/RepoContextStack.tsx +24 -16
  104. package/extensions/dashboard-client/src/components/RepoDetailModal.tsx +52 -49
  105. package/extensions/dashboard-client/src/components/RepoTable.tsx +26 -17
  106. package/extensions/dashboard-client/src/components/SessionContextGauges.tsx +11 -14
  107. package/extensions/dashboard-client/src/components/SessionInfo.tsx +20 -15
  108. package/extensions/dashboard-client/src/components/SessionsMemoryChart.tsx +8 -5
  109. package/extensions/dashboard-client/src/components/SummaryTiles.tsx +26 -17
  110. package/extensions/dashboard-client/src/components/TabBar.tsx +1 -3
  111. package/extensions/dashboard-client/src/components/layout/AppShell.tsx +42 -0
  112. package/extensions/dashboard-client/src/components/layout/BottomBar.tsx +88 -0
  113. package/extensions/dashboard-client/src/components/layout/Sidebar.tsx +83 -0
  114. package/extensions/dashboard-client/src/components/ui/badge.tsx +30 -0
  115. package/extensions/dashboard-client/src/components/ui/button.tsx +44 -0
  116. package/extensions/dashboard-client/src/components/ui/card.tsx +32 -0
  117. package/extensions/dashboard-client/src/components/ui/select.tsx +64 -0
  118. package/extensions/dashboard-client/src/components/ui/switch.tsx +26 -0
  119. package/extensions/dashboard-client/src/components/ui/tabs.tsx +36 -0
  120. package/extensions/dashboard-client/src/components/ui/toggle.tsx +28 -0
  121. package/extensions/dashboard-client/src/components/ui/tooltip.tsx +27 -0
  122. package/extensions/dashboard-client/src/config.ts +10 -0
  123. package/extensions/dashboard-client/src/main.tsx +2 -2
  124. package/extensions/dashboard-client/src/memory-map-layout.ts +1 -2
  125. package/extensions/dashboard-client/src/styles/index.css +79 -0
  126. package/extensions/dashboard-client/src/tabs/CacheTab.tsx +33 -26
  127. package/extensions/dashboard-client/src/tabs/EventsTab.tsx +22 -7
  128. package/extensions/dashboard-client/src/tabs/HealthTab.tsx +77 -46
  129. package/extensions/dashboard-client/src/tabs/MaintenanceTab/ActionsCard.tsx +60 -50
  130. package/extensions/dashboard-client/src/tabs/MaintenanceTab/DbStatsCard.tsx +61 -35
  131. package/extensions/dashboard-client/src/tabs/MaintenanceTab/DebugBundleCard.tsx +34 -23
  132. package/extensions/dashboard-client/src/tabs/MaintenanceTab/HealthMitigationCard.tsx +62 -43
  133. package/extensions/dashboard-client/src/tabs/MaintenanceTab/SchemaHealthCard.tsx +53 -32
  134. package/extensions/dashboard-client/src/tabs/MaintenanceTab.tsx +2 -2
  135. package/extensions/dashboard-client/src/tabs/MemoryMapTab/MemoryMapView.tsx +29 -46
  136. package/extensions/dashboard-client/src/tabs/MemoryMapTab/RaptorTreeView.tsx +10 -34
  137. package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +14 -34
  138. package/extensions/dashboard-client/src/tabs/MetricsTab.tsx +5 -2
  139. package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +11 -5
  140. package/extensions/dashboard-client/src/tabs/ReposTab.tsx +5 -5
  141. package/extensions/dashboard-client/src/tabs/SessionsTab.tsx +47 -33
  142. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderSetup.tsx +6 -2
  143. package/extensions/dashboard-client/src/tabs/SetupTab/RagHealthCard.tsx +78 -0
  144. package/extensions/dashboard-client/src/tabs/SetupTab/SettingsPanel.tsx +198 -0
  145. package/extensions/dashboard-client/src/tabs/SetupTab/SettingsSection.tsx +50 -0
  146. package/extensions/dashboard-client/src/tabs/SetupTab.tsx +21 -59
  147. package/extensions/dashboard-client/src/tabs/TopicsTab.tsx +75 -40
  148. package/extensions/dashboard-client/src/tabs/TurnsTab.tsx +150 -112
  149. package/extensions/dashboard-client/src/tabs/WikiTab/TopicEvolutionGraph.tsx +274 -0
  150. package/extensions/dashboard-client/src/tabs/WikiTab/TopicEvolutionView.tsx +71 -0
  151. package/extensions/dashboard-client/src/tabs/WikiTab/TopicTimeline.tsx +103 -0
  152. package/extensions/dashboard-client/src/tabs/WikiTab/WikiPage.tsx +213 -0
  153. package/extensions/dashboard-client/src/tabs/WikiTab/WikiPageControls.tsx +188 -0
  154. package/extensions/dashboard-client/src/tabs/WikiTab.tsx +222 -0
  155. package/extensions/dashboard-client/src/tabs/registry.ts +57 -0
  156. package/extensions/dashboard-client/src/utils/cn.ts +7 -0
  157. package/extensions/dashboard-client/tailwind.config.js +46 -0
  158. package/extensions/dashboard-server/api-contracts/core.ts +59 -0
  159. package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +79 -0
  160. package/extensions/dashboard-server/api-contracts/index.ts +48 -2
  161. package/extensions/dashboard-server/api-contracts/rag-metrics.ts +22 -0
  162. package/extensions/dashboard-server/api-contracts/rag-settings.ts +30 -20
  163. package/extensions/dashboard-server/api-contracts/wiki.ts +138 -0
  164. package/extensions/dashboard-server/index-reader.ts +3 -1
  165. package/extensions/dashboard-server/resolve-snapshot.ts +98 -0
  166. package/extensions/dashboard-server/routes-core.ts +8 -3
  167. package/extensions/dashboard-server/routes-rag-metrics.ts +82 -0
  168. package/extensions/dashboard-server/routes-rag-settings-helpers.ts +314 -0
  169. package/extensions/dashboard-server/routes-rag-settings.ts +147 -97
  170. package/extensions/dashboard-server/routes-repo.ts +5 -1
  171. package/extensions/dashboard-server/routes-sessions.ts +6 -3
  172. package/extensions/dashboard-server/routes-wiki-helpers.ts +126 -0
  173. package/extensions/dashboard-server/routes-wiki.ts +376 -0
  174. package/extensions/dashboard-server/routes.ts +2 -0
  175. package/extensions/dashboard-server/server.ts +18 -2
  176. package/extensions/mega-events/context-handler/afterCompact.ts +44 -1
  177. package/extensions/mega-pipeline/recall.ts +31 -0
  178. package/extensions/mega-runtime/capture-model.ts +5 -2
  179. package/extensions/mega-turn-store.ts +13 -0
  180. package/package.json +1 -1
  181. package/src/config/turns.ts +17 -0
  182. package/src/config.ts +3 -0
  183. package/src/pricing.ts +8 -0
  184. package/src/recall/async.ts +10 -1
  185. package/src/recall/format.ts +15 -2
  186. package/src/recall/hydeTelemetry.ts +44 -0
  187. package/src/recall/sync.ts +34 -2
  188. package/src/recall/types.ts +36 -0
  189. package/src/store/turns/hydeStore.ts +82 -0
  190. package/src/store/turns/index.ts +4 -0
  191. package/src/store/turns/schema.ts +126 -1
  192. package/src/store/turns/sqlite-store/rows.ts +25 -0
  193. package/src/store/turns/sqlite-store/write.ts +20 -2
  194. package/src/store/turns/types.ts +33 -0
  195. package/src/topics/durability.ts +231 -0
  196. package/src/topics/index.ts +7 -1
  197. package/src/topics/store.ts +35 -9
  198. package/src/wiki/curation-helpers.ts +140 -0
  199. package/src/wiki/curation.ts +217 -0
  200. package/src/wiki/incremental.ts +164 -0
  201. package/src/wiki/index.ts +13 -0
  202. package/extensions/dashboard-client/dist/assets/CacheTab-Cqnc7lMg.js +0 -2
  203. package/extensions/dashboard-client/dist/assets/CacheTab-Cqnc7lMg.js.map +0 -1
  204. package/extensions/dashboard-client/dist/assets/EventsTab-tYk3VFAp.js +0 -2
  205. package/extensions/dashboard-client/dist/assets/EventsTab-tYk3VFAp.js.map +0 -1
  206. package/extensions/dashboard-client/dist/assets/HealthTab-D2_kjgxw.js +0 -2
  207. package/extensions/dashboard-client/dist/assets/HealthTab-D2_kjgxw.js.map +0 -1
  208. package/extensions/dashboard-client/dist/assets/MaintenanceTab-Bc-uPYrz.js +0 -2
  209. package/extensions/dashboard-client/dist/assets/MaintenanceTab-Bc-uPYrz.js.map +0 -1
  210. package/extensions/dashboard-client/dist/assets/MemoryMapTab-D1V9g_NC.js +0 -2
  211. package/extensions/dashboard-client/dist/assets/MemoryMapTab-D1V9g_NC.js.map +0 -1
  212. package/extensions/dashboard-client/dist/assets/MetricsTab-DT_XfL96.js +0 -2
  213. package/extensions/dashboard-client/dist/assets/MetricsTab-DT_XfL96.js.map +0 -1
  214. package/extensions/dashboard-client/dist/assets/OverviewTab-BY6KZCme.js +0 -2
  215. package/extensions/dashboard-client/dist/assets/OverviewTab-BY6KZCme.js.map +0 -1
  216. package/extensions/dashboard-client/dist/assets/ReposTab-CeS-tpDM.js +0 -2
  217. package/extensions/dashboard-client/dist/assets/ReposTab-CeS-tpDM.js.map +0 -1
  218. package/extensions/dashboard-client/dist/assets/SessionsTab-C30AwyoF.js +0 -70
  219. package/extensions/dashboard-client/dist/assets/SetupTab-BJUHOVK2.js +0 -2
  220. package/extensions/dashboard-client/dist/assets/SetupTab-BJUHOVK2.js.map +0 -1
  221. package/extensions/dashboard-client/dist/assets/TimeSavedCard-p55K1tFP.js +0 -2
  222. package/extensions/dashboard-client/dist/assets/TimeSavedCard-p55K1tFP.js.map +0 -1
  223. package/extensions/dashboard-client/dist/assets/TopicsTab-D8FqyAbK.js +0 -2
  224. package/extensions/dashboard-client/dist/assets/TopicsTab-D8FqyAbK.js.map +0 -1
  225. package/extensions/dashboard-client/dist/assets/TurnsTab-ju-nXDys.js +0 -2
  226. package/extensions/dashboard-client/dist/assets/TurnsTab-ju-nXDys.js.map +0 -1
  227. package/extensions/dashboard-client/dist/assets/index-BEj1Zahy.js +0 -42
  228. package/extensions/dashboard-client/dist/assets/index-BEj1Zahy.js.map +0 -1
  229. package/extensions/dashboard-client/dist/assets/index-CUNn22iV.css +0 -1
  230. package/extensions/dashboard-client/dist/assets/pricing-CvE4NllQ.js +0 -2
  231. package/extensions/dashboard-client/dist/assets/pricing-CvE4NllQ.js.map +0 -1
  232. package/extensions/dashboard-client/src/styles/metrics-extra.css +0 -69
  233. package/extensions/dashboard-client/src/styles/turns.css +0 -142
  234. package/extensions/dashboard-client/src/tabs/SetupTab/RagSettingsCard.tsx +0 -160
@@ -0,0 +1,126 @@
1
+ /**
2
+ * routes-wiki-helpers.ts — shared read/build helpers for routes-wiki.ts.
3
+ *
4
+ * Content access, index-entry resolution, provenance + time-bucket builders,
5
+ * lazy topic-model build, and SSE emission. Kept out of the dispatcher so
6
+ * routes-wiki.ts stays a thin pointer. Loopback-only (PREVENT-PI-004),
7
+ * parameterized (PREVENT-002).
8
+ */
9
+
10
+ import { appendFileSync } from "node:fs";
11
+ import type { DatabaseSync } from "node:sqlite";
12
+ import type { RouteContext } from "./routes-core.js";
13
+ import { openStore } from "../../src/store/sqlite.js";
14
+ import { createTopicStore } from "../../src/topics/store.js";
15
+ import { buildTopicModel } from "../../src/topics/cluster.js";
16
+ import { createWikiCuration } from "../../src/wiki/curation.js";
17
+ import type { TopicAssignment, Topic } from "../../src/topics/types.js";
18
+ import type {
19
+ WikiIndexEntry,
20
+ MemoryProvenance,
21
+ } from "./api-contracts/wiki.js";
22
+
23
+ /** Append a JSONL SSE line to events.log (best-effort, non-fatal). */
24
+ export function emitWikiEvent(
25
+ ctx: RouteContext,
26
+ event: { type: string } & Record<string, unknown>,
27
+ ): void {
28
+ try {
29
+ appendFileSync(
30
+ ctx.eventsPath,
31
+ JSON.stringify({ ts: new Date().toISOString(), ...event }) + "\n",
32
+ );
33
+ } catch {
34
+ /* non-fatal: SSE push best-effort */
35
+ }
36
+ }
37
+
38
+ /** Lazy-build the topic model when the topics table is empty (best-effort). */
39
+ export function ensureWikiBuilt(ctx: RouteContext, tdb: DatabaseSync): void {
40
+ const topicCount = (
41
+ tdb.prepare("SELECT COUNT(*) AS c FROM topics").get() as { c: number }
42
+ ).c;
43
+ if (topicCount > 0) return;
44
+ try {
45
+ const mainDb = openStore(ctx.stateDir);
46
+ const model = buildTopicModel(mainDb);
47
+ if (model.k > 0 && model.totalChunks > 0) {
48
+ createTopicStore(ctx.stateDir).replaceTopicModel(model);
49
+ }
50
+ } catch {
51
+ /* non-fatal: lazy wiki build is best-effort */
52
+ }
53
+ }
54
+
55
+ /** Build a topic index entry with resolved label + override badges. */
56
+ export function toIndexEntry(
57
+ topic: Topic,
58
+ curation: ReturnType<typeof createWikiCuration>,
59
+ ): WikiIndexEntry {
60
+ const resolved = curation.resolveLabel(topic.id, topic.label);
61
+ return {
62
+ id: topic.id,
63
+ label: resolved.label,
64
+ memoryCount: topic.memoryCount,
65
+ lastUpdated: topic.lastUpdated,
66
+ edited: resolved.edited,
67
+ overrideKinds: curation.overrideKinds(topic.id),
68
+ };
69
+ }
70
+
71
+ /** Convert a member assignment into contract provenance. */
72
+ export function toProvenance(member: TopicAssignment): MemoryProvenance {
73
+ return {
74
+ memoryId: member.memoryId,
75
+ sessionId: member.sessionId ?? "",
76
+ assignedAt: member.assignedAt,
77
+ method: member.method,
78
+ };
79
+ }
80
+
81
+ /** Bucket epoch-ms values by day; returns ascending [{bucket, count}]. */
82
+ export function bucketize(
83
+ values: number[],
84
+ ): Array<{ bucket: number; count: number }> {
85
+ const DAY = 86_400_000;
86
+ const counts = new Map<number, number>();
87
+ for (const v of values) {
88
+ if (!Number.isFinite(v) || v <= 0) continue;
89
+ const b = Math.floor(v / DAY) * DAY;
90
+ counts.set(b, (counts.get(b) ?? 0) + 1);
91
+ }
92
+ return [...counts.entries()]
93
+ .sort((a, b) => a[0] - b[0])
94
+ .map(([bucket, count]) => ({ bucket, count }));
95
+ }
96
+
97
+ /** Fetch member content (text + timestamp) from the main DB. */
98
+ export function memberContent(
99
+ ctx: RouteContext,
100
+ members: TopicAssignment[],
101
+ ): Map<string, { text: string; timestamp: number }> {
102
+ const out = new Map<string, { text: string; timestamp: number }>();
103
+ if (members.length === 0) return out;
104
+ try {
105
+ const mainDb = openStore(ctx.stateDir);
106
+ for (const m of members) {
107
+ const row = mainDb
108
+ .prepare(
109
+ `SELECT COALESCE(normalized_text, summary, topic_summary) AS text, timestamp
110
+ FROM context_chunks WHERE session_id = ? AND id = ?`,
111
+ )
112
+ .get(m.sessionId ?? "", m.memoryId) as
113
+ | { text: string | null; timestamp: number | null }
114
+ | undefined;
115
+ if (row) {
116
+ out.set(m.memoryId, {
117
+ text: (row.text ?? "").trim(),
118
+ timestamp: row.timestamp ?? 0,
119
+ });
120
+ }
121
+ }
122
+ } catch {
123
+ /* content fetch is best-effort */
124
+ }
125
+ return out;
126
+ }
@@ -0,0 +1,376 @@
1
+ /**
2
+ * dashboard-server/routes-wiki.ts — W2 wiki revival routes (curation + provenance).
3
+ *
4
+ * GET /api/wiki/index — topics with resolved labels
5
+ * GET /api/wiki/evolution — D3 nodes/edges/time buckets
6
+ * GET /api/wiki/topic/:topicId — single topic page + provenance
7
+ * GET /api/wiki/topic/:topicId/timeline — per-topic time buckets
8
+ * PUT /api/wiki/topic/:topicId/label — rename (emits wiki_topic_renamed)
9
+ * POST /api/wiki/merge — merge (emits wiki_topics_merged)
10
+ * POST /api/wiki/topic/:topicId/split — split (emits wiki_topic_split)
11
+ *
12
+ * Flag-gated by MEGACOMPACT_WIKI_ENHANCED (flag-OFF → 404 for all /api/wiki/*).
13
+ * Read/build helpers live in routes-wiki-helpers.ts (pointer-file split).
14
+ * Loopback-only (PREVENT-PI-004); parameterized (PREVENT-002); no `any`.
15
+ */
16
+
17
+ import type { IncomingMessage, ServerResponse } from "node:http";
18
+ import type { RouteContext } from "./routes-core.js";
19
+ import { TurnsConfig } from "../../src/config/turns.js";
20
+ import { openTurnStore } from "../../src/store/turns/connection.js";
21
+ import { createTopicStore } from "../../src/topics/store.js";
22
+ import { extractiveSummary } from "../../src/wiki.js";
23
+ import { createWikiCuration } from "../../src/wiki/curation.js";
24
+ import {
25
+ emitWikiEvent,
26
+ ensureWikiBuilt,
27
+ toIndexEntry,
28
+ toProvenance,
29
+ bucketize,
30
+ memberContent,
31
+ } from "./routes-wiki-helpers.js";
32
+ import type {
33
+ WikiIndexResponse,
34
+ WikiPageResponse,
35
+ TopicEvolutionResponse,
36
+ TopicTimelineResponse,
37
+ CurationResult,
38
+ } from "./api-contracts/wiki.js";
39
+
40
+ function sendJson(res: ServerResponse, status: number, body: unknown): void {
41
+ // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
42
+ res.writeHead(status, { "Content-Type": "application/json" });
43
+ res.end(JSON.stringify(body));
44
+ }
45
+
46
+ /** Decode a percent-encoded path segment; null on malformed encoding (%zz). */
47
+ function decodeSegment(raw: string): string | null {
48
+ try {
49
+ return decodeURIComponent(raw);
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+
55
+ /** Read a capped JSON body; returns { ok, value } or { error }. */
56
+ function readJsonBody(
57
+ req: IncomingMessage,
58
+ cb: (
59
+ result:
60
+ | { ok: true; value: Record<string, unknown> }
61
+ | { ok: false; error: string },
62
+ ) => void,
63
+ ): void {
64
+ let body = "";
65
+ let tooBig = false;
66
+ req.on("data", (chunk: Buffer) => {
67
+ if (body.length > 65536) {
68
+ tooBig = true;
69
+ return;
70
+ }
71
+ body += chunk.toString();
72
+ });
73
+ req.on("end", () => {
74
+ if (tooBig) return cb({ ok: false, error: "body_too_large" });
75
+ try {
76
+ const v = body ? JSON.parse(body) : {};
77
+ if (typeof v !== "object" || v === null || Array.isArray(v)) {
78
+ return cb({ ok: false, error: "invalid_object" });
79
+ }
80
+ cb({ ok: true, value: v as Record<string, unknown> });
81
+ } catch {
82
+ cb({ ok: false, error: "invalid_json" });
83
+ }
84
+ });
85
+ }
86
+
87
+ export function handleWiki(
88
+ req: IncomingMessage,
89
+ res: ServerResponse,
90
+ ctx: RouteContext,
91
+ ): boolean {
92
+ const url = req.url ?? "";
93
+ if (!url.startsWith("/api/wiki")) return false;
94
+
95
+ // Flag gate: feature-OFF → 404 for every wiki endpoint.
96
+ if (!TurnsConfig.WIKI_ENHANCED_ENABLED) {
97
+ sendJson(res, 404, { error: "wiki_enhanced_disabled" });
98
+ return true;
99
+ }
100
+
101
+ // ── GET /api/wiki/index ──────────────────────────────────────────
102
+ if (req.method === "GET" && url === "/api/wiki/index") {
103
+ try {
104
+ const tdb = openTurnStore(ctx.stateDir);
105
+ ensureWikiBuilt(ctx, tdb);
106
+ const store = createTopicStore(ctx.stateDir);
107
+ const curation = createWikiCuration(ctx.stateDir);
108
+ const entries = store.getTopics().map((t) => toIndexEntry(t, curation));
109
+ const totalMemories = entries.reduce((s, e) => s + e.memoryCount, 0);
110
+ const lastRebuildAt =
111
+ entries.length > 0 ? Math.max(...entries.map((e) => e.lastUpdated)) : null;
112
+ const body: WikiIndexResponse = {
113
+ topics: entries,
114
+ totalTopics: entries.length,
115
+ totalMemories,
116
+ lastRebuildAt,
117
+ };
118
+ sendJson(res, 200, body);
119
+ } catch (e) {
120
+ sendJson(res, 500, { error: String(e) });
121
+ }
122
+ return true;
123
+ }
124
+
125
+ // ── GET /api/wiki/evolution ──────────────────────────────────────
126
+ if (req.method === "GET" && url === "/api/wiki/evolution") {
127
+ try {
128
+ const tdb = openTurnStore(ctx.stateDir);
129
+ const curation = createWikiCuration(ctx.stateDir);
130
+ const nodes = createTopicStore(ctx.stateDir)
131
+ .getTopics()
132
+ .map((t) => ({
133
+ id: t.id,
134
+ label: curation.resolveLabel(t.id, t.label).label,
135
+ memoryCount: t.memoryCount,
136
+ }));
137
+ const overrides = tdb
138
+ .prepare(
139
+ `SELECT kind, topic_id, merged_into, split_from, overridden_at
140
+ FROM topic_overrides WHERE kind IN ('merge','split')`,
141
+ )
142
+ .all() as Array<{
143
+ kind: string;
144
+ topic_id: string;
145
+ merged_into: string | null;
146
+ split_from: string | null;
147
+ overridden_at: number;
148
+ }>;
149
+ const edges = overrides.flatMap(
150
+ (o): TopicEvolutionResponse["edges"][number][] => {
151
+ if (o.kind === "merge" && o.merged_into) {
152
+ return [
153
+ {
154
+ source: o.topic_id,
155
+ target: o.merged_into,
156
+ kind: "merge",
157
+ at: o.overridden_at,
158
+ },
159
+ ];
160
+ }
161
+ if (o.kind === "split" && o.split_from) {
162
+ return [
163
+ {
164
+ source: o.split_from,
165
+ target: o.topic_id,
166
+ kind: "split",
167
+ at: o.overridden_at,
168
+ },
169
+ ];
170
+ }
171
+ return [];
172
+ },
173
+ );
174
+ const evoRows = tdb
175
+ .prepare("SELECT assigned_at FROM topic_evolution")
176
+ .all() as Array<{ assigned_at: number }>;
177
+ const body: TopicEvolutionResponse = {
178
+ nodes,
179
+ edges,
180
+ buckets: bucketize(evoRows.map((r) => r.assigned_at)),
181
+ };
182
+ sendJson(res, 200, body);
183
+ } catch (e) {
184
+ sendJson(res, 500, { error: String(e) });
185
+ }
186
+ return true;
187
+ }
188
+
189
+ // ── GET /api/wiki/topic/:topicId/timeline ────────────────────────
190
+ const timelineMatch =
191
+ req.method === "GET"
192
+ ? url.match(/^\/api\/wiki\/topic\/([^/?]+)\/timeline$/)
193
+ : null;
194
+ if (timelineMatch) {
195
+ const topicId = decodeSegment(timelineMatch[1]);
196
+ if (topicId === null) {
197
+ sendJson(res, 400, { error: "malformed_topic_id" });
198
+ return true;
199
+ }
200
+ try {
201
+ const tdb = openTurnStore(ctx.stateDir);
202
+ const rows = tdb
203
+ .prepare("SELECT assigned_at FROM memory_topics WHERE topic_id = ?")
204
+ .all(topicId) as Array<{ assigned_at: number }>;
205
+ const body: TopicTimelineResponse = {
206
+ topicId,
207
+ buckets: bucketize(rows.map((r) => r.assigned_at)),
208
+ total: rows.length,
209
+ };
210
+ sendJson(res, 200, body);
211
+ } catch (e) {
212
+ sendJson(res, 500, { error: String(e) });
213
+ }
214
+ return true;
215
+ }
216
+
217
+ // ── GET /api/wiki/topic/:topicId ─────────────────────────────────
218
+ const topicMatch =
219
+ req.method === "GET" ? url.match(/^\/api\/wiki\/topic\/([^/?]+)$/) : null;
220
+ if (topicMatch) {
221
+ const topicId = decodeSegment(topicMatch[1]);
222
+ if (topicId === null) {
223
+ sendJson(res, 400, { error: "malformed_topic_id" });
224
+ return true;
225
+ }
226
+ try {
227
+ const store = createTopicStore(ctx.stateDir);
228
+ const topic = store.getTopics().find((t) => t.id === topicId);
229
+ if (!topic) {
230
+ sendJson(res, 404, { error: "topic_not_found" });
231
+ return true;
232
+ }
233
+ const members = store.getMemoriesForTopic(topicId, 200);
234
+ const content = memberContent(ctx, members);
235
+ const getText = (memId: string): string => content.get(memId)?.text ?? "";
236
+ const summary = extractiveSummary(topic, members, getText);
237
+ const present = members.filter((m) => content.has(m.memoryId));
238
+ const keyMemories = [...present]
239
+ .sort(
240
+ (a, b) =>
241
+ (content.get(b.memoryId)?.text.length ?? 0) -
242
+ (content.get(a.memoryId)?.text.length ?? 0),
243
+ )
244
+ .slice(0, 10)
245
+ .map((m) => ({
246
+ memoryId: m.memoryId,
247
+ content: content.get(m.memoryId)?.text ?? "",
248
+ timestamp: content.get(m.memoryId)?.timestamp ?? 0,
249
+ importance: Math.round(
250
+ (content.get(m.memoryId)?.text.length ?? 0) / 40,
251
+ ),
252
+ }));
253
+ const relatedTopicIds = store
254
+ .getTopics()
255
+ .filter((t) => t.id !== topicId && t.memoryCount > 0)
256
+ .slice(0, 3)
257
+ .map((t) => t.id);
258
+ const body: WikiPageResponse = {
259
+ topic: toIndexEntry(topic, createWikiCuration(ctx.stateDir)),
260
+ summary,
261
+ keyMemories,
262
+ provenance: members.map(toProvenance),
263
+ relatedTopicIds,
264
+ generatedAt: Date.now(),
265
+ };
266
+ sendJson(res, 200, body);
267
+ } catch (e) {
268
+ sendJson(res, 500, { error: String(e) });
269
+ }
270
+ return true;
271
+ }
272
+
273
+ // ── PUT /api/wiki/topic/:topicId/label ───────────────────────────
274
+ const renameMatch =
275
+ req.method === "PUT"
276
+ ? url.match(/^\/api\/wiki\/topic\/([^/?]+)\/label$/)
277
+ : null;
278
+ if (renameMatch) {
279
+ readJsonBody(req, (result) => {
280
+ if (!result.ok) return sendJson(res, 400, { error: result.error });
281
+ const topicId = decodeSegment(renameMatch[1]);
282
+ if (topicId === null) return sendJson(res, 400, { error: "malformed_topic_id" });
283
+ const label = typeof result.value.label === "string" ? result.value.label : "";
284
+ try {
285
+ const curation = createWikiCuration(ctx.stateDir);
286
+ const out = curation.renameTopic(topicId, label);
287
+ if (!out.ok) return sendJson(res, 404, { error: "topic_not_found" });
288
+ emitWikiEvent(ctx, {
289
+ type: "wiki_topic_renamed",
290
+ topicId,
291
+ label: out.label,
292
+ edited: out.edited,
293
+ });
294
+ sendJson(res, 200, out satisfies CurationResult);
295
+ } catch (e) {
296
+ sendJson(res, 500, { error: String(e) });
297
+ }
298
+ });
299
+ return true;
300
+ }
301
+
302
+ // ── POST /api/wiki/merge ─────────────────────────────────────────
303
+ if (req.method === "POST" && url === "/api/wiki/merge") {
304
+ readJsonBody(req, (result) => {
305
+ if (!result.ok) return sendJson(res, 400, { error: result.error });
306
+ const sourceTopicId =
307
+ typeof result.value.sourceTopicId === "string"
308
+ ? result.value.sourceTopicId
309
+ : "";
310
+ const targetTopicId =
311
+ typeof result.value.targetTopicId === "string"
312
+ ? result.value.targetTopicId
313
+ : "";
314
+ if (!sourceTopicId || !targetTopicId) {
315
+ return sendJson(res, 400, { error: "missing_source_or_target" });
316
+ }
317
+ try {
318
+ const curation = createWikiCuration(ctx.stateDir);
319
+ const out = curation.mergeTopics(sourceTopicId, targetTopicId);
320
+ if (!out.ok) {
321
+ return sendJson(res, 400, {
322
+ error:
323
+ sourceTopicId === targetTopicId
324
+ ? "cannot_merge_into_itself"
325
+ : "topic_not_found",
326
+ });
327
+ }
328
+ emitWikiEvent(ctx, {
329
+ type: "wiki_topics_merged",
330
+ sourceTopicId,
331
+ targetTopicId,
332
+ });
333
+ sendJson(res, 200, out satisfies CurationResult);
334
+ } catch (e) {
335
+ sendJson(res, 500, { error: String(e) });
336
+ }
337
+ });
338
+ return true;
339
+ }
340
+
341
+ // ── POST /api/wiki/topic/:topicId/split ──────────────────────────
342
+ const splitMatch =
343
+ req.method === "POST"
344
+ ? url.match(/^\/api\/wiki\/topic\/([^/?]+)\/split$/)
345
+ : null;
346
+ if (splitMatch) {
347
+ readJsonBody(req, (result) => {
348
+ if (!result.ok) return sendJson(res, 400, { error: result.error });
349
+ const topicId = decodeSegment(splitMatch[1]);
350
+ if (topicId === null) return sendJson(res, 400, { error: "malformed_topic_id" });
351
+ const raw = result.value.memoryIds;
352
+ const memoryIds = Array.isArray(raw)
353
+ ? raw.filter((m): m is string => typeof m === "string")
354
+ : [];
355
+ if (memoryIds.length === 0) {
356
+ return sendJson(res, 400, { error: "empty_memoryIds" });
357
+ }
358
+ try {
359
+ const curation = createWikiCuration(ctx.stateDir);
360
+ const out = curation.splitTopic(topicId, memoryIds);
361
+ if (!out.ok) return sendJson(res, 404, { error: "topic_not_found" });
362
+ emitWikiEvent(ctx, {
363
+ type: "wiki_topic_split",
364
+ fromTopicId: topicId,
365
+ toTopicId: out.topicId,
366
+ });
367
+ sendJson(res, 200, out satisfies CurationResult);
368
+ } catch (e) {
369
+ sendJson(res, 500, { error: String(e) });
370
+ }
371
+ });
372
+ return true;
373
+ }
374
+
375
+ return false;
376
+ }
@@ -28,3 +28,5 @@ export { handleCacheStripes } from "./routes-cache.js";
28
28
  export { handleContextHealth, handleCachePoison, handleHealthSettings } from "./routes-health.js";
29
29
  export { handleEmbedderHealth } from "./routes-embedder-health.js";
30
30
  export { handleRagSettings } from "./routes-rag-settings.js";
31
+ export { handleRagMetrics } from "./routes-rag-metrics.js";
32
+ export { handleWiki } from "./routes-wiki.js";
@@ -23,6 +23,7 @@ import { join, dirname } from "node:path";
23
23
  import { fileURLToPath } from "node:url";
24
24
  import { createRequire } from "node:module";
25
25
 
26
+ import { NEW_UI } from "../../src/config.js";
26
27
  import { log, setLogPath, setDashboardServerVersion } from "./state.js";
27
28
  import {
28
29
  buildRouteContext,
@@ -50,6 +51,8 @@ import {
50
51
  handleHealthSettings,
51
52
  handleEmbedderHealth,
52
53
  handleRagSettings,
54
+ handleRagMetrics,
55
+ handleWiki,
53
56
  handleStatic,
54
57
  } from "./routes.js";
55
58
 
@@ -119,6 +122,15 @@ export async function launchDashboardServer(
119
122
  const hasClientBuild = existsSync(clientIndexHtml);
120
123
  if (hasClientBuild) log("client build present", { clientDist });
121
124
 
125
+ function injectUiFlag(html: string): string {
126
+ const flag = NEW_UI() ? "true" : "false";
127
+ const script = `<script>window.MEGACOMPACT_NEW_UI=${flag}</script>`;
128
+ if (html.includes("</head>")) {
129
+ return html.replace("</head>", `${script}</head>`);
130
+ }
131
+ return html + script;
132
+ }
133
+
122
134
  // guardrails-allow PREVENT-PI-004: read-only static file serving from the local dashboard-client/dist bundle (loopback-only UI).
123
135
  const serveClientAsset = (reqPath: string, res: ServerResponse): boolean => {
124
136
  if (!hasClientBuild) return false;
@@ -131,7 +143,7 @@ export async function launchDashboardServer(
131
143
  if (!file.startsWith(clientDist) || !existsSync(file)) {
132
144
  // SPA fallback: unknown non-asset routes serve index.html (client-side routing).
133
145
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
134
- res.end(readFileSync(clientIndexHtml));
146
+ res.end(injectUiFlag(readFileSync(clientIndexHtml, "utf-8")));
135
147
  return true;
136
148
  }
137
149
  const ext = rel.slice(rel.lastIndexOf(".") + 1);
@@ -145,10 +157,12 @@ export async function launchDashboardServer(
145
157
  ico: "image/x-icon",
146
158
  map: "application/json",
147
159
  };
160
+ const payload: string | Uint8Array =
161
+ ext === "html" ? injectUiFlag(readFileSync(file, "utf-8")) : readFileSync(file);
148
162
  res.writeHead(200, {
149
163
  "Content-Type": types[ext] ?? "application/octet-stream",
150
164
  });
151
- res.end(readFileSync(file));
165
+ res.end(payload);
152
166
  return true;
153
167
  };
154
168
 
@@ -251,6 +265,8 @@ export async function launchDashboardServer(
251
265
  if (handleHealthSettings(req, res, ctx)) return;
252
266
  if (handleEmbedderHealth(req, res, ctx)) return;
253
267
  if (handleRagSettings(req, res, ctx)) return;
268
+ if (handleRagMetrics(req, res, ctx)) return;
269
+ if (handleWiki(req, res, ctx)) return;
254
270
  handleStatic(req, res, ctx);
255
271
  });
256
272
 
@@ -21,7 +21,10 @@ import {
21
21
  buildTopicModel,
22
22
  createTopicStore,
23
23
  bumpWikiCompactCounter,
24
+ applyOverridesAfterRebuild,
25
+ applyFullOverridesAfterRebuild,
24
26
  } from "../../../src/topics/index.js";
27
+ import { assignNewMemoriesIncremental } from "../../../src/wiki/index.js";
25
28
  import { TrigramEmbedder } from "../../../src/embedder.js";
26
29
  import type { EmbeddedChunk } from "../../../src/topics/types.js";
27
30
  import type { MegaConfig } from "../../mega-config.js";
@@ -89,7 +92,36 @@ export async function persistEpochAndMaintain(
89
92
  );
90
93
  const tdb = openTurnStore(runtime.currentStateDir);
91
94
  const n = bumpWikiCompactCounter(tdb);
92
- if (n % every === 0) {
95
+ // W5-B: on the Nth compact we full-rebuild; otherwise try the
96
+ // cheap incremental path (assign new memories to nearest
97
+ // centroid) and only force a full rebuild when the mean
98
+ // silhouette has degraded below the configured floor.
99
+ let rebuildNow = n % every === 0;
100
+ if (
101
+ !rebuildNow &&
102
+ TurnsConfig.WIKI_INCREMENTAL_ENABLED &&
103
+ TurnsConfig.WIKI_ENHANCED_ENABLED
104
+ ) {
105
+ const inc = assignNewMemoriesIncremental(
106
+ db,
107
+ runtime.currentStateDir,
108
+ );
109
+ if (
110
+ inc.silhouette !== null &&
111
+ inc.silhouette < TurnsConfig.WIKI_INCREMENTAL_SILHOUETTE_MIN
112
+ ) {
113
+ // Silhouette degraded — fall through to a full rebuild.
114
+ rebuildNow = true;
115
+ } else {
116
+ runtime.logger.info("wiki_incremental", {
117
+ assigned: inc.assigned,
118
+ pending: inc.pending,
119
+ silhouetteScore: inc.silhouette,
120
+ uncalibrated: false,
121
+ });
122
+ }
123
+ }
124
+ if (rebuildNow) {
93
125
  const model = buildTopicModel(db, {
94
126
  kRange: [TurnsConfig.WIKI_K_MIN, TurnsConfig.WIKI_K_MAX],
95
127
  labelTopTerms: TurnsConfig.WIKI_LABEL_TOP_TERMS,
@@ -99,6 +131,13 @@ export async function persistEpochAndMaintain(
99
131
  createTopicStore(runtime.currentStateDir).replaceTopicModel(
100
132
  model,
101
133
  );
134
+ // W5-A: full hybrid replay — label + merge + split overrides
135
+ // survive the rebuild (feature A, not just the label fast path).
136
+ applyFullOverridesAfterRebuild(tdb);
137
+ // SSE push so the dashboard Wiki/Evolution views refresh (non-fatal).
138
+ runtime.dashboard.event("wiki_rebuilt", {
139
+ topicCount: model.k,
140
+ });
102
141
  runtime.logger.info("wiki_rebuild", {
103
142
  clusterCount: model.k,
104
143
  totalChunks: model.totalChunks,
@@ -177,6 +216,10 @@ export async function persistEpochAndMaintain(
177
216
  createTopicStore(
178
217
  runtime.currentStateDir,
179
218
  ).replaceTopicModel(model);
219
+ // Re-stamp custom label overrides wiped by the seed rebuild.
220
+ applyOverridesAfterRebuild(
221
+ openTurnStore(runtime.currentStateDir),
222
+ );
180
223
  runtime.logger.info("wiki_seed", {
181
224
  clusterCount: model.k,
182
225
  sourceChunks: seedChunks.length,
@@ -66,6 +66,32 @@ export function doRecall(
66
66
  injected: result.toInject.length,
67
67
  empty: result.empty,
68
68
  });
69
+ if (config.ragRecallMetrics && result.hydeInfo) {
70
+ runtime.dashboard.event("hyde_executed", {
71
+ sessionId: sid,
72
+ ran: result.hydeInfo.ran,
73
+ skipped: result.hydeInfo.skipped,
74
+ reason: result.hydeInfo.reason,
75
+ hypotheticalDoc: result.hydeInfo.hypotheticalDoc.slice(0, 400),
76
+ generationMs: result.hydeInfo.generationMs,
77
+ rawHitCount: result.hydeInfo.rawHitCount,
78
+ hydeHitCount: result.hydeInfo.hydeHitCount,
79
+ fusedHitCount: result.hydeInfo.fusedHitCount,
80
+ lift: result.hydeInfo.lift,
81
+ });
82
+ }
83
+ if (config.ragRecallMetrics && result.recallMetrics) {
84
+ runtime.dashboard.event("recall_metrics", {
85
+ sessionId: sid,
86
+ hitCount: result.recallMetrics.hitCount,
87
+ score: result.recallMetrics.score,
88
+ pass: result.recallMetrics.pass,
89
+ relevance: result.recallMetrics.relevance,
90
+ coverage: result.recallMetrics.coverage,
91
+ diversity: result.recallMetrics.diversity,
92
+ specificity: result.recallMetrics.specificity,
93
+ });
94
+ }
69
95
  if (!result.empty && result.toInject.length > 0) {
70
96
  const top = result.toInject[0];
71
97
  const scorePct = Math.round((top.score ?? 0) * 100);
@@ -105,6 +131,8 @@ export function doRecall(
105
131
  turnIndex: runtime.currentTurn,
106
132
  role: "assistant",
107
133
  startedAt: Date.now(),
134
+ hyde: result.hydeInfo ?? undefined,
135
+ recallMetrics: result.recallMetrics ?? undefined,
108
136
  },
109
137
  runtime.currentStateDir,
110
138
  );
@@ -229,6 +257,9 @@ export async function doRecallAsync(
229
257
  ),
230
258
  block,
231
259
  empty: merged.length === 0,
260
+ // H1: merged cross-repo result reuses the same-repo pass's telemetry.
261
+ hydeInfo: sameRepo.hydeInfo,
262
+ recallMetrics: sameRepo.recallMetrics,
232
263
  };
233
264
  } catch {
234
265
  return sameRepo; // cross-repo failure → same-repo only (non-fatal)