pi-mega-compact 0.13.0 → 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.
- package/dist/extensions/dashboard-server/api-contracts/embedder-health.js +11 -0
- package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +225 -0
- package/dist/extensions/dashboard-server/api-contracts/endpoints/types.js +1 -0
- package/dist/extensions/dashboard-server/api-contracts/endpoints.js +5 -224
- package/dist/extensions/dashboard-server/api-contracts/raptor.js +8 -0
- package/dist/extensions/dashboard-server/api-contracts.test/_helpers.js +32 -0
- package/dist/extensions/dashboard-server/routes-cache.test/_helpers.js +48 -0
- package/dist/extensions/dashboard-server/routes-embedder-health.js +67 -0
- package/dist/extensions/dashboard-server/routes-raptor.js +88 -0
- package/dist/extensions/dashboard-server/routes-setup.js +1 -1
- package/dist/extensions/dashboard-server/routes.js +2 -0
- package/dist/extensions/dashboard-server/server.js +5 -1
- package/dist/extensions/mega-cache-replay.test/_helpers.js +175 -0
- package/dist/extensions/mega-commands/dataCommands.js +151 -0
- package/dist/extensions/mega-commands/helpers.js +61 -0
- package/dist/extensions/mega-commands/historyCommands.js +97 -0
- package/dist/extensions/mega-commands/setupCommand.js +200 -0
- package/dist/extensions/mega-commands.js +14 -499
- package/dist/extensions/mega-compact-s38.test/_helpers.js +194 -0
- package/dist/extensions/mega-compact.js +12 -2
- package/dist/extensions/mega-compact.test/_helpers.js +225 -0
- package/dist/extensions/mega-events/agent-handlers/agentEndHandler.js +211 -0
- package/dist/extensions/mega-events/agent-handlers/turnEndHandler/cacheStripe.js +49 -0
- package/dist/extensions/mega-events/agent-handlers/turnEndHandler/contextHealth.js +33 -0
- package/dist/extensions/mega-events/agent-handlers/turnEndHandler/errorRetry.js +387 -0
- package/dist/extensions/mega-events/agent-handlers/turnEndHandler/event.js +7 -0
- package/dist/extensions/mega-events/agent-handlers/turnEndHandler/gameScoring.js +55 -0
- package/dist/extensions/mega-events/agent-handlers/turnEndHandler/lengthStop.js +12 -0
- package/dist/extensions/mega-events/agent-handlers/turnEndHandler/memoryReview.js +22 -0
- package/dist/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.js +38 -0
- package/dist/extensions/mega-events/agent-handlers/turnEndHandler.js +36 -0
- package/dist/extensions/mega-events/agent-handlers.js +4 -799
- package/dist/extensions/mega-events/context-handler/afterCompact.js +163 -0
- package/dist/extensions/mega-events/context-handler/messageText.js +12 -0
- package/dist/extensions/mega-events/context-handler/tailResult.js +46 -0
- package/dist/extensions/mega-events/context-handler.js +11 -198
- package/dist/extensions/mega-events/separated-prompt.test/_helpers.js +25 -0
- package/dist/src/config.js +2 -0
- package/dist/src/dedup-engine.test/_helpers.js +55 -0
- package/dist/src/e2e.test/_helpers.js +52 -0
- package/dist/src/embedder.js +1 -0
- package/dist/src/httpEmbedder.js +6 -0
- package/dist/src/hyde.js +146 -0
- package/dist/src/importance.test/_helpers.js +5 -0
- package/dist/src/memoryGraph/sources/checkpoints.js +78 -0
- package/dist/src/memoryGraph/sources/edges.js +17 -0
- package/dist/src/memoryGraph/sources/flags.js +22 -0
- package/dist/src/memoryGraph/sources/helpers.js +14 -0
- package/dist/src/memoryGraph/sources/memories.js +88 -0
- package/dist/src/memoryGraph/sources/raptor.js +26 -0
- package/dist/src/memoryGraph/sources/turns.js +176 -0
- package/dist/src/memoryGraph/sources.js +15 -441
- package/dist/src/memoryGraph-gates.test/_helpers.js +120 -0
- package/dist/src/ratio.bench.test/_helpers.js +198 -0
- package/dist/src/recall/async.js +112 -0
- package/dist/src/recall/format.js +149 -0
- package/dist/src/recall/memory.js +62 -0
- package/dist/src/recall/reformulate.js +82 -0
- package/dist/src/recall/sync.js +169 -0
- package/dist/src/recall/types.js +1 -0
- package/dist/src/recall.js +13 -530
- package/dist/src/store/sqlite/schema/core.js +232 -0
- package/dist/src/store/sqlite/schema/game.js +65 -0
- package/dist/src/store/sqlite/schema/plan-v2.js +107 -0
- package/dist/src/store/sqlite/schema/turns.js +59 -0
- package/dist/src/store/sqlite/schema.js +8 -441
- package/dist/src/store/turns/contract-compliance.test/_helpers.js +21 -0
- package/dist/src/store/turns/sqlite-store/admin.js +171 -0
- package/dist/src/store/turns/sqlite-store/ctx.js +1 -0
- package/dist/src/store/turns/sqlite-store/read.js +111 -0
- package/dist/src/store/turns/sqlite-store/rows.js +42 -0
- package/dist/src/store/turns/sqlite-store/write.js +102 -0
- package/dist/src/store/turns/sqlite-store.js +31 -367
- package/dist/src/vectorStore/class.js +356 -0
- package/dist/src/vectorStore/hash.js +10 -0
- package/dist/src/vectorStore/types.js +2 -0
- package/dist/src/vectorStore.js +8 -359
- package/dist/src/vectorStore.test/_helpers.js +17 -0
- package/extensions/dashboard-client/dist/assets/{CacheTab-BZNnKhh5.js → CacheTab-DPRHjYZ0.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-BZNnKhh5.js.map → CacheTab-DPRHjYZ0.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-CzF5L42U.js → EventsTab-CgwYq-Qf.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-CzF5L42U.js.map → EventsTab-CgwYq-Qf.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-BLpidMrX.js → HealthTab-BgOCjVzR.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-BLpidMrX.js.map → HealthTab-BgOCjVzR.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-Yw1yFpsy.js +2 -0
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-Yw1yFpsy.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-yHUX7Yzi.js +2 -0
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-yHUX7Yzi.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{MetricsTab-B0PCysqs.js → MetricsTab-CcH9fP8P.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-B0PCysqs.js.map → MetricsTab-CcH9fP8P.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-DsOhqFu4.js → OverviewTab-BOs8ZNev.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-DsOhqFu4.js.map → OverviewTab-BOs8ZNev.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-B3YKYuOf.js → ReposTab-B-MfSPvg.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-B3YKYuOf.js.map → ReposTab-B-MfSPvg.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-B96FWcrC.js → SessionsTab-BmeN649Q.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-B96FWcrC.js.map → SessionsTab-BmeN649Q.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/SetupTab-nOknoph5.js +2 -0
- package/extensions/dashboard-client/dist/assets/SetupTab-nOknoph5.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BfkUVTvA.js → TimeSavedCard-DnPg0L0z.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BfkUVTvA.js.map → TimeSavedCard-DnPg0L0z.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TopicsTab-DLRsZmXJ.js → TopicsTab-BODHOg_b.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TopicsTab-DLRsZmXJ.js.map → TopicsTab-BODHOg_b.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-DKGU2Bf6.js → TurnsTab-BK0I7hKr.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-DKGU2Bf6.js.map → TurnsTab-BK0I7hKr.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-D-lPLRWB.js → index-mOlljTFI.js} +10 -10
- package/extensions/dashboard-client/dist/assets/index-mOlljTFI.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{useSSE-ClbbcgIp.js → useSSE-BYOtv1-3.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-ClbbcgIp.js.map → useSSE-BYOtv1-3.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/App.tsx +0 -4
- package/extensions/dashboard-client/src/api/client.ts +16 -0
- package/extensions/dashboard-client/src/tabs/MaintenanceTab/ActionsCard.tsx +178 -0
- package/extensions/dashboard-client/src/tabs/MaintenanceTab/DbStatsCard.tsx +140 -0
- package/extensions/dashboard-client/src/tabs/MaintenanceTab/DebugBundleCard.tsx +109 -0
- package/extensions/dashboard-client/src/tabs/MaintenanceTab/HealthMitigationCard.tsx +98 -0
- package/extensions/dashboard-client/src/tabs/MaintenanceTab/SchemaHealthCard.tsx +108 -0
- package/extensions/dashboard-client/src/tabs/MaintenanceTab.tsx +12 -598
- package/extensions/dashboard-client/src/tabs/MemoryMapTab/MemoryMapView.tsx +476 -0
- package/extensions/dashboard-client/src/tabs/MemoryMapTab/RaptorTreeView.tsx +163 -0
- package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +56 -466
- package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderHealthCard.tsx +109 -0
- package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderSetup.tsx +468 -0
- package/extensions/dashboard-client/src/tabs/SetupTab.tsx +23 -484
- package/extensions/dashboard-server/api-contracts/embedder-health.ts +32 -0
- package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +360 -0
- package/extensions/dashboard-server/api-contracts/endpoints/types.ts +350 -0
- package/extensions/dashboard-server/api-contracts/endpoints.ts +27 -648
- package/extensions/dashboard-server/api-contracts/index.ts +6 -0
- package/extensions/dashboard-server/api-contracts/raptor.ts +49 -0
- package/extensions/dashboard-server/api-contracts.test/_helpers.ts +47 -0
- package/extensions/dashboard-server/routes-cache.test/_helpers.ts +94 -0
- package/extensions/dashboard-server/routes-embedder-health.ts +77 -0
- package/extensions/dashboard-server/routes-raptor.ts +128 -0
- package/extensions/dashboard-server/routes-setup.ts +1 -1
- package/extensions/dashboard-server/routes.ts +2 -0
- package/extensions/dashboard-server/server.ts +4 -0
- package/extensions/mega-cache-replay.test/_helpers.ts +188 -0
- package/extensions/mega-commands/dataCommands.ts +169 -0
- package/extensions/mega-commands/helpers.ts +62 -0
- package/extensions/mega-commands/historyCommands.ts +117 -0
- package/extensions/mega-commands/setupCommand.ts +235 -0
- package/extensions/mega-commands.ts +25 -538
- package/extensions/mega-compact-s38.test/_helpers.ts +201 -0
- package/extensions/mega-compact.test/_helpers.ts +237 -0
- package/extensions/mega-compact.ts +12 -2
- package/extensions/mega-events/agent-handlers/agentEndHandler.ts +238 -0
- package/extensions/mega-events/agent-handlers/turnEndHandler/cacheStripe.ts +74 -0
- package/extensions/mega-events/agent-handlers/turnEndHandler/contextHealth.ts +50 -0
- package/extensions/mega-events/agent-handlers/turnEndHandler/errorRetry.ts +421 -0
- package/extensions/mega-events/agent-handlers/turnEndHandler/event.ts +16 -0
- package/extensions/mega-events/agent-handlers/turnEndHandler/gameScoring.ts +73 -0
- package/extensions/mega-events/agent-handlers/turnEndHandler/lengthStop.ts +29 -0
- package/extensions/mega-events/agent-handlers/turnEndHandler/memoryReview.ts +42 -0
- package/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.ts +61 -0
- package/extensions/mega-events/agent-handlers/turnEndHandler.ts +64 -0
- package/extensions/mega-events/agent-handlers.ts +15 -861
- package/extensions/mega-events/context-handler/afterCompact.ts +213 -0
- package/extensions/mega-events/context-handler/messageText.ts +20 -0
- package/extensions/mega-events/context-handler/tailResult.ts +63 -0
- package/extensions/mega-events/context-handler.ts +17 -235
- package/extensions/mega-events/separated-prompt.test/_helpers.ts +35 -0
- package/package.json +1 -1
- package/src/config.ts +4 -0
- package/src/dedup-engine.test/_helpers.ts +79 -0
- package/src/e2e.test/_helpers.ts +59 -0
- package/src/embedder.ts +4 -0
- package/src/httpEmbedder.ts +7 -0
- package/src/hyde.ts +162 -0
- package/src/importance.test/_helpers.ts +5 -0
- package/src/memoryGraph/sources/checkpoints.ts +99 -0
- package/src/memoryGraph/sources/edges.ts +23 -0
- package/src/memoryGraph/sources/flags.ts +26 -0
- package/src/memoryGraph/sources/helpers.ts +13 -0
- package/src/memoryGraph/sources/memories.ts +108 -0
- package/src/memoryGraph/sources/raptor.ts +37 -0
- package/src/memoryGraph/sources/turns.ts +226 -0
- package/src/memoryGraph/sources.ts +19 -544
- package/src/memoryGraph-gates.test/_helpers.ts +164 -0
- package/src/ratio.bench.test/_helpers.ts +268 -0
- package/src/recall/async.ts +142 -0
- package/src/recall/format.ts +173 -0
- package/src/recall/memory.ts +90 -0
- package/src/recall/reformulate.ts +111 -0
- package/src/recall/sync.ts +196 -0
- package/src/recall/types.ts +63 -0
- package/src/recall.ts +25 -712
- package/src/store/sqlite/schema/core.ts +233 -0
- package/src/store/sqlite/schema/game.ts +66 -0
- package/src/store/sqlite/schema/plan-v2.ts +108 -0
- package/src/store/sqlite/schema/turns.ts +60 -0
- package/src/store/sqlite/schema.ts +13 -441
- package/src/store/turns/contract-compliance.test/_helpers.ts +38 -0
- package/src/store/turns/sqlite-store/admin.ts +249 -0
- package/src/store/turns/sqlite-store/ctx.ts +14 -0
- package/src/store/turns/sqlite-store/read.ts +174 -0
- package/src/store/turns/sqlite-store/rows.ts +55 -0
- package/src/store/turns/sqlite-store/write.ts +193 -0
- package/src/store/turns/sqlite-store.ts +33 -514
- package/src/vectorStore/class.ts +433 -0
- package/src/vectorStore/hash.ts +11 -0
- package/src/vectorStore/types.ts +58 -0
- package/src/vectorStore.test/_helpers.ts +20 -0
- package/src/vectorStore.ts +13 -489
- package/extensions/dashboard-client/dist/assets/ConfigTab-DG0WkuuG.js +0 -2
- package/extensions/dashboard-client/dist/assets/ConfigTab-DG0WkuuG.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-BQF7W7xX.js +0 -2
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-BQF7W7xX.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-CPtHLv0A.js +0 -2
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-CPtHLv0A.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/SetupTab-hbzQ_NZv.js +0 -2
- package/extensions/dashboard-client/dist/assets/SetupTab-hbzQ_NZv.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/index-D-lPLRWB.js.map +0 -1
package/dist/src/hyde.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hyde.ts — Hypothetical Document Embeddings for LLM-backed recall (S43 re-plan).
|
|
3
|
+
*
|
|
4
|
+
* When an HttpEmbedder (localhost Ollama) is active and RAG_HYDE_ENABLED, we
|
|
5
|
+
* generate a hypothetical answer to the recall query via the Ollama /api/chat
|
|
6
|
+
* endpoint, embed it, and RRF-fuse the resulting search hits with the raw-query
|
|
7
|
+
* hits. This gives a stronger recall signal than TF-IDF term expansion (S57 B1)
|
|
8
|
+
* when an LLM is available. Non-fatal: any error → returns "" (caller uses raw-only).
|
|
9
|
+
*
|
|
10
|
+
* PREVENT-PI-004: reuses HttpEmbedder's loopback-child-process pattern (spawnSync
|
|
11
|
+
* with its own event loop). The remote opt-in (MEGACOMPACT_ALLOW_REMOTE_EMBEDDER)
|
|
12
|
+
* governs this too — derived chat URL is on the same host as the embedding URL.
|
|
13
|
+
*/
|
|
14
|
+
import { spawnSync } from "node:child_process"; // guardrails-allow PREVENT-PI-004: localhost-only LLM generation — reuses HttpEmbedder's loopback exception; governed by MEGACOMPACT_ALLOW_REMOTE_EMBEDDER
|
|
15
|
+
import { reciprocalRankFusion } from "./queryReformulation/rrf.js";
|
|
16
|
+
import { Logger } from "./log.js";
|
|
17
|
+
// Inline worker: POSTs to the derived Ollama /api/chat endpoint in a child
|
|
18
|
+
// process (its own event loop, so spawnSync blocks without a fetch deadlock).
|
|
19
|
+
const CHAT_WORKER = String.raw `
|
|
20
|
+
const u = process.env.MC_HYDE_URL, b = process.env.MC_HYDE_BODY;
|
|
21
|
+
try {
|
|
22
|
+
const r = await fetch(u, { method: "POST", headers: { "content-type": "application/json" }, body: b }); // guardrails-allow PREVENT-PI-004: localhost-only /api/chat — same host as BYO embedding endpoint; remote only via MEGACOMPACT_ALLOW_REMOTE_EMBEDDER
|
|
23
|
+
const out = JSON.stringify({ status: r.status, ok: r.ok, text: await r.text() });
|
|
24
|
+
process.stdout.write(out);
|
|
25
|
+
} catch (e) {
|
|
26
|
+
process.stdout.write(JSON.stringify({ error: String(e && e.message ? e.message : e) }));
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
29
|
+
/**
|
|
30
|
+
* Generate a hypothetical answer document for a recall query via the LLM-hosted
|
|
31
|
+
* /api/chat endpoint (HttpEmbedder only). Returns "" when the embedder has no
|
|
32
|
+
* LLM chat surface (TrigramEmbedder) or on ANY error. Non-fatal — the caller
|
|
33
|
+
* falls back to raw-query recall only.
|
|
34
|
+
*/
|
|
35
|
+
/** Best-effort structured failure log. Never throws (PyDE is non-fatal). */
|
|
36
|
+
function logHydeFailure(reason) {
|
|
37
|
+
try {
|
|
38
|
+
new Logger().warn("hyde_failed", { reason });
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
/* ignore — logging must never break recall */
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export function generateHypotheticalDoc(query, embedder) {
|
|
45
|
+
if (embedder.kind !== "http")
|
|
46
|
+
return ""; // TrigramEmbedder: no LLM surface
|
|
47
|
+
const url = embedder.chatUrl;
|
|
48
|
+
if (!url) {
|
|
49
|
+
logHydeFailure("no chatUrl");
|
|
50
|
+
return "";
|
|
51
|
+
}
|
|
52
|
+
const model = process.env.MEGACOMPACT_HYDE_MODEL ?? "llama3.2";
|
|
53
|
+
const content = "Answer this question about the codebase concisely (2-3 sentences): " + query;
|
|
54
|
+
const body = JSON.stringify({
|
|
55
|
+
model,
|
|
56
|
+
messages: [{ role: "user", content }],
|
|
57
|
+
stream: false,
|
|
58
|
+
});
|
|
59
|
+
let res;
|
|
60
|
+
try {
|
|
61
|
+
res = spawnSync(process.execPath, ["-e", CHAT_WORKER], {
|
|
62
|
+
encoding: "utf8",
|
|
63
|
+
env: { ...process.env, MC_HYDE_URL: url, MC_HYDE_BODY: body },
|
|
64
|
+
timeout: 20000,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
logHydeFailure("spawn-error");
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
if (res.error ||
|
|
72
|
+
typeof res.stdout !== "string" ||
|
|
73
|
+
res.stdout.length === 0 ||
|
|
74
|
+
res.status !== 0) {
|
|
75
|
+
logHydeFailure("child-error");
|
|
76
|
+
return "";
|
|
77
|
+
}
|
|
78
|
+
let parsed;
|
|
79
|
+
try {
|
|
80
|
+
parsed = JSON.parse(res.stdout);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
logHydeFailure("non-json");
|
|
84
|
+
return "";
|
|
85
|
+
}
|
|
86
|
+
if (parsed.error || !parsed.ok || (parsed.status ?? 0) >= 400) {
|
|
87
|
+
logHydeFailure("chat-http");
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
// Ollama /api/chat (stream:false) returns { message: { role, content } }.
|
|
91
|
+
let messageBody;
|
|
92
|
+
try {
|
|
93
|
+
if (typeof parsed.text === "string")
|
|
94
|
+
messageBody = JSON.parse(parsed.text);
|
|
95
|
+
else {
|
|
96
|
+
logHydeFailure("empty-body");
|
|
97
|
+
return "";
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
logHydeFailure("non-json-body");
|
|
102
|
+
return "";
|
|
103
|
+
}
|
|
104
|
+
if (!messageBody || typeof messageBody !== "object") {
|
|
105
|
+
logHydeFailure("bad-body");
|
|
106
|
+
return "";
|
|
107
|
+
}
|
|
108
|
+
const msg = messageBody.message;
|
|
109
|
+
if (!msg || typeof msg !== "object") {
|
|
110
|
+
logHydeFailure("no-message");
|
|
111
|
+
return "";
|
|
112
|
+
}
|
|
113
|
+
const contentOut = msg.content;
|
|
114
|
+
if (typeof contentOut !== "string" || contentOut.length === 0) {
|
|
115
|
+
logHydeFailure("empty-content");
|
|
116
|
+
return "";
|
|
117
|
+
}
|
|
118
|
+
return contentOut;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* RRF-fuse two recall hit lists (raw-query and hypothetical-doc) into a single
|
|
122
|
+
* deduplicated ranking, sliced to `limit`. Order is by fused RRF score.
|
|
123
|
+
*/
|
|
124
|
+
export function fuseRecallHits(raw, hyde, limit) {
|
|
125
|
+
const byId = new Map();
|
|
126
|
+
const rawRanked = raw.map((h, i) => {
|
|
127
|
+
byId.set(h.checkpoint.checkpointId, h);
|
|
128
|
+
return { id: h.checkpoint.checkpointId, rank: i + 1 };
|
|
129
|
+
});
|
|
130
|
+
const hydeRanked = hyde.map((h, i) => {
|
|
131
|
+
if (!byId.has(h.checkpoint.checkpointId)) {
|
|
132
|
+
byId.set(h.checkpoint.checkpointId, h);
|
|
133
|
+
}
|
|
134
|
+
return { id: h.checkpoint.checkpointId, rank: i + 1 };
|
|
135
|
+
});
|
|
136
|
+
const fused = reciprocalRankFusion([rawRanked, hydeRanked]);
|
|
137
|
+
const out = [];
|
|
138
|
+
for (const item of fused) {
|
|
139
|
+
const hit = byId.get(item.id);
|
|
140
|
+
if (hit)
|
|
141
|
+
out.push(hit);
|
|
142
|
+
if (out.length >= limit)
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { decodeEmbedding } from "../../store/sqlite/utils.js";
|
|
2
|
+
import { cosineSimilarity } from "../embedding.js";
|
|
3
|
+
import { safeJsonCount } from "./helpers.js";
|
|
4
|
+
export function buildCheckpointNodes(db, sessionId, nodes, edges) {
|
|
5
|
+
const rows = sessionId
|
|
6
|
+
? db
|
|
7
|
+
.prepare(`SELECT id, session_id, summary, token_estimate, timestamp,
|
|
8
|
+
dedup_status, topic_summary, key_decisions,
|
|
9
|
+
normalized_text, embedding_blob
|
|
10
|
+
FROM context_chunks
|
|
11
|
+
WHERE session_id = ?
|
|
12
|
+
ORDER BY timestamp ASC`)
|
|
13
|
+
.all(sessionId)
|
|
14
|
+
: db
|
|
15
|
+
.prepare(`SELECT id, session_id, summary, token_estimate, timestamp,
|
|
16
|
+
dedup_status, topic_summary, key_decisions,
|
|
17
|
+
normalized_text, embedding_blob
|
|
18
|
+
FROM context_chunks
|
|
19
|
+
ORDER BY timestamp ASC`)
|
|
20
|
+
.all();
|
|
21
|
+
if (rows.length === 0)
|
|
22
|
+
return;
|
|
23
|
+
let prevId = null;
|
|
24
|
+
for (const row of rows) {
|
|
25
|
+
const id = String(row.id);
|
|
26
|
+
const rowSessionId = sessionId || String(row.session_id ?? "");
|
|
27
|
+
const summary = String(row.summary ?? "");
|
|
28
|
+
const node = {
|
|
29
|
+
id,
|
|
30
|
+
sessionId: rowSessionId,
|
|
31
|
+
label: id,
|
|
32
|
+
summaryTruncated: summary.length > 200 ? summary.slice(0, 197) + "..." : summary,
|
|
33
|
+
tokenEstimate: Number(row.token_estimate ?? 0),
|
|
34
|
+
timestamp: Number(row.timestamp ?? Date.now()),
|
|
35
|
+
dedupStatus: row.dedup_status ? String(row.dedup_status) : undefined,
|
|
36
|
+
raptorLevel: 0,
|
|
37
|
+
topicSummary: row.topic_summary ? String(row.topic_summary) : undefined,
|
|
38
|
+
decisionCount: row.key_decisions ? safeJsonCount(String(row.key_decisions)) : 0,
|
|
39
|
+
textSnippet: summary.length > 200 ? summary.slice(0, 197) + "..." : summary,
|
|
40
|
+
nodeType: "checkpoint",
|
|
41
|
+
};
|
|
42
|
+
nodes.push(node);
|
|
43
|
+
if (prevId !== null) {
|
|
44
|
+
edges.push({ source: prevId, target: id, weight: 1.0, type: "temporal" });
|
|
45
|
+
}
|
|
46
|
+
prevId = id;
|
|
47
|
+
}
|
|
48
|
+
addCheckpointSemanticEdges(rows, edges);
|
|
49
|
+
}
|
|
50
|
+
function addCheckpointSemanticEdges(rows, edges) {
|
|
51
|
+
const embeddings = [];
|
|
52
|
+
for (const row of rows) {
|
|
53
|
+
const blob = row.embedding_blob;
|
|
54
|
+
embeddings.push({
|
|
55
|
+
id: String(row.id),
|
|
56
|
+
blob: blob instanceof Buffer ? blob : null,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
for (let i = 0; i < embeddings.length; i++) {
|
|
60
|
+
if (!embeddings[i].blob)
|
|
61
|
+
continue;
|
|
62
|
+
const a = decodeEmbedding(embeddings[i].blob);
|
|
63
|
+
for (let j = i + 1; j < embeddings.length; j++) {
|
|
64
|
+
if (!embeddings[j].blob)
|
|
65
|
+
continue;
|
|
66
|
+
const b = decodeEmbedding(embeddings[j].blob);
|
|
67
|
+
const sim = cosineSimilarity(a, b);
|
|
68
|
+
if (sim > 0) {
|
|
69
|
+
edges.push({
|
|
70
|
+
source: embeddings[i].id,
|
|
71
|
+
target: embeddings[j].id,
|
|
72
|
+
weight: sim,
|
|
73
|
+
type: "semantic",
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function deduplicateEdges(edges) {
|
|
2
|
+
const seen = new Map();
|
|
3
|
+
for (const e of edges) {
|
|
4
|
+
const keyA = `${e.source}|${e.target}`;
|
|
5
|
+
const keyB = `${e.target}|${e.source}`;
|
|
6
|
+
const existing = seen.get(keyA) ?? seen.get(keyB);
|
|
7
|
+
if (existing) {
|
|
8
|
+
if (e.weight > existing.weight) {
|
|
9
|
+
seen.set(keyA, e);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
seen.set(keyA, e);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return [...seen.values()];
|
|
17
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* flags.ts — memory-graph feature-flag / env helpers (extracted from sources.ts).
|
|
3
|
+
*/
|
|
4
|
+
export function flagEnabled(name, def) {
|
|
5
|
+
const v = process.env[name];
|
|
6
|
+
if (v === undefined)
|
|
7
|
+
return def;
|
|
8
|
+
return v === "true" || v === "1";
|
|
9
|
+
}
|
|
10
|
+
export function areTurnsEnabled() {
|
|
11
|
+
return flagEnabled("MEGACOMPACT_MEMORY_GRAPH_SEED_TURNS", true);
|
|
12
|
+
}
|
|
13
|
+
export function isTurnContentEnabled() {
|
|
14
|
+
return flagEnabled("MEGACOMPACT_MEMORY_GRAPH_SEED_TURN_CONTENT", true) &&
|
|
15
|
+
flagEnabled("MEGACOMPACT_DB_MIRROR", false);
|
|
16
|
+
}
|
|
17
|
+
export function isTurnContentFlaggedOn() {
|
|
18
|
+
return flagEnabled("MEGACOMPACT_MEMORY_GRAPH_SEED_TURN_CONTENT", true);
|
|
19
|
+
}
|
|
20
|
+
export function areMemoriesEnabled() {
|
|
21
|
+
return flagEnabled("MEGACOMPACT_MEMORY_GRAPH_SEED_MEMORIES", true);
|
|
22
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* helpers.ts — shared micro-helpers (extracted from sources.ts).
|
|
3
|
+
*/
|
|
4
|
+
export function safeJsonCount(raw) {
|
|
5
|
+
if (!raw || typeof raw !== "string")
|
|
6
|
+
return 0;
|
|
7
|
+
try {
|
|
8
|
+
const arr = JSON.parse(raw);
|
|
9
|
+
return Array.isArray(arr) ? arr.length : 0;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return 0;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { getOrComputeEmbedding, cosineSimilarity } from "../embedding.js";
|
|
2
|
+
export function buildMemoryNodes(db, sessionId, nodes, edges) {
|
|
3
|
+
const existingIds = new Set(nodes.map((n) => n.id));
|
|
4
|
+
const rows = db
|
|
5
|
+
.prepare(`SELECT id, content, source_turn, created_at
|
|
6
|
+
FROM memories
|
|
7
|
+
ORDER BY created_at ASC`)
|
|
8
|
+
.all();
|
|
9
|
+
if (rows.length === 0)
|
|
10
|
+
return;
|
|
11
|
+
const memoryNodes = [];
|
|
12
|
+
for (const row of rows) {
|
|
13
|
+
const id = `mem:${String(row.id)}`;
|
|
14
|
+
if (existingIds.has(id))
|
|
15
|
+
continue;
|
|
16
|
+
existingIds.add(id);
|
|
17
|
+
const content = row.content ? String(row.content) : "";
|
|
18
|
+
const snippet = content.length > 200 ? content.slice(0, 197) + "..." : content;
|
|
19
|
+
const node = {
|
|
20
|
+
id,
|
|
21
|
+
sessionId,
|
|
22
|
+
label: `Memory ${String(row.id)}`,
|
|
23
|
+
summaryTruncated: snippet,
|
|
24
|
+
tokenEstimate: content.length,
|
|
25
|
+
timestamp: row.created_at ? Number(row.created_at) : Date.now(),
|
|
26
|
+
dedupStatus: undefined,
|
|
27
|
+
raptorLevel: 0,
|
|
28
|
+
topicSummary: undefined,
|
|
29
|
+
decisionCount: 0,
|
|
30
|
+
textSnippet: snippet,
|
|
31
|
+
nodeType: "memory",
|
|
32
|
+
epochId: undefined,
|
|
33
|
+
};
|
|
34
|
+
memoryNodes.push(node);
|
|
35
|
+
}
|
|
36
|
+
if (memoryNodes.length > 1) {
|
|
37
|
+
const embeddings = memoryNodes.map((n) => {
|
|
38
|
+
try {
|
|
39
|
+
return { id: n.id, vec: getOrComputeEmbedding(db, n.textSnippet) };
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return { id: n.id, vec: null };
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
for (let i = 0; i < embeddings.length; i++) {
|
|
46
|
+
if (!embeddings[i].vec)
|
|
47
|
+
continue;
|
|
48
|
+
for (let j = i + 1; j < embeddings.length; j++) {
|
|
49
|
+
if (!embeddings[j].vec)
|
|
50
|
+
continue;
|
|
51
|
+
const sim = cosineSimilarity(embeddings[i].vec, embeddings[j].vec);
|
|
52
|
+
if (sim > 0) {
|
|
53
|
+
edges.push({
|
|
54
|
+
source: embeddings[i].id,
|
|
55
|
+
target: embeddings[j].id,
|
|
56
|
+
weight: sim,
|
|
57
|
+
type: "semantic",
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
crossLinkMemoryToCheckpoints(memoryNodes, nodes, edges);
|
|
63
|
+
}
|
|
64
|
+
nodes.push(...memoryNodes);
|
|
65
|
+
}
|
|
66
|
+
function crossLinkMemoryToCheckpoints(memoryNodes, allNodes, edges) {
|
|
67
|
+
for (const mn of memoryNodes) {
|
|
68
|
+
let bestNeighbor = null;
|
|
69
|
+
for (const n of allNodes) {
|
|
70
|
+
if (n.nodeType !== "checkpoint" && n.nodeType !== "turn-content")
|
|
71
|
+
continue;
|
|
72
|
+
if (n.id === mn.id)
|
|
73
|
+
continue;
|
|
74
|
+
const diff = Math.abs(mn.timestamp - n.timestamp);
|
|
75
|
+
if (bestNeighbor === null || diff < bestNeighbor.diff) {
|
|
76
|
+
bestNeighbor = { id: n.id, diff };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (bestNeighbor) {
|
|
80
|
+
edges.push({
|
|
81
|
+
source: mn.id,
|
|
82
|
+
target: bestNeighbor.id,
|
|
83
|
+
weight: 0.85,
|
|
84
|
+
type: "semantic",
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { listRaptorNodes } from "../../store/sqlite/raptor.js";
|
|
2
|
+
export function addRaptorAnnotations(sessionId, stateDir, nodes, edges) {
|
|
3
|
+
const raptorNodes = listRaptorNodes(sessionId, stateDir);
|
|
4
|
+
if (!raptorNodes || raptorNodes.length === 0)
|
|
5
|
+
return;
|
|
6
|
+
const nodeIndex = new Map();
|
|
7
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
8
|
+
nodeIndex.set(nodes[i].id, i);
|
|
9
|
+
}
|
|
10
|
+
for (const rn of raptorNodes) {
|
|
11
|
+
const idx = nodeIndex.get(rn.id);
|
|
12
|
+
if (idx !== undefined) {
|
|
13
|
+
nodes[idx].raptorLevel = rn.level;
|
|
14
|
+
}
|
|
15
|
+
if (!rn.parentId)
|
|
16
|
+
continue;
|
|
17
|
+
if (nodeIndex.has(rn.id) && nodeIndex.has(rn.parentId)) {
|
|
18
|
+
edges.push({
|
|
19
|
+
source: rn.id,
|
|
20
|
+
target: rn.parentId,
|
|
21
|
+
weight: Math.max(0.1, 1.0 - rn.level * 0.1),
|
|
22
|
+
type: "raptor_parent",
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { getOrComputeEmbedding, cosineSimilarity } from "../embedding.js";
|
|
2
|
+
export function buildTurnNodes(db, sessionId, nodes, edges) {
|
|
3
|
+
const existingIds = new Set(nodes.map((n) => n.id));
|
|
4
|
+
const rows = sessionId
|
|
5
|
+
? db
|
|
6
|
+
.prepare(`SELECT turn_index, session_id, role, pressure_band, ctx_tokens, ctx_percent,
|
|
7
|
+
epoch_id, ended_at
|
|
8
|
+
FROM turns
|
|
9
|
+
WHERE session_id = ?
|
|
10
|
+
ORDER BY turn_index ASC`)
|
|
11
|
+
.all(sessionId)
|
|
12
|
+
: db
|
|
13
|
+
.prepare(`SELECT turn_index, session_id, role, pressure_band, ctx_tokens, ctx_percent,
|
|
14
|
+
epoch_id, ended_at
|
|
15
|
+
FROM turns
|
|
16
|
+
ORDER BY turn_index ASC`)
|
|
17
|
+
.all();
|
|
18
|
+
if (rows.length === 0)
|
|
19
|
+
return;
|
|
20
|
+
let prevId = null;
|
|
21
|
+
for (const row of rows) {
|
|
22
|
+
const ti = Number(row.turn_index);
|
|
23
|
+
const rowSessionId = sessionId || String(row.session_id ?? "");
|
|
24
|
+
const nodeId = `turn:${rowSessionId}:${ti}`;
|
|
25
|
+
if (existingIds.has(nodeId))
|
|
26
|
+
continue;
|
|
27
|
+
existingIds.add(nodeId);
|
|
28
|
+
const role = row.role ? String(row.role) : "unknown";
|
|
29
|
+
const endedAt = row.ended_at ? Number(row.ended_at) : Date.now();
|
|
30
|
+
const node = {
|
|
31
|
+
id: nodeId,
|
|
32
|
+
sessionId: rowSessionId,
|
|
33
|
+
label: `Turn ${ti}`,
|
|
34
|
+
summaryTruncated: `Turn ${ti}: ${role}${row.epoch_id ? ` (epoch: ${String(row.epoch_id)})` : ""}`,
|
|
35
|
+
tokenEstimate: row.ctx_tokens ? Number(row.ctx_tokens) : 0,
|
|
36
|
+
timestamp: endedAt,
|
|
37
|
+
dedupStatus: undefined,
|
|
38
|
+
raptorLevel: 0,
|
|
39
|
+
topicSummary: undefined,
|
|
40
|
+
decisionCount: 0,
|
|
41
|
+
textSnippet: `Turn ${ti}: ${role} — ${String(row.ctx_tokens ?? "?")} tokens`,
|
|
42
|
+
nodeType: "turn",
|
|
43
|
+
epochId: row.epoch_id ? String(row.epoch_id) : undefined,
|
|
44
|
+
};
|
|
45
|
+
nodes.push(node);
|
|
46
|
+
if (prevId !== null) {
|
|
47
|
+
edges.push({ source: prevId, target: nodeId, weight: 1.0, type: "temporal" });
|
|
48
|
+
}
|
|
49
|
+
prevId = nodeId;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function buildTurnContentNodes(turnDb, mainDb, sessionId, nodes, edges) {
|
|
53
|
+
// Local dedup only — DON'T skip ids from Source A (turn-structural). Source A
|
|
54
|
+
// and Source B share the same node id (turn:<session>:<idx>); Gate 2
|
|
55
|
+
// (identity_merge) merges them with the richest nodeType winning. Using the
|
|
56
|
+
// global nodes array here would skip every node (Source A already added them).
|
|
57
|
+
const seen = new Set();
|
|
58
|
+
const turnRows = sessionId
|
|
59
|
+
? turnDb
|
|
60
|
+
.prepare(`SELECT turn_index, role, ended_at
|
|
61
|
+
FROM turns
|
|
62
|
+
WHERE session_id = ?
|
|
63
|
+
ORDER BY turn_index ASC`)
|
|
64
|
+
.all(sessionId)
|
|
65
|
+
: turnDb
|
|
66
|
+
.prepare(`SELECT turn_index, role, ended_at
|
|
67
|
+
FROM turns
|
|
68
|
+
ORDER BY turn_index ASC`)
|
|
69
|
+
.all();
|
|
70
|
+
// Build a map of (session_id, turn_index) → content from raw_transcript.
|
|
71
|
+
const contentMap = new Map();
|
|
72
|
+
try {
|
|
73
|
+
const transcriptRows = sessionId
|
|
74
|
+
? mainDb
|
|
75
|
+
.prepare(`SELECT turn_index, content_bytes FROM raw_transcript WHERE session_id = ?`)
|
|
76
|
+
.all(sessionId)
|
|
77
|
+
: mainDb
|
|
78
|
+
.prepare(`SELECT turn_index, content_bytes FROM raw_transcript`)
|
|
79
|
+
.all();
|
|
80
|
+
for (const r of transcriptRows) {
|
|
81
|
+
contentMap.set(String(r.turn_index), String(r.content_bytes ?? ""));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// raw_transcript may not exist in the main db (dbMirror off) — return empty.
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// Merge: only produce nodes where both a turn row AND transcript content exist.
|
|
89
|
+
const rows = turnRows
|
|
90
|
+
.filter((t) => contentMap.has(String(t.turn_index)))
|
|
91
|
+
.map((t) => ({
|
|
92
|
+
turn_index: t.turn_index,
|
|
93
|
+
role: t.role,
|
|
94
|
+
ended_at: t.ended_at,
|
|
95
|
+
content_bytes: contentMap.get(String(t.turn_index)) ?? "",
|
|
96
|
+
}));
|
|
97
|
+
if (rows.length === 0)
|
|
98
|
+
return;
|
|
99
|
+
const turnContentNodes = [];
|
|
100
|
+
for (const row of rows) {
|
|
101
|
+
const ti = Number(row.turn_index);
|
|
102
|
+
const nodeId = `turn:${sessionId}:${ti}`;
|
|
103
|
+
if (seen.has(nodeId))
|
|
104
|
+
continue;
|
|
105
|
+
seen.add(nodeId);
|
|
106
|
+
const contentBytes = row.content_bytes ? String(row.content_bytes) : "";
|
|
107
|
+
const snippet = contentBytes.length > 200 ? contentBytes.slice(0, 197) + "..." : contentBytes;
|
|
108
|
+
const node = {
|
|
109
|
+
id: nodeId,
|
|
110
|
+
sessionId,
|
|
111
|
+
label: `Turn ${ti}`,
|
|
112
|
+
summaryTruncated: snippet,
|
|
113
|
+
tokenEstimate: contentBytes.length,
|
|
114
|
+
timestamp: row.ended_at ? Number(row.ended_at) : Date.now(),
|
|
115
|
+
dedupStatus: undefined,
|
|
116
|
+
raptorLevel: 0,
|
|
117
|
+
topicSummary: undefined,
|
|
118
|
+
decisionCount: 0,
|
|
119
|
+
textSnippet: snippet,
|
|
120
|
+
nodeType: "turn-content",
|
|
121
|
+
epochId: undefined,
|
|
122
|
+
};
|
|
123
|
+
turnContentNodes.push(node);
|
|
124
|
+
}
|
|
125
|
+
if (turnContentNodes.length > 1) {
|
|
126
|
+
const embeddings = turnContentNodes.map((n) => {
|
|
127
|
+
try {
|
|
128
|
+
return { id: n.id, vec: getOrComputeEmbedding(mainDb, n.textSnippet) };
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return { id: n.id, vec: null };
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
for (let i = 0; i < embeddings.length; i++) {
|
|
135
|
+
if (!embeddings[i].vec)
|
|
136
|
+
continue;
|
|
137
|
+
for (let j = i + 1; j < embeddings.length; j++) {
|
|
138
|
+
if (!embeddings[j].vec)
|
|
139
|
+
continue;
|
|
140
|
+
const sim = cosineSimilarity(embeddings[i].vec, embeddings[j].vec);
|
|
141
|
+
if (sim > 0) {
|
|
142
|
+
edges.push({
|
|
143
|
+
source: embeddings[i].id,
|
|
144
|
+
target: embeddings[j].id,
|
|
145
|
+
weight: sim,
|
|
146
|
+
type: "semantic",
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
linkTurnToCheckpointEdges(mainDb, sessionId, turnContentNodes, edges);
|
|
152
|
+
}
|
|
153
|
+
nodes.push(...turnContentNodes);
|
|
154
|
+
}
|
|
155
|
+
function linkTurnToCheckpointEdges(db, sessionId, turnNodes, edges) {
|
|
156
|
+
const checkpoints = db
|
|
157
|
+
.prepare(`SELECT id, timestamp FROM context_chunks WHERE session_id = ? ORDER BY timestamp ASC`)
|
|
158
|
+
.all(sessionId);
|
|
159
|
+
for (const tn of turnNodes) {
|
|
160
|
+
let bestCp = null;
|
|
161
|
+
for (const cp of checkpoints) {
|
|
162
|
+
const diff = Math.abs(tn.timestamp - cp.timestamp);
|
|
163
|
+
if (bestCp === null || diff < bestCp.diff) {
|
|
164
|
+
bestCp = { id: cp.id, diff };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (bestCp) {
|
|
168
|
+
edges.push({
|
|
169
|
+
source: tn.id,
|
|
170
|
+
target: bestCp.id,
|
|
171
|
+
weight: 1.0,
|
|
172
|
+
type: "temporal",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|