pi-mega-compact 0.12.0 → 0.12.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/gather-debug.js +63 -0
- package/dist/extensions/dashboard-server/routes-health.js +44 -0
- package/dist/extensions/dashboard-server/routes-setup.js +30 -4
- package/dist/extensions/dashboard-server/routes.js +1 -1
- package/dist/extensions/dashboard-server/server.js +3 -1
- package/dist/extensions/mega-events/agent-handlers.js +27 -1
- package/dist/extensions/mega-events/health-handler.js +19 -2
- package/dist/src/memoryGraph/sources.js +42 -15
- package/dist/src/memoryGraph.js +17 -5
- package/dist/src/store/sqlite/meta.js +11 -0
- package/dist/src/store/turns/schema.js +18 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-DeQ0qnBX.js → AchievementTiles-Cntu89Xw.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-DeQ0qnBX.js.map → AchievementTiles-Cntu89Xw.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-Dtk_RJfs.js → AchievementsTab-4ckY9z4n.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-Dtk_RJfs.js.map → AchievementsTab-4ckY9z4n.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-kWcQt3PI.js → CacheTab-BfGJkiaf.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-kWcQt3PI.js.map → CacheTab-BfGJkiaf.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ConfigTab-UFlo73fR.js → ConfigTab-DV08rHoV.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ConfigTab-UFlo73fR.js.map → ConfigTab-DV08rHoV.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-BENO2N0y.js → EventsTab-DHytZ0xs.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-BENO2N0y.js.map → EventsTab-DHytZ0xs.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{GameTab-BCJavOML.js → GameTab-Ofpoz-6s.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{GameTab-BCJavOML.js.map → GameTab-Ofpoz-6s.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/HealthTab-B0klAxD8.js +2 -0
- package/extensions/dashboard-client/dist/assets/HealthTab-B0klAxD8.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-CHKh3KPI.js +2 -0
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-CHKh3KPI.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-w5BZP3i-.js → MemoryMapTab-B6FLQ-32.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-w5BZP3i-.js.map → MemoryMapTab-B6FLQ-32.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-BBCXa3xS.js → MetricsTab-DvOjTDkW.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-BBCXa3xS.js.map → MetricsTab-DvOjTDkW.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-CzABMSAQ.js → OverviewTab-CqbONEwi.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-CzABMSAQ.js.map → OverviewTab-CqbONEwi.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-DFiM_Ubg.js → ReposTab-sc1GLn7i.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-DFiM_Ubg.js.map → ReposTab-sc1GLn7i.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-BtW4bees.js → SessionsTab-C5gB45oc.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-BtW4bees.js.map → SessionsTab-C5gB45oc.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/SetupTab-BEEX4OF5.js +2 -0
- package/extensions/dashboard-client/dist/assets/SetupTab-BEEX4OF5.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CC13EXij.js → TimeSavedCard-BBwvb4cC.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CC13EXij.js.map → TimeSavedCard-BBwvb4cC.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TopicsTab-BrTB8uEK.js → TopicsTab-BsCf512_.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TopicsTab-BrTB8uEK.js.map → TopicsTab-BsCf512_.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-BJK7FIHu.js → TurnsTab-C3ti7NB5.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-BJK7FIHu.js.map → TurnsTab-C3ti7NB5.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/health-lSeDFTz7.js +2 -0
- package/extensions/dashboard-client/dist/assets/health-lSeDFTz7.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{index-B8k19B9g.js → index-BEeaI9uo.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-B8k19B9g.js.map → index-BEeaI9uo.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-CWBB4Me7.js → useSSE-BMSSO9wB.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-CWBB4Me7.js.map → useSSE-BMSSO9wB.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/api/health.ts +16 -0
- package/extensions/dashboard-client/src/tabs/MaintenanceTab.tsx +91 -1
- package/extensions/dashboard-client/src/tabs/SetupTab.tsx +19 -0
- package/extensions/dashboard-server/api-contracts/maintenance.ts +1 -0
- package/extensions/dashboard-server/api-contracts/setup.ts +7 -1
- package/extensions/dashboard-server/gather-debug.ts +63 -0
- package/extensions/dashboard-server/routes-health.ts +51 -0
- package/extensions/dashboard-server/routes-setup.ts +28 -4
- package/extensions/dashboard-server/routes.ts +1 -1
- package/extensions/dashboard-server/server.ts +2 -0
- package/extensions/mega-events/agent-handlers.ts +26 -1
- package/extensions/mega-events/health-handler.ts +31 -3
- package/package.json +1 -1
- package/src/memoryGraph/sources.ts +49 -15
- package/src/memoryGraph.ts +17 -5
- package/src/store/sqlite/meta.ts +16 -0
- package/src/store/turns/schema.ts +18 -2
- package/extensions/dashboard-client/dist/assets/HealthTab-C7iw7rhx.js +0 -2
- package/extensions/dashboard-client/dist/assets/HealthTab-C7iw7rhx.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-C0rdAMWc.js +0 -2
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-C0rdAMWc.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/SetupTab-Duy1LJhg.js +0 -2
- package/extensions/dashboard-client/dist/assets/SetupTab-Duy1LJhg.js.map +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as e}from"./index-
|
|
2
|
-
//# sourceMappingURL=useSSE-
|
|
1
|
+
import{r as e}from"./index-BEeaI9uo.js";function R(i={}){const{maxEvents:c=500,maxReconnects:l=5}=i,[f,v]=e.useState([]),[E,n]=e.useState("connecting"),[S,p]=e.useState(0),[m,d]=e.useState(null),o=e.useRef(0),s=e.useRef(null),r=e.useCallback(()=>{s.current&&s.current.close(),n("connecting");const t=new EventSource("/api/events");s.current=t,t.onopen=()=>{n("connected"),o.current=0},t.onmessage=g=>{try{const x=JSON.parse(g.data);v(u=>{const a=[...u,x];return a.length>c?a.slice(-c):a}),p(u=>u+1),d(Date.now())}catch{}},t.onerror=()=>{t.close(),o.current<l?(o.current+=1,n("connecting"),setTimeout(r,1e3)):n("disconnected")}},[c,l]);return e.useEffect(()=>(r(),()=>{s.current?.close()}),[r]),{events:f,status:E,eventCount:S,lastEventAt:m}}export{R as u};
|
|
2
|
+
//# sourceMappingURL=useSSE-BMSSO9wB.js.map
|
package/extensions/dashboard-client/dist/assets/{useSSE-CWBB4Me7.js.map → useSSE-BMSSO9wB.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSSE-
|
|
1
|
+
{"version":3,"file":"useSSE-BMSSO9wB.js","sources":["../../src/hooks/useSSE.ts"],"sourcesContent":["/**\n * dashboard-client/src/hooks/useSSE.ts — Server-Sent Events hook.\n *\n * Connects to /api/events, provides real-time event stream.\n * SPRINT-B1: basic connection. SPRINT-D1: reconnect with backoff.\n */\n\nimport { useState, useEffect, useRef, useCallback } from 'react';\nimport type { SseEvent } from '../../../dashboard-server/api-contracts';\n\nexport type SseStatus = 'connecting' | 'connected' | 'disconnected' | 'error';\n\nexport interface UseSSEResult {\n events: SseEvent[];\n status: SseStatus;\n eventCount: number;\n lastEventAt: number | null;\n}\n\nexport interface UseSSEOptions {\n /** Max events to keep in memory (ring buffer). */\n maxEvents?: number;\n /** Max reconnect attempts before giving up. */\n maxReconnects?: number;\n}\n\n// SPRINT-D1-REMAINING: exponential backoff (1s → 2s → 4s → max 30s).\n// SPRINT-D1-REMAINING: after 5 failures, set status='disconnected'.\n\nexport function useSSE(options: UseSSEOptions = {}): UseSSEResult {\n const { maxEvents = 500, maxReconnects = 5 } = options;\n const [events, setEvents] = useState<SseEvent[]>([]);\n const [status, setStatus] = useState<SseStatus>('connecting');\n const [eventCount, setEventCount] = useState(0);\n const [lastEventAt, setLastEventAt] = useState<number | null>(null);\n const reconnectCount = useRef(0);\n const sourceRef = useRef<EventSource | null>(null);\n\n const connect = useCallback(() => {\n if (sourceRef.current) {\n sourceRef.current.close();\n }\n setStatus('connecting');\n const source = new EventSource('/api/events');\n sourceRef.current = source;\n\n source.onopen = () => {\n setStatus('connected');\n reconnectCount.current = 0;\n };\n\n source.onmessage = (event) => {\n try {\n const parsed = JSON.parse(event.data) as SseEvent;\n setEvents(prev => {\n const next = [...prev, parsed];\n return next.length > maxEvents ? next.slice(-maxEvents) : next;\n });\n setEventCount(c => c + 1);\n setLastEventAt(Date.now());\n } catch {\n // non-fatal: skip malformed events\n }\n };\n\n source.onerror = () => {\n source.close();\n if (reconnectCount.current < maxReconnects) {\n reconnectCount.current += 1;\n setStatus('connecting');\n // SPRINT-D1: use exponential backoff instead of fixed delay\n setTimeout(connect, 1000);\n } else {\n setStatus('disconnected');\n }\n };\n }, [maxEvents, maxReconnects]);\n\n useEffect(() => {\n connect();\n return () => {\n sourceRef.current?.close();\n };\n }, [connect]);\n\n return { events, status, eventCount, lastEventAt };\n}\n"],"names":["useSSE","options","maxEvents","maxReconnects","events","setEvents","useState","status","setStatus","eventCount","setEventCount","lastEventAt","setLastEventAt","reconnectCount","useRef","sourceRef","connect","useCallback","source","event","parsed","prev","next","c","useEffect"],"mappings":"wCA6BO,SAASA,EAAOC,EAAyB,GAAkB,CAChE,KAAM,CAAE,UAAAC,EAAY,IAAK,cAAAC,EAAgB,GAAMF,EACzC,CAACG,EAAQC,CAAS,EAAIC,EAAAA,SAAqB,CAAA,CAAE,EAC7C,CAACC,EAAQC,CAAS,EAAIF,EAAAA,SAAoB,YAAY,EACtD,CAACG,EAAYC,CAAa,EAAIJ,EAAAA,SAAS,CAAC,EACxC,CAACK,EAAaC,CAAc,EAAIN,EAAAA,SAAwB,IAAI,EAC5DO,EAAiBC,EAAAA,OAAO,CAAC,EACzBC,EAAYD,EAAAA,OAA2B,IAAI,EAE3CE,EAAUC,EAAAA,YAAY,IAAM,CAC5BF,EAAU,SACZA,EAAU,QAAQ,MAAA,EAEpBP,EAAU,YAAY,EACtB,MAAMU,EAAS,IAAI,YAAY,aAAa,EAC5CH,EAAU,QAAUG,EAEpBA,EAAO,OAAS,IAAM,CACpBV,EAAU,WAAW,EACrBK,EAAe,QAAU,CAC3B,EAEAK,EAAO,UAAaC,GAAU,CAC5B,GAAI,CACF,MAAMC,EAAS,KAAK,MAAMD,EAAM,IAAI,EACpCd,EAAUgB,GAAQ,CAChB,MAAMC,EAAO,CAAC,GAAGD,EAAMD,CAAM,EAC7B,OAAOE,EAAK,OAASpB,EAAYoB,EAAK,MAAM,CAACpB,CAAS,EAAIoB,CAC5D,CAAC,EACDZ,EAAca,GAAKA,EAAI,CAAC,EACxBX,EAAe,KAAK,KAAK,CAC3B,MAAQ,CAER,CACF,EAEAM,EAAO,QAAU,IAAM,CACrBA,EAAO,MAAA,EACHL,EAAe,QAAUV,GAC3BU,EAAe,SAAW,EAC1BL,EAAU,YAAY,EAEtB,WAAWQ,EAAS,GAAI,GAExBR,EAAU,cAAc,CAE5B,CACF,EAAG,CAACN,EAAWC,CAAa,CAAC,EAE7BqB,OAAAA,EAAAA,UAAU,KACRR,EAAA,EACO,IAAM,CACXD,EAAU,SAAS,MAAA,CACrB,GACC,CAACC,CAAO,CAAC,EAEL,CAAE,OAAAZ,EAAQ,OAAAG,EAAQ,WAAAE,EAAY,YAAAE,CAAA,CACvC"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>mega-compact dashboard</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/index-
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-BEeaI9uo.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="./assets/index-CUNn22iV.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
@@ -62,3 +62,19 @@ export async function fetchCachePoison(): Promise<CachePoisonResponse> {
|
|
|
62
62
|
if (!r.ok) throw new Error(`cache-poison: ${r.status}`);
|
|
63
63
|
return r.json() as Promise<CachePoisonResponse>;
|
|
64
64
|
}
|
|
65
|
+
|
|
66
|
+
export async function fetchHealthSettings(): Promise<{ mitigate: boolean }> {
|
|
67
|
+
const r = await fetch("/api/context-health/settings");
|
|
68
|
+
if (!r.ok) throw new Error(`health-settings: ${r.status}`);
|
|
69
|
+
return r.json() as Promise<{ mitigate: boolean }>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function setHealthMitigate(mitigate: boolean): Promise<{ mitigate: boolean }> {
|
|
73
|
+
const r = await fetch("/api/context-health/settings", {
|
|
74
|
+
method: "PUT",
|
|
75
|
+
headers: { "Content-Type": "application/json" },
|
|
76
|
+
body: JSON.stringify({ mitigate }),
|
|
77
|
+
});
|
|
78
|
+
if (!r.ok) throw new Error(`health-settings PUT: ${r.status}`);
|
|
79
|
+
return r.json() as Promise<{ mitigate: boolean }>;
|
|
80
|
+
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type React from "react";
|
|
13
|
-
import { useState, useCallback } from "react";
|
|
13
|
+
import { useState, useCallback, useEffect } from "react";
|
|
14
14
|
import type {
|
|
15
15
|
DbStatsResponse,
|
|
16
16
|
SchemaHealthResponse,
|
|
@@ -519,6 +519,95 @@ function DebugBundleCard(): React.ReactElement {
|
|
|
519
519
|
);
|
|
520
520
|
}
|
|
521
521
|
|
|
522
|
+
// ---------------------------------------------------------------------------
|
|
523
|
+
// Health Mitigation card — toggle auto-mitigation for degraded context
|
|
524
|
+
// ---------------------------------------------------------------------------
|
|
525
|
+
|
|
526
|
+
function HealthMitigationCard(): React.ReactElement {
|
|
527
|
+
const [mitigate, setMitigate] = useState(false);
|
|
528
|
+
const [loading, setLoading] = useState(true);
|
|
529
|
+
const [saving, setSaving] = useState(false);
|
|
530
|
+
const [error, setError] = useState<string | null>(null);
|
|
531
|
+
|
|
532
|
+
const load = useCallback(async () => {
|
|
533
|
+
try {
|
|
534
|
+
const { fetchHealthSettings } = await import("../api/health");
|
|
535
|
+
const s = await fetchHealthSettings();
|
|
536
|
+
setMitigate(s.mitigate);
|
|
537
|
+
} catch (e) {
|
|
538
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
539
|
+
} finally {
|
|
540
|
+
setLoading(false);
|
|
541
|
+
}
|
|
542
|
+
}, []);
|
|
543
|
+
|
|
544
|
+
useEffect(() => { void load(); }, [load]);
|
|
545
|
+
|
|
546
|
+
const toggle = useCallback(async () => {
|
|
547
|
+
setSaving(true);
|
|
548
|
+
setError(null);
|
|
549
|
+
try {
|
|
550
|
+
const { setHealthMitigate } = await import("../api/health");
|
|
551
|
+
const next = !mitigate;
|
|
552
|
+
await setHealthMitigate(next);
|
|
553
|
+
setMitigate(next);
|
|
554
|
+
} catch (e) {
|
|
555
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
556
|
+
} finally {
|
|
557
|
+
setSaving(false);
|
|
558
|
+
}
|
|
559
|
+
}, [mitigate]);
|
|
560
|
+
|
|
561
|
+
return (
|
|
562
|
+
<div className="stat-section maintenance-card">
|
|
563
|
+
<div className="stat-label">Context Health Mitigation</div>
|
|
564
|
+
{loading ? (
|
|
565
|
+
<p className="repo-none">Loading…</p>
|
|
566
|
+
) : (
|
|
567
|
+
<>
|
|
568
|
+
<div className="health-mitigate-row">
|
|
569
|
+
<label className="health-mitigate-toggle">
|
|
570
|
+
<input
|
|
571
|
+
type="checkbox"
|
|
572
|
+
checked={mitigate}
|
|
573
|
+
onChange={toggle}
|
|
574
|
+
disabled={saving}
|
|
575
|
+
/>
|
|
576
|
+
<span>Auto-mitigation {mitigate ? "ON" : "OFF"}</span>
|
|
577
|
+
</label>
|
|
578
|
+
</div>
|
|
579
|
+
<div className="health-mitigate-info">
|
|
580
|
+
<p>
|
|
581
|
+
When <strong>ON</strong>, the extension automatically intervenes when
|
|
582
|
+
context health degrades:
|
|
583
|
+
</p>
|
|
584
|
+
<ul>
|
|
585
|
+
<li><strong>Forced compaction</strong> — when the composite health score
|
|
586
|
+
drops below 0.4 (severe drift or garbled output), a compaction is
|
|
587
|
+
triggered to flush the degraded context and rebuild from clean
|
|
588
|
+
checkpoints, even if the context window isn't full.</li>
|
|
589
|
+
<li><strong>Prefix break</strong> — when KV cache poison is detected
|
|
590
|
+
(score < 0.3), the cached prefix is invalidated to force a cache
|
|
591
|
+
miss on the next turn, bypassing the corrupted KV state.</li>
|
|
592
|
+
</ul>
|
|
593
|
+
<p className="health-mitigate-warning">
|
|
594
|
+
WARNING: both interventions have costs — forced compaction discards
|
|
595
|
+
live context, and prefix breaks kill cache savings. Only enable when
|
|
596
|
+
you're seeing degraded output (garbled text, hallucinations,
|
|
597
|
+
loss of coherence) and the Health tab confirms low scores.
|
|
598
|
+
</p>
|
|
599
|
+
<p>
|
|
600
|
+
Also settable via env var: <code>MEGACOMPACT_CONTEXT_HEALTH_MITIGATE=1</code>.
|
|
601
|
+
The dashboard toggle persists to the store and takes precedence.
|
|
602
|
+
</p>
|
|
603
|
+
{error && <p className="health-mitigate-error">Error: {error}</p>}
|
|
604
|
+
</div>
|
|
605
|
+
</>
|
|
606
|
+
)}
|
|
607
|
+
</div>
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
|
|
522
611
|
// ---------------------------------------------------------------------------
|
|
523
612
|
// Main tab component
|
|
524
613
|
// ---------------------------------------------------------------------------
|
|
@@ -554,6 +643,7 @@ export default function MaintenanceTab(): React.ReactElement {
|
|
|
554
643
|
</div>
|
|
555
644
|
<ActionsCard />
|
|
556
645
|
<DebugBundleCard />
|
|
646
|
+
<HealthMitigationCard />
|
|
557
647
|
</div>
|
|
558
648
|
);
|
|
559
649
|
}
|
|
@@ -207,6 +207,25 @@ export default function SetupTab(): React.ReactElement {
|
|
|
207
207
|
{embedderLabel(status.currentEmbedder)}
|
|
208
208
|
</span>
|
|
209
209
|
</div>
|
|
210
|
+
{"configuredEmbedder" in status && status.configuredEmbedder !== status.currentEmbedder && (
|
|
211
|
+
<div style={styles.warning}>
|
|
212
|
+
<strong>Configured but not active:</strong>{" "}
|
|
213
|
+
{embedderLabel(status.configuredEmbedder)} is configured in
|
|
214
|
+
.mega-compact.env but not yet loaded.{" "}
|
|
215
|
+
<strong>Restart pi</strong> to activate it.
|
|
216
|
+
{status.configuredUrl && (
|
|
217
|
+
<> ({status.configuredUrl})</>
|
|
218
|
+
)}
|
|
219
|
+
</div>
|
|
220
|
+
)}
|
|
221
|
+
{"restartRequired" in status && status.restartRequired && (
|
|
222
|
+
<div style={{ ...styles.row, color: "#ff9800" }}>
|
|
223
|
+
<span style={styles.label}>Status:</span>
|
|
224
|
+
<span style={styles.value}>
|
|
225
|
+
Restart required to activate the configured embedder
|
|
226
|
+
</span>
|
|
227
|
+
</div>
|
|
228
|
+
)}
|
|
210
229
|
<div style={styles.row}>
|
|
211
230
|
<span style={styles.label}>Embedding URL:</span>
|
|
212
231
|
<span style={styles.value}>
|
|
@@ -13,8 +13,14 @@
|
|
|
13
13
|
* Returns the current embedder configuration read from the environment.
|
|
14
14
|
*/
|
|
15
15
|
export interface SetupStatusResponse {
|
|
16
|
-
/** Which embedder implementation is currently active. */
|
|
16
|
+
/** Which embedder implementation is currently active in the running process. */
|
|
17
17
|
readonly currentEmbedder: "trigram" | "http" | "minilm" | "unknown";
|
|
18
|
+
/** Which embedder was configured via the dashboard (from .mega-compact.env). */
|
|
19
|
+
readonly configuredEmbedder: "trigram" | "http" | "minilm";
|
|
20
|
+
/** The URL configured in .mega-compact.env, if any. */
|
|
21
|
+
readonly configuredUrl: string | null;
|
|
22
|
+
/** True when the configured embedder differs from the active one (restart needed). */
|
|
23
|
+
readonly restartRequired: boolean;
|
|
18
24
|
/** The embedding URL if httpEmbedder is active, otherwise null. */
|
|
19
25
|
readonly embeddingUrl: string | null;
|
|
20
26
|
/** The MEGACOMPACT_EMBED_CACHE value (number as string), or null. */
|
|
@@ -44,6 +44,7 @@ export function sendGatherDebug(res: ServerResponse, ctx: RouteContext): void {
|
|
|
44
44
|
dashboardSnapshot: { error: "not fetched" },
|
|
45
45
|
recentEvents: [],
|
|
46
46
|
criticalEvents: [],
|
|
47
|
+
dataSourceAudit: {},
|
|
47
48
|
};
|
|
48
49
|
|
|
49
50
|
// 1. Config flags (no secrets — MEGACOMPACT_* only)
|
|
@@ -123,6 +124,68 @@ export function sendGatherDebug(res: ServerResponse, ctx: RouteContext): void {
|
|
|
123
124
|
result.recentEvents = [{ error: e instanceof Error ? e.message : String(e) }];
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
// 6. Data source audit — per-tab data population check
|
|
128
|
+
try {
|
|
129
|
+
const audit: Record<string, unknown> = {};
|
|
130
|
+
const db = openStoreDb(ctx);
|
|
131
|
+
|
|
132
|
+
// Main db tables
|
|
133
|
+
for (const table of ["context_chunks", "memories", "raw_transcript", "checkpoint_epochs", "dedup_mirror"]) {
|
|
134
|
+
try {
|
|
135
|
+
const row = db.prepare(`SELECT COUNT(*) AS c FROM ${table}`).get() as { c: number };
|
|
136
|
+
audit[`main.${table}`] = row.c;
|
|
137
|
+
} catch {
|
|
138
|
+
audit[`main.${table}`] = "table missing";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// context_health + cache_poison_events
|
|
143
|
+
for (const table of ["context_health", "cache_poison_events"]) {
|
|
144
|
+
try {
|
|
145
|
+
const row = db.prepare(`SELECT COUNT(*) AS c FROM ${table}`).get() as { c: number };
|
|
146
|
+
audit[`main.${table}`] = row.c;
|
|
147
|
+
} catch {
|
|
148
|
+
audit[`main.${table}`] = "table missing";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// turns.db tables
|
|
153
|
+
try {
|
|
154
|
+
const turnMod = require("../../src/store/turns/connection.js") as typeof import("../../src/store/turns/connection.js");
|
|
155
|
+
const tdb = turnMod.openTurnDb(ctx.stateDir);
|
|
156
|
+
for (const table of ["turns", "turn_recall", "session_conversations", "topics", "memory_topics"]) {
|
|
157
|
+
try {
|
|
158
|
+
const row = tdb.prepare(`SELECT COUNT(*) AS c FROM ${table}`).get() as { c: number };
|
|
159
|
+
audit[`turns.${table}`] = row.c;
|
|
160
|
+
} catch {
|
|
161
|
+
audit[`turns.${table}`] = "table missing";
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} catch (e) {
|
|
165
|
+
audit["turns.db"] = `error: ${e instanceof Error ? e.message : String(e)}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// .mega-compact.env
|
|
169
|
+
try {
|
|
170
|
+
const envPath = ctx.stateDir + "/.mega-compact.env";
|
|
171
|
+
audit["mega-compact.env"] = existsSync(envPath) ? "exists" : "not found";
|
|
172
|
+
} catch {
|
|
173
|
+
audit["mega-compact.env"] = "check failed";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// dashboard.json
|
|
177
|
+
try {
|
|
178
|
+
const metricsPath = defaultMetricsPath(ctx.stateDir);
|
|
179
|
+
audit["dashboard.json"] = existsSync(metricsPath) ? "exists" : "not found";
|
|
180
|
+
} catch {
|
|
181
|
+
audit["dashboard.json"] = "check failed";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
(result as { dataSourceAudit: Record<string, unknown> }).dataSourceAudit = audit;
|
|
185
|
+
} catch (e) {
|
|
186
|
+
(result as { dataSourceAudit: unknown }).dataSourceAudit = { error: e instanceof Error ? e.message : String(e) };
|
|
187
|
+
}
|
|
188
|
+
|
|
126
189
|
// Send (local helper to avoid importing sendJson from a non-exporting module)
|
|
127
190
|
try {
|
|
128
191
|
const body = JSON.stringify(result);
|
|
@@ -102,3 +102,54 @@ export function handleCachePoison(
|
|
|
102
102
|
}
|
|
103
103
|
return true;
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
/** GET/PUT /api/context-health/settings — read or toggle mitigation. */
|
|
107
|
+
export function handleHealthSettings(
|
|
108
|
+
req: IncomingMessage,
|
|
109
|
+
res: ServerResponse,
|
|
110
|
+
ctx: RouteContext,
|
|
111
|
+
): boolean {
|
|
112
|
+
if (!req.url?.startsWith("/api/context-health/settings")) return false;
|
|
113
|
+
try {
|
|
114
|
+
const pfReq = createRequire(import.meta.url);
|
|
115
|
+
const { getHealthMitigate, setHealthMitigate } = pfReq(
|
|
116
|
+
"../../src/store/sqlite.js",
|
|
117
|
+
) as typeof import("../../src/store/sqlite.js");
|
|
118
|
+
|
|
119
|
+
if (req.method === "GET") {
|
|
120
|
+
const mitigate = getHealthMitigate(ctx.stateDir);
|
|
121
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
122
|
+
res.end(JSON.stringify({ mitigate }));
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (req.method === "PUT") {
|
|
127
|
+
let body = "";
|
|
128
|
+
req.on("data", (c) => (body += c));
|
|
129
|
+
req.on("end", () => {
|
|
130
|
+
try {
|
|
131
|
+
const parsed = JSON.parse(body) as { mitigate?: unknown };
|
|
132
|
+
if (typeof parsed.mitigate !== "boolean") {
|
|
133
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
134
|
+
res.end(JSON.stringify({ error: "mitigate must be boolean" }));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
setHealthMitigate(parsed.mitigate, ctx.stateDir);
|
|
138
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
139
|
+
res.end(JSON.stringify({ mitigate: parsed.mitigate }));
|
|
140
|
+
} catch {
|
|
141
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
142
|
+
res.end(JSON.stringify({ error: "invalid_json" }));
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
res.writeHead(405, { "Content-Type": "application/json" });
|
|
149
|
+
res.end(JSON.stringify({ error: "method_not_allowed" }));
|
|
150
|
+
} catch (e) {
|
|
151
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
152
|
+
res.end(JSON.stringify({ error: String(e) }));
|
|
153
|
+
}
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { spawnSync } from "node:child_process"; // guardrails-allow PREVENT-PI-004: local subprocess detection only
|
|
13
13
|
import { createRequire } from "node:module";
|
|
14
|
-
import { writeFileSync, mkdirSync } from "node:fs";
|
|
14
|
+
import { writeFileSync, mkdirSync, readFileSync, existsSync } from "node:fs";
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
17
17
|
import type { RouteContext } from "./routes-core.js";
|
|
@@ -37,10 +37,29 @@ function detectCurrentEmbedder(): SetupStatusResponse["currentEmbedder"] {
|
|
|
37
37
|
return "trigram";
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/** Read the configured embedder from the .mega-compact.env file (written by
|
|
41
|
+
* the configure endpoint). This shows what the user SELECTED even before pi
|
|
42
|
+
* restarts and loads the env file into process.env. */
|
|
43
|
+
function detectConfiguredEmbedder(stateDir: string): { embedder: "trigram" | "http" | "minilm"; url: string | null } {
|
|
44
|
+
try {
|
|
45
|
+
const envPath = join(stateDir, ".mega-compact.env");
|
|
46
|
+
if (!existsSync(envPath)) return { embedder: "trigram", url: null };
|
|
47
|
+
const content = readFileSync(envPath, "utf-8");
|
|
48
|
+
const urlMatch = content.match(/^export\s+MEGACOMPACT_EMBEDDING_URL="([^"]+)"/m);
|
|
49
|
+
if (urlMatch) return { embedder: "http", url: urlMatch[1] };
|
|
50
|
+
const minilmMatch = content.match(/^export\s+MEGACOMPACT_MINILM="?(.+?)"?\s*$/m);
|
|
51
|
+
if (minilmMatch && ["1", "true", "yes"].includes(minilmMatch[1].trim().toLowerCase()))
|
|
52
|
+
return { embedder: "minilm", url: null };
|
|
53
|
+
return { embedder: "trigram", url: null };
|
|
54
|
+
} catch {
|
|
55
|
+
return { embedder: "trigram", url: null };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
40
59
|
export function handleSetupStatus(
|
|
41
60
|
req: IncomingMessage,
|
|
42
61
|
res: ServerResponse,
|
|
43
|
-
|
|
62
|
+
ctx: RouteContext,
|
|
44
63
|
): boolean {
|
|
45
64
|
if (req.url !== "/api/setup-status") return false;
|
|
46
65
|
if (req.method !== "GET") {
|
|
@@ -49,9 +68,14 @@ export function handleSetupStatus(
|
|
|
49
68
|
res.end(JSON.stringify({ error: "method_not_allowed" }));
|
|
50
69
|
return true;
|
|
51
70
|
}
|
|
71
|
+
const configured = detectConfiguredEmbedder(ctx.stateDir);
|
|
72
|
+
const active = detectCurrentEmbedder();
|
|
52
73
|
const body: SetupStatusResponse = {
|
|
53
|
-
currentEmbedder:
|
|
54
|
-
|
|
74
|
+
currentEmbedder: active,
|
|
75
|
+
configuredEmbedder: configured.embedder,
|
|
76
|
+
configuredUrl: configured.url,
|
|
77
|
+
restartRequired: active !== configured.embedder,
|
|
78
|
+
embeddingUrl: process.env["MEGACOMPACT_EMBEDDING_URL"] ?? configured.url,
|
|
55
79
|
embedCache: process.env["MEGACOMPACT_EMBED_CACHE"] ?? null,
|
|
56
80
|
minilm: ["1", "true", "yes"].includes(
|
|
57
81
|
(process.env["MEGACOMPACT_MINILM"] ?? "").trim().toLowerCase(),
|
|
@@ -24,4 +24,4 @@ export { handleMemoryStatus } from "./routes-memory.js";
|
|
|
24
24
|
export { handleSetupStatus, handleSetupDetect, handleSetupConfigure } from "./routes-setup.js";
|
|
25
25
|
export { handleMemoryMap } from "./routes-memory-map.js";
|
|
26
26
|
export { handleCacheStripes } from "./routes-cache.js";
|
|
27
|
-
export { handleContextHealth, handleCachePoison } from "./routes-health.js";
|
|
27
|
+
export { handleContextHealth, handleCachePoison, handleHealthSettings } from "./routes-health.js";
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
handleMemoryMap,
|
|
47
47
|
handleContextHealth,
|
|
48
48
|
handleCachePoison,
|
|
49
|
+
handleHealthSettings,
|
|
49
50
|
handleStatic,
|
|
50
51
|
} from "./routes.js";
|
|
51
52
|
|
|
@@ -243,6 +244,7 @@ export async function launchDashboardServer(
|
|
|
243
244
|
if (handleMemoryMap(req, res, ctx)) return;
|
|
244
245
|
if (handleContextHealth(req, res, ctx)) return;
|
|
245
246
|
if (handleCachePoison(req, res, ctx)) return;
|
|
247
|
+
if (handleHealthSettings(req, res, ctx)) return;
|
|
246
248
|
handleStatic(req, res, ctx);
|
|
247
249
|
});
|
|
248
250
|
|
|
@@ -865,6 +865,31 @@ export function registerAgentHandlers(
|
|
|
865
865
|
}
|
|
866
866
|
|
|
867
867
|
// v0.12: Context Health — compute + persist health score. Non-fatal.
|
|
868
|
-
|
|
868
|
+
// Mitigation signals are acted on here (ctx is in scope at turn_end).
|
|
869
|
+
const healthSignal = handleTurnEndHealth(event, runtime, config);
|
|
870
|
+
if (healthSignal.forceCompact) {
|
|
871
|
+
runtime.logger?.info("health_mitigate_compact", {
|
|
872
|
+
composite: healthSignal.composite,
|
|
873
|
+
turnIndex: event.turnIndex,
|
|
874
|
+
});
|
|
875
|
+
try {
|
|
876
|
+
// Force a compaction to flush degraded context. Same race-guarded
|
|
877
|
+
// deferred path as the critical-over escape hatch above.
|
|
878
|
+
if (!piCompactWouldNoop(ctx)) {
|
|
879
|
+
ctx.compact();
|
|
880
|
+
}
|
|
881
|
+
} catch {
|
|
882
|
+
/* non-fatal: mitigation never breaks the agent loop */
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
if (healthSignal.breakPrefix) {
|
|
886
|
+
runtime.logger?.info("health_mitigate_prefix_break", {
|
|
887
|
+
turnIndex: event.turnIndex,
|
|
888
|
+
});
|
|
889
|
+
// Invalidate the cached trim so the next context event regenerates
|
|
890
|
+
// from scratch — the prefix change forces a cache miss, bypassing
|
|
891
|
+
// the corrupted KV state.
|
|
892
|
+
runtime.trimCache = null;
|
|
893
|
+
}
|
|
869
894
|
});
|
|
870
895
|
}
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
import {
|
|
33
33
|
recordCachePoisonEvent,
|
|
34
34
|
} from "../../src/store/sqlite/context-health.js";
|
|
35
|
+
import { getHealthMitigate } from "../../src/store/sqlite/meta.js";
|
|
35
36
|
|
|
36
37
|
const RING_MAX = 5;
|
|
37
38
|
|
|
@@ -64,13 +65,24 @@ interface TurnEvent {
|
|
|
64
65
|
message?: { role?: string; content?: unknown; stopReason?: string; usage?: { cacheRead?: number } };
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
export interface HealthMitigationSignal {
|
|
69
|
+
/** Force a compaction to flush degraded context (composite < 0.4). */
|
|
70
|
+
forceCompact: boolean;
|
|
71
|
+
/** Inject a prefix break to bypass corrupted KV cache (cachePoison < 0.3). */
|
|
72
|
+
breakPrefix: boolean;
|
|
73
|
+
/** The composite health score that triggered mitigation (if any). */
|
|
74
|
+
composite: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Handle turn_end: compute + persist context health. Non-fatal.
|
|
78
|
+
* Returns mitigation signals for the caller to act on (agent-handlers has ctx). */
|
|
68
79
|
export function handleTurnEndHealth(
|
|
69
80
|
event: TurnEvent,
|
|
70
81
|
runtime: MegaRuntime,
|
|
71
82
|
config: MegaConfig,
|
|
72
|
-
):
|
|
73
|
-
|
|
83
|
+
): HealthMitigationSignal {
|
|
84
|
+
const noSignal: HealthMitigationSignal = { forceCompact: false, breakPrefix: false, composite: 1 };
|
|
85
|
+
if (!config.contextHealth) return noSignal;
|
|
74
86
|
try {
|
|
75
87
|
const text = extractAssistantText(event);
|
|
76
88
|
const embedder = defaultEmbedder();
|
|
@@ -173,6 +185,11 @@ export function handleTurnEndHealth(
|
|
|
173
185
|
turnIndex: event.turnIndex,
|
|
174
186
|
});
|
|
175
187
|
|
|
188
|
+
// Mitigation: check the runtime-toggleable flag (meta table), not just
|
|
189
|
+
// the env var. The dashboard Maintenance tab toggles this at runtime.
|
|
190
|
+
const mitigate = config.contextHealthMitigate ||
|
|
191
|
+
getHealthMitigate(runtime.currentStateDir);
|
|
192
|
+
|
|
176
193
|
// Update ring buffers
|
|
177
194
|
runtime.recentTurnEmbeddings.push(emb);
|
|
178
195
|
if (runtime.recentTurnEmbeddings.length > RING_MAX) {
|
|
@@ -182,9 +199,20 @@ export function handleTurnEndHealth(
|
|
|
182
199
|
if (runtime.recentErrorCategories.length > RING_MAX) {
|
|
183
200
|
runtime.recentErrorCategories.shift();
|
|
184
201
|
}
|
|
202
|
+
|
|
203
|
+
// Return mitigation signals so agent-handlers.ts (which has ctx) can act.
|
|
204
|
+
if (mitigate) {
|
|
205
|
+
return {
|
|
206
|
+
forceCompact: composite < 0.4,
|
|
207
|
+
breakPrefix: cachePoison < 0.3,
|
|
208
|
+
composite,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return { forceCompact: false, breakPrefix: false, composite };
|
|
185
212
|
} catch (e) {
|
|
186
213
|
runtime.logger?.error("context_health_failed", {
|
|
187
214
|
error: String(e instanceof Error ? e.message : e),
|
|
188
215
|
});
|
|
216
|
+
return noSignal;
|
|
189
217
|
}
|
|
190
218
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mega-compact",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.2",
|
|
4
4
|
"description": "Layered, local, vector-backed context compressor for pi — supersede/collapse/cluster compaction with deduped inline recall.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BSD-3-Clause",
|
|
@@ -206,7 +206,8 @@ export function buildTurnNodes(
|
|
|
206
206
|
// ---------------------------------------------------------------------------
|
|
207
207
|
|
|
208
208
|
export function buildTurnContentNodes(
|
|
209
|
-
|
|
209
|
+
turnDb: DatabaseSync,
|
|
210
|
+
mainDb: DatabaseSync,
|
|
210
211
|
sessionId: string,
|
|
211
212
|
nodes: MemoryGraphNode[],
|
|
212
213
|
edges: MemoryGraphEdge[],
|
|
@@ -216,25 +217,58 @@ export function buildTurnContentNodes(
|
|
|
216
217
|
// (identity_merge) merges them with the richest nodeType winning. Using the
|
|
217
218
|
// global nodes array here would skip every node (Source A already added them).
|
|
218
219
|
const seen = new Set<string>();
|
|
219
|
-
|
|
220
|
-
|
|
220
|
+
|
|
221
|
+
// turns live in turns.db, raw_transcript in the main db — can't JOIN across
|
|
222
|
+
// databases, so query separately and merge in JS.
|
|
223
|
+
const turnRows = sessionId
|
|
224
|
+
? (turnDb
|
|
221
225
|
.prepare(
|
|
222
|
-
`SELECT
|
|
223
|
-
FROM turns
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
ORDER BY t.turn_index ASC`,
|
|
226
|
+
`SELECT turn_index, role, ended_at
|
|
227
|
+
FROM turns
|
|
228
|
+
WHERE session_id = ?
|
|
229
|
+
ORDER BY turn_index ASC`,
|
|
227
230
|
)
|
|
228
231
|
.all(sessionId) as Array<Record<string, unknown>>)
|
|
229
|
-
: (
|
|
232
|
+
: (turnDb
|
|
230
233
|
.prepare(
|
|
231
|
-
`SELECT
|
|
232
|
-
FROM turns
|
|
233
|
-
|
|
234
|
-
ORDER BY t.turn_index ASC`,
|
|
234
|
+
`SELECT turn_index, role, ended_at
|
|
235
|
+
FROM turns
|
|
236
|
+
ORDER BY turn_index ASC`,
|
|
235
237
|
)
|
|
236
238
|
.all() as Array<Record<string, unknown>>);
|
|
237
239
|
|
|
240
|
+
// Build a map of (session_id, turn_index) → content from raw_transcript.
|
|
241
|
+
const contentMap = new Map<string, string>();
|
|
242
|
+
try {
|
|
243
|
+
const transcriptRows = sessionId
|
|
244
|
+
? (mainDb
|
|
245
|
+
.prepare(
|
|
246
|
+
`SELECT turn_index, content_bytes FROM raw_transcript WHERE session_id = ?`,
|
|
247
|
+
)
|
|
248
|
+
.all(sessionId) as Array<Record<string, unknown>>)
|
|
249
|
+
: (mainDb
|
|
250
|
+
.prepare(
|
|
251
|
+
`SELECT turn_index, content_bytes FROM raw_transcript`,
|
|
252
|
+
)
|
|
253
|
+
.all() as Array<Record<string, unknown>>);
|
|
254
|
+
for (const r of transcriptRows) {
|
|
255
|
+
contentMap.set(String(r.turn_index), String(r.content_bytes ?? ""));
|
|
256
|
+
}
|
|
257
|
+
} catch {
|
|
258
|
+
// raw_transcript may not exist in the main db (dbMirror off) — return empty.
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Merge: only produce nodes where both a turn row AND transcript content exist.
|
|
263
|
+
const rows = turnRows
|
|
264
|
+
.filter((t) => contentMap.has(String(t.turn_index)))
|
|
265
|
+
.map((t) => ({
|
|
266
|
+
turn_index: t.turn_index,
|
|
267
|
+
role: t.role,
|
|
268
|
+
ended_at: t.ended_at,
|
|
269
|
+
content_bytes: contentMap.get(String(t.turn_index)) ?? "",
|
|
270
|
+
}));
|
|
271
|
+
|
|
238
272
|
if (rows.length === 0) return;
|
|
239
273
|
|
|
240
274
|
const turnContentNodes: MemoryGraphNode[] = [];
|
|
@@ -269,7 +303,7 @@ export function buildTurnContentNodes(
|
|
|
269
303
|
if (turnContentNodes.length > 1) {
|
|
270
304
|
const embeddings = turnContentNodes.map((n) => {
|
|
271
305
|
try {
|
|
272
|
-
return { id: n.id, vec: getOrComputeEmbedding(
|
|
306
|
+
return { id: n.id, vec: getOrComputeEmbedding(mainDb, n.textSnippet) };
|
|
273
307
|
} catch {
|
|
274
308
|
return { id: n.id, vec: null as number[] | null };
|
|
275
309
|
}
|
|
@@ -291,7 +325,7 @@ export function buildTurnContentNodes(
|
|
|
291
325
|
}
|
|
292
326
|
}
|
|
293
327
|
|
|
294
|
-
linkTurnToCheckpointEdges(
|
|
328
|
+
linkTurnToCheckpointEdges(mainDb, sessionId, turnContentNodes, edges);
|
|
295
329
|
}
|
|
296
330
|
|
|
297
331
|
nodes.push(...turnContentNodes);
|