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.
- package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +50 -0
- package/dist/extensions/dashboard-server/api-contracts/rag-metrics.js +1 -0
- package/dist/extensions/dashboard-server/api-contracts/rag-settings.js +3 -3
- package/dist/extensions/dashboard-server/api-contracts/wiki.js +1 -0
- package/dist/extensions/dashboard-server/index-reader.js +3 -1
- package/dist/extensions/dashboard-server/resolve-snapshot.js +81 -0
- package/dist/extensions/dashboard-server/routes-core.js +8 -3
- package/dist/extensions/dashboard-server/routes-rag-metrics.js +60 -0
- package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +167 -0
- package/dist/extensions/dashboard-server/routes-rag-settings.js +136 -77
- package/dist/extensions/dashboard-server/routes-repo.js +5 -1
- package/dist/extensions/dashboard-server/routes-sessions.js +6 -3
- package/dist/extensions/dashboard-server/routes-wiki-helpers.js +97 -0
- package/dist/extensions/dashboard-server/routes-wiki.js +330 -0
- package/dist/extensions/dashboard-server/routes.js +2 -0
- package/dist/extensions/dashboard-server/server.js +17 -3
- package/dist/extensions/mega-events/context-handler/afterCompact.js +35 -2
- package/dist/extensions/mega-pipeline/recall.js +31 -0
- package/dist/extensions/mega-runtime/capture-model.js +5 -2
- package/dist/extensions/mega-turn-store.js +2 -1
- package/dist/src/config/turns.js +10 -0
- package/dist/src/config.js +2 -0
- package/dist/src/pricing.js +8 -0
- package/dist/src/recall/async.js +10 -1
- package/dist/src/recall/format.js +10 -1
- package/dist/src/recall/hydeTelemetry.js +22 -0
- package/dist/src/recall/sync.js +28 -2
- package/dist/src/store/turns/hydeStore.js +32 -0
- package/dist/src/store/turns/index.js +1 -0
- package/dist/src/store/turns/schema.js +102 -1
- package/dist/src/store/turns/sqlite-store/rows.js +23 -0
- package/dist/src/store/turns/sqlite-store/write.js +7 -3
- package/dist/src/topics/durability.js +187 -0
- package/dist/src/topics/index.js +2 -1
- package/dist/src/topics/store.js +28 -9
- package/dist/src/wiki/curation-helpers.js +71 -0
- package/dist/src/wiki/curation.js +147 -0
- package/dist/src/wiki/incremental.js +126 -0
- package/dist/src/wiki/index.js +5 -0
- package/extensions/dashboard-client/dist/assets/AreaChart-j2VOSRp6.js +70 -0
- package/extensions/dashboard-client/dist/assets/{SessionsTab-C30AwyoF.js.map → AreaChart-j2VOSRp6.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/CacheTab-Dr1HeMfc.js +2 -0
- package/extensions/dashboard-client/dist/assets/CacheTab-Dr1HeMfc.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/EventsTab-DRG2k7FS.js +2 -0
- package/extensions/dashboard-client/dist/assets/EventsTab-DRG2k7FS.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/HealthTab-DGE9PpW3.js +2 -0
- package/extensions/dashboard-client/dist/assets/HealthTab-DGE9PpW3.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-DeYRhF4q.js +2 -0
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-DeYRhF4q.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-DSnSO3nG.js +2 -0
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-DSnSO3nG.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/MetricsTab-RIzNc1Uj.js +2 -0
- package/extensions/dashboard-client/dist/assets/MetricsTab-RIzNc1Uj.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/OverviewTab-_1BJ4Bmh.js +2 -0
- package/extensions/dashboard-client/dist/assets/OverviewTab-_1BJ4Bmh.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/ReposTab-DkU7ywZB.js +2 -0
- package/extensions/dashboard-client/dist/assets/ReposTab-DkU7ywZB.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/SessionsTab-Bq_F_aOk.js +2 -0
- package/extensions/dashboard-client/dist/assets/SessionsTab-Bq_F_aOk.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/SetupTab-BkBD2Y97.js +2 -0
- package/extensions/dashboard-client/dist/assets/SetupTab-BkBD2Y97.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/TimeSavedCard-BXzFKU7C.js +2 -0
- package/extensions/dashboard-client/dist/assets/TimeSavedCard-BXzFKU7C.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/TurnsTab-Bjpp3vBH.js +5 -0
- package/extensions/dashboard-client/dist/assets/TurnsTab-Bjpp3vBH.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/WikiTab-dBN2EA5G.js +2 -0
- package/extensions/dashboard-client/dist/assets/WikiTab-dBN2EA5G.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/button-Bt0b5QVp.js +2 -0
- package/extensions/dashboard-client/dist/assets/button-Bt0b5QVp.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/card-D5CafXtg.js +2 -0
- package/extensions/dashboard-client/dist/assets/card-D5CafXtg.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/index-CUA6EDRF.css +1 -0
- package/extensions/dashboard-client/dist/assets/index-GPHRnEN7.js +137 -0
- package/extensions/dashboard-client/dist/assets/index-GPHRnEN7.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/pricing-DkYp8-vi.js +2 -0
- package/extensions/dashboard-client/dist/assets/pricing-DkYp8-vi.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/switch-CY5Gwj1o.js +2 -0
- package/extensions/dashboard-client/dist/assets/switch-CY5Gwj1o.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/toggle-C9HpkIfH.js +2 -0
- package/extensions/dashboard-client/dist/assets/toggle-C9HpkIfH.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{useSSE-p1pOI_-P.js → useSSE-2JFrLDlE.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-p1pOI_-P.js.map → useSSE-2JFrLDlE.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +2 -2
- package/extensions/dashboard-client/package-lock.json +1995 -191
- package/extensions/dashboard-client/package.json +12 -1
- package/extensions/dashboard-client/postcss.config.js +6 -0
- package/extensions/dashboard-client/src/App.tsx +127 -88
- package/extensions/dashboard-client/src/api/client.ts +109 -10
- package/extensions/dashboard-client/src/components/ActiveReposTable.tsx +15 -7
- package/extensions/dashboard-client/src/components/ActiveSessionsTable.tsx +67 -55
- package/extensions/dashboard-client/src/components/CacheHitRateTrendCard.tsx +17 -13
- package/extensions/dashboard-client/src/components/CacheHitsCard.tsx +35 -30
- package/extensions/dashboard-client/src/components/CacheStatusPerModel.tsx +33 -28
- package/extensions/dashboard-client/src/components/EventCategoryFilter.tsx +5 -6
- package/extensions/dashboard-client/src/components/EventStream.tsx +17 -13
- package/extensions/dashboard-client/src/components/HydeDetailPanel.tsx +173 -0
- package/extensions/dashboard-client/src/components/LegendCard.tsx +16 -9
- package/extensions/dashboard-client/src/components/PerfCards.tsx +28 -20
- package/extensions/dashboard-client/src/components/PerfChart.tsx +9 -6
- package/extensions/dashboard-client/src/components/ProviderCacheCard.tsx +15 -7
- package/extensions/dashboard-client/src/components/RagDashboard.tsx +253 -0
- package/extensions/dashboard-client/src/components/RepoAllSessionsCard.tsx +15 -10
- package/extensions/dashboard-client/src/components/RepoContextStack.tsx +24 -16
- package/extensions/dashboard-client/src/components/RepoDetailModal.tsx +52 -49
- package/extensions/dashboard-client/src/components/RepoTable.tsx +26 -17
- package/extensions/dashboard-client/src/components/SessionContextGauges.tsx +11 -14
- package/extensions/dashboard-client/src/components/SessionInfo.tsx +20 -15
- package/extensions/dashboard-client/src/components/SessionsMemoryChart.tsx +8 -5
- package/extensions/dashboard-client/src/components/SummaryTiles.tsx +26 -17
- package/extensions/dashboard-client/src/components/TabBar.tsx +1 -3
- package/extensions/dashboard-client/src/components/layout/AppShell.tsx +42 -0
- package/extensions/dashboard-client/src/components/layout/BottomBar.tsx +88 -0
- package/extensions/dashboard-client/src/components/layout/Sidebar.tsx +83 -0
- package/extensions/dashboard-client/src/components/ui/badge.tsx +30 -0
- package/extensions/dashboard-client/src/components/ui/button.tsx +44 -0
- package/extensions/dashboard-client/src/components/ui/card.tsx +32 -0
- package/extensions/dashboard-client/src/components/ui/select.tsx +64 -0
- package/extensions/dashboard-client/src/components/ui/switch.tsx +26 -0
- package/extensions/dashboard-client/src/components/ui/tabs.tsx +36 -0
- package/extensions/dashboard-client/src/components/ui/toggle.tsx +28 -0
- package/extensions/dashboard-client/src/components/ui/tooltip.tsx +27 -0
- package/extensions/dashboard-client/src/config.ts +10 -0
- package/extensions/dashboard-client/src/main.tsx +2 -2
- package/extensions/dashboard-client/src/memory-map-layout.ts +1 -2
- package/extensions/dashboard-client/src/styles/index.css +79 -0
- package/extensions/dashboard-client/src/tabs/CacheTab.tsx +33 -26
- package/extensions/dashboard-client/src/tabs/EventsTab.tsx +22 -7
- package/extensions/dashboard-client/src/tabs/HealthTab.tsx +77 -46
- package/extensions/dashboard-client/src/tabs/MaintenanceTab/ActionsCard.tsx +60 -50
- package/extensions/dashboard-client/src/tabs/MaintenanceTab/DbStatsCard.tsx +61 -35
- package/extensions/dashboard-client/src/tabs/MaintenanceTab/DebugBundleCard.tsx +34 -23
- package/extensions/dashboard-client/src/tabs/MaintenanceTab/HealthMitigationCard.tsx +62 -43
- package/extensions/dashboard-client/src/tabs/MaintenanceTab/SchemaHealthCard.tsx +53 -32
- package/extensions/dashboard-client/src/tabs/MaintenanceTab.tsx +2 -2
- package/extensions/dashboard-client/src/tabs/MemoryMapTab/MemoryMapView.tsx +29 -46
- package/extensions/dashboard-client/src/tabs/MemoryMapTab/RaptorTreeView.tsx +10 -34
- package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +14 -34
- package/extensions/dashboard-client/src/tabs/MetricsTab.tsx +5 -2
- package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +11 -5
- package/extensions/dashboard-client/src/tabs/ReposTab.tsx +5 -5
- package/extensions/dashboard-client/src/tabs/SessionsTab.tsx +47 -33
- package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderSetup.tsx +6 -2
- package/extensions/dashboard-client/src/tabs/SetupTab/RagHealthCard.tsx +78 -0
- package/extensions/dashboard-client/src/tabs/SetupTab/SettingsPanel.tsx +198 -0
- package/extensions/dashboard-client/src/tabs/SetupTab/SettingsSection.tsx +50 -0
- package/extensions/dashboard-client/src/tabs/SetupTab.tsx +21 -59
- package/extensions/dashboard-client/src/tabs/TopicsTab.tsx +75 -40
- package/extensions/dashboard-client/src/tabs/TurnsTab.tsx +150 -112
- package/extensions/dashboard-client/src/tabs/WikiTab/TopicEvolutionGraph.tsx +274 -0
- package/extensions/dashboard-client/src/tabs/WikiTab/TopicEvolutionView.tsx +71 -0
- package/extensions/dashboard-client/src/tabs/WikiTab/TopicTimeline.tsx +103 -0
- package/extensions/dashboard-client/src/tabs/WikiTab/WikiPage.tsx +213 -0
- package/extensions/dashboard-client/src/tabs/WikiTab/WikiPageControls.tsx +188 -0
- package/extensions/dashboard-client/src/tabs/WikiTab.tsx +222 -0
- package/extensions/dashboard-client/src/tabs/registry.ts +57 -0
- package/extensions/dashboard-client/src/utils/cn.ts +7 -0
- package/extensions/dashboard-client/tailwind.config.js +46 -0
- package/extensions/dashboard-server/api-contracts/core.ts +59 -0
- package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +79 -0
- package/extensions/dashboard-server/api-contracts/index.ts +48 -2
- package/extensions/dashboard-server/api-contracts/rag-metrics.ts +22 -0
- package/extensions/dashboard-server/api-contracts/rag-settings.ts +30 -20
- package/extensions/dashboard-server/api-contracts/wiki.ts +138 -0
- package/extensions/dashboard-server/index-reader.ts +3 -1
- package/extensions/dashboard-server/resolve-snapshot.ts +98 -0
- package/extensions/dashboard-server/routes-core.ts +8 -3
- package/extensions/dashboard-server/routes-rag-metrics.ts +82 -0
- package/extensions/dashboard-server/routes-rag-settings-helpers.ts +314 -0
- package/extensions/dashboard-server/routes-rag-settings.ts +147 -97
- package/extensions/dashboard-server/routes-repo.ts +5 -1
- package/extensions/dashboard-server/routes-sessions.ts +6 -3
- package/extensions/dashboard-server/routes-wiki-helpers.ts +126 -0
- package/extensions/dashboard-server/routes-wiki.ts +376 -0
- package/extensions/dashboard-server/routes.ts +2 -0
- package/extensions/dashboard-server/server.ts +18 -2
- package/extensions/mega-events/context-handler/afterCompact.ts +44 -1
- package/extensions/mega-pipeline/recall.ts +31 -0
- package/extensions/mega-runtime/capture-model.ts +5 -2
- package/extensions/mega-turn-store.ts +13 -0
- package/package.json +1 -1
- package/src/config/turns.ts +17 -0
- package/src/config.ts +3 -0
- package/src/pricing.ts +8 -0
- package/src/recall/async.ts +10 -1
- package/src/recall/format.ts +15 -2
- package/src/recall/hydeTelemetry.ts +44 -0
- package/src/recall/sync.ts +34 -2
- package/src/recall/types.ts +36 -0
- package/src/store/turns/hydeStore.ts +82 -0
- package/src/store/turns/index.ts +4 -0
- package/src/store/turns/schema.ts +126 -1
- package/src/store/turns/sqlite-store/rows.ts +25 -0
- package/src/store/turns/sqlite-store/write.ts +20 -2
- package/src/store/turns/types.ts +33 -0
- package/src/topics/durability.ts +231 -0
- package/src/topics/index.ts +7 -1
- package/src/topics/store.ts +35 -9
- package/src/wiki/curation-helpers.ts +140 -0
- package/src/wiki/curation.ts +217 -0
- package/src/wiki/incremental.ts +164 -0
- package/src/wiki/index.ts +13 -0
- package/extensions/dashboard-client/dist/assets/CacheTab-Cqnc7lMg.js +0 -2
- package/extensions/dashboard-client/dist/assets/CacheTab-Cqnc7lMg.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/EventsTab-tYk3VFAp.js +0 -2
- package/extensions/dashboard-client/dist/assets/EventsTab-tYk3VFAp.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/HealthTab-D2_kjgxw.js +0 -2
- package/extensions/dashboard-client/dist/assets/HealthTab-D2_kjgxw.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-Bc-uPYrz.js +0 -2
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-Bc-uPYrz.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-D1V9g_NC.js +0 -2
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-D1V9g_NC.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/MetricsTab-DT_XfL96.js +0 -2
- package/extensions/dashboard-client/dist/assets/MetricsTab-DT_XfL96.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/OverviewTab-BY6KZCme.js +0 -2
- package/extensions/dashboard-client/dist/assets/OverviewTab-BY6KZCme.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/ReposTab-CeS-tpDM.js +0 -2
- package/extensions/dashboard-client/dist/assets/ReposTab-CeS-tpDM.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/SessionsTab-C30AwyoF.js +0 -70
- package/extensions/dashboard-client/dist/assets/SetupTab-BJUHOVK2.js +0 -2
- package/extensions/dashboard-client/dist/assets/SetupTab-BJUHOVK2.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/TimeSavedCard-p55K1tFP.js +0 -2
- package/extensions/dashboard-client/dist/assets/TimeSavedCard-p55K1tFP.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/TopicsTab-D8FqyAbK.js +0 -2
- package/extensions/dashboard-client/dist/assets/TopicsTab-D8FqyAbK.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/TurnsTab-ju-nXDys.js +0 -2
- package/extensions/dashboard-client/dist/assets/TurnsTab-ju-nXDys.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/index-BEj1Zahy.js +0 -42
- package/extensions/dashboard-client/dist/assets/index-BEj1Zahy.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/index-CUNn22iV.css +0 -1
- package/extensions/dashboard-client/dist/assets/pricing-CvE4NllQ.js +0 -2
- package/extensions/dashboard-client/dist/assets/pricing-CvE4NllQ.js.map +0 -1
- package/extensions/dashboard-client/src/styles/metrics-extra.css +0 -69
- package/extensions/dashboard-client/src/styles/turns.css +0 -142
- 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(
|
|
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
|
-
|
|
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)
|