pi-mega-compact 0.11.7 → 0.11.9
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-keys.js +18 -0
- package/dist/extensions/dashboard-server/gather-debug.js +129 -0
- package/dist/extensions/dashboard-server/routes-maintenance.js +6 -0
- package/dist/extensions/dashboard-server/routes-memory-map.js +8 -15
- package/dist/extensions/mega-commands.js +65 -5
- package/dist/extensions/mega-config.js +7 -0
- package/dist/extensions/mega-events/agent-handlers.js +74 -5
- package/dist/extensions/mega-events/context-handler.js +132 -3
- package/dist/extensions/mega-runtime/snapshot.js +1 -1
- package/dist/extensions/mega-trim.js +10 -0
- package/dist/src/config/turns.js +2 -1
- package/dist/src/memoryGraph/embedding.js +50 -0
- package/dist/src/memoryGraph/gates.js +379 -0
- package/dist/src/memoryGraph/sources.js +390 -0
- package/dist/src/memoryGraph.js +133 -259
- package/dist/src/store/sqlite/embedding-cache.js +71 -0
- package/dist/src/store/sqlite.js +1 -0
- package/dist/src/topics/cluster.js +10 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-DR9cLGDL.js → AchievementTiles-DFitrb4Y.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-DR9cLGDL.js.map → AchievementTiles-DFitrb4Y.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-svNUgJ1e.js → AchievementsTab-PJrmq8pa.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-svNUgJ1e.js.map → AchievementsTab-PJrmq8pa.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/CacheTab-C1WE7dEx.js +2 -0
- package/extensions/dashboard-client/dist/assets/CacheTab-C1WE7dEx.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{ConfigTab-CmmWheuW.js → ConfigTab-Dx-RpO8C.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ConfigTab-CmmWheuW.js.map → ConfigTab-Dx-RpO8C.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-CFE7jLsa.js → EventsTab-C63BEWEs.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-CFE7jLsa.js.map → EventsTab-C63BEWEs.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{GameTab-CJAX_th_.js → GameTab-DHpJE6km.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{GameTab-CJAX_th_.js.map → GameTab-DHpJE6km.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-BSv0GfBb.js +2 -0
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-BSv0GfBb.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-Cg1fgEx_.js +2 -0
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-Cg1fgEx_.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CHQscixy.js → MetricsTab-DCahxNOQ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CHQscixy.js.map → MetricsTab-DCahxNOQ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-DBs4DJdd.js → OverviewTab-RYvO1SZL.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-DBs4DJdd.js.map → OverviewTab-RYvO1SZL.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-DeFz76L5.js → ReposTab-BDGr_UXb.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-DeFz76L5.js.map → ReposTab-BDGr_UXb.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-BWNsj1OB.js → SessionsTab-ByrTnHaB.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-BWNsj1OB.js.map → SessionsTab-ByrTnHaB.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-uS6A0xmw.js → SetupTab-CELafKis.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-uS6A0xmw.js.map → SetupTab-CELafKis.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CiqCOQuL.js → TimeSavedCard-BLWtmfr_.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CiqCOQuL.js.map → TimeSavedCard-BLWtmfr_.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/TopicsTab-8Fsh_Igx.js +2 -0
- package/extensions/dashboard-client/dist/assets/TopicsTab-8Fsh_Igx.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{TurnsTab-CVz4f4-o.js → TurnsTab-D7mgXDe2.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-CVz4f4-o.js.map → TurnsTab-D7mgXDe2.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-DrktNOoT.js → index-h3jgEs6G.js} +10 -10
- package/extensions/dashboard-client/dist/assets/{index-DrktNOoT.js.map → index-h3jgEs6G.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-Bf5LOLSi.js → useSSE-C5lmipBF.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-Bf5LOLSi.js.map → useSSE-C5lmipBF.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/api/client.ts +5 -0
- package/extensions/dashboard-client/src/memory-map-layout.ts +161 -0
- package/extensions/dashboard-client/src/memory-map-shapes.tsx +126 -0
- package/extensions/dashboard-client/src/tabs/CacheTab.tsx +29 -2
- package/extensions/dashboard-client/src/tabs/MaintenanceTab.tsx +101 -0
- package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +201 -165
- package/extensions/dashboard-client/src/tabs/ReposTab.tsx +21 -0
- package/extensions/dashboard-client/src/tabs/TopicsTab.tsx +1 -1
- package/extensions/dashboard-server/api-contracts/index.ts +3 -1
- package/extensions/dashboard-server/api-contracts/maintenance.ts +13 -0
- package/extensions/dashboard-server/api-contracts/memory-map.ts +14 -6
- package/extensions/dashboard-server/gather-debug-keys.ts +18 -0
- package/extensions/dashboard-server/gather-debug.ts +136 -0
- package/extensions/dashboard-server/routes-maintenance.ts +7 -0
- package/extensions/dashboard-server/routes-memory-map.ts +21 -29
- package/extensions/mega-commands.ts +63 -6
- package/extensions/mega-config.ts +22 -0
- package/extensions/mega-events/agent-handlers.ts +88 -5
- package/extensions/mega-events/context-handler.ts +152 -3
- package/extensions/mega-runtime/snapshot.ts +1 -1
- package/extensions/mega-trim.ts +15 -0
- package/package.json +1 -1
- package/src/config/turns.ts +5 -2
- package/src/memoryGraph/embedding.ts +63 -0
- package/src/memoryGraph/gates.ts +451 -0
- package/src/memoryGraph/sources.ts +484 -0
- package/src/memoryGraph.ts +188 -350
- package/src/store/sqlite/embedding-cache.ts +81 -0
- package/src/store/sqlite.ts +1 -0
- package/src/topics/cluster.ts +11 -1
- package/extensions/dashboard-client/dist/assets/CacheTab-ASwhOMjR.js +0 -2
- package/extensions/dashboard-client/dist/assets/CacheTab-ASwhOMjR.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-P4SUsvMf.js +0 -2
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-P4SUsvMf.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js +0 -2
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/TopicsTab-BWWQxltv.js +0 -2
- package/extensions/dashboard-client/dist/assets/TopicsTab-BWWQxltv.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-h3jgEs6G.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-C5lmipBF.js.map
|
package/extensions/dashboard-client/dist/assets/{useSSE-Bf5LOLSi.js.map → useSSE-C5lmipBF.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSSE-
|
|
1
|
+
{"version":3,"file":"useSSE-C5lmipBF.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-h3jgEs6G.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="./assets/index-CUNn22iV.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
@@ -39,6 +39,7 @@ import type {
|
|
|
39
39
|
SchemaHealthResponse,
|
|
40
40
|
MaintenanceAction,
|
|
41
41
|
MaintenanceActionResult,
|
|
42
|
+
DebugBundleResponse,
|
|
42
43
|
ProviderCacheResponse,
|
|
43
44
|
CacheStripesResponse,
|
|
44
45
|
SetupStatusResponse,
|
|
@@ -284,6 +285,10 @@ export function postMaintenanceAction(
|
|
|
284
285
|
);
|
|
285
286
|
}
|
|
286
287
|
|
|
288
|
+
export function fetchDebugBundle(): Promise<DebugBundleResponse> {
|
|
289
|
+
return postJson<DebugBundleResponse>("/api/maintenance/gather-debug", {});
|
|
290
|
+
}
|
|
291
|
+
|
|
287
292
|
// ─── Setup wizard (P0b) ────────────────────────────────────────────────────
|
|
288
293
|
|
|
289
294
|
/** GET /api/setup-status — current embedder configuration. */
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memory-map-layout.ts — Force-directed layout engine for MemoryMapTab (D3).
|
|
3
|
+
*
|
|
4
|
+
* Uses Coulomb repulsion + Hooke attraction + center gravity to arrange nodes.
|
|
5
|
+
* No external graph library. Mutates layout in place.
|
|
6
|
+
* Self-contained types: only needs id + edges from graph data.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Types (self-contained — mirrors the subset used by the layout engine)
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
interface GraphNodeMin {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly nodeType: string;
|
|
16
|
+
readonly label: string;
|
|
17
|
+
readonly summaryTruncated: string;
|
|
18
|
+
readonly tokenEstimate: number;
|
|
19
|
+
readonly timestamp: number;
|
|
20
|
+
readonly dedupStatus: string | undefined;
|
|
21
|
+
readonly raptorLevel: number;
|
|
22
|
+
readonly topicSummary: string | undefined;
|
|
23
|
+
readonly decisionCount: number;
|
|
24
|
+
readonly textSnippet: string;
|
|
25
|
+
readonly sessionId: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface GraphEdgeMin {
|
|
29
|
+
readonly source: string;
|
|
30
|
+
readonly target: string;
|
|
31
|
+
readonly weight: number;
|
|
32
|
+
readonly type: "temporal" | "semantic" | "raptor_parent";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface Position {
|
|
36
|
+
x: number;
|
|
37
|
+
y: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface LayoutNode extends GraphNodeMin {
|
|
41
|
+
pos: Position;
|
|
42
|
+
vx: number;
|
|
43
|
+
vy: number;
|
|
44
|
+
pinned: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface LayoutEdge {
|
|
48
|
+
source: number;
|
|
49
|
+
target: number;
|
|
50
|
+
weight: number;
|
|
51
|
+
type: GraphEdge["type"];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface Layout {
|
|
55
|
+
nodes: LayoutNode[];
|
|
56
|
+
edges: LayoutEdge[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Constants
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
const REPULSION = 5_000;
|
|
64
|
+
const ATTRACTION = 0.005;
|
|
65
|
+
const CENTER_GRAVITY = 0.01;
|
|
66
|
+
const DAMPING = 0.9;
|
|
67
|
+
const MIN_VELOCITY = 0.1;
|
|
68
|
+
const SPEED_LIMIT = 50;
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Build
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
/** Build initial layout from graph data. */
|
|
75
|
+
export function buildLayout(data: { nodes: GraphNodeMin[]; edges: GraphEdgeMin[] }): Layout {
|
|
76
|
+
const nodes: LayoutNode[] = data.nodes.map((n, i) => {
|
|
77
|
+
const angle = (2 * Math.PI * i) / Math.max(data.nodes.length, 1);
|
|
78
|
+
const radius = 200;
|
|
79
|
+
return {
|
|
80
|
+
...n,
|
|
81
|
+
pos: { x: Math.cos(angle) * radius + 300, y: Math.sin(angle) * radius + 200 },
|
|
82
|
+
vx: 0,
|
|
83
|
+
vy: 0,
|
|
84
|
+
pinned: false,
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const nodeIndex = new Map<string, number>();
|
|
89
|
+
nodes.forEach((n, i) => nodeIndex.set(n.id, i));
|
|
90
|
+
|
|
91
|
+
const edges: LayoutEdge[] = data.edges
|
|
92
|
+
.map((e) => {
|
|
93
|
+
const si = nodeIndex.get(e.source);
|
|
94
|
+
const ti = nodeIndex.get(e.target);
|
|
95
|
+
if (si === undefined || ti === undefined || si === ti) return null;
|
|
96
|
+
return { source: si, target: ti, weight: e.weight, type: e.type };
|
|
97
|
+
})
|
|
98
|
+
.filter((e): e is LayoutEdge => e !== null);
|
|
99
|
+
|
|
100
|
+
return { nodes, edges };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Apply forces
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
/** Apply one iteration of the force simulation. Mutates layout in place. */
|
|
108
|
+
export function applyForces(layout: Layout): void {
|
|
109
|
+
const { nodes, edges } = layout;
|
|
110
|
+
const n = nodes.length;
|
|
111
|
+
if (n === 0) return;
|
|
112
|
+
|
|
113
|
+
for (let i = 0; i < n; i++) {
|
|
114
|
+
if (nodes[i].pinned) continue;
|
|
115
|
+
let fx = 0;
|
|
116
|
+
let fy = 0;
|
|
117
|
+
|
|
118
|
+
// Coulomb repulsion
|
|
119
|
+
for (let j = 0; j < n; j++) {
|
|
120
|
+
if (i === j) continue;
|
|
121
|
+
const dx = nodes[i].pos.x - nodes[j].pos.x;
|
|
122
|
+
const dy = nodes[i].pos.y - nodes[j].pos.y;
|
|
123
|
+
const distSq = dx * dx + dy * dy || 1;
|
|
124
|
+
const force = REPULSION / distSq;
|
|
125
|
+
const dist = Math.sqrt(distSq);
|
|
126
|
+
fx += (dx / dist) * force;
|
|
127
|
+
fy += (dy / dist) * force;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Hooke attraction along edges
|
|
131
|
+
for (const e of edges) {
|
|
132
|
+
let other: number;
|
|
133
|
+
if (e.source === i) other = e.target;
|
|
134
|
+
else if (e.target === i) other = e.source;
|
|
135
|
+
else continue;
|
|
136
|
+
const dx = nodes[other].pos.x - nodes[i].pos.x;
|
|
137
|
+
const dy = nodes[other].pos.y - nodes[i].pos.y;
|
|
138
|
+
const dist = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
139
|
+
fx += dx * ATTRACTION * e.weight;
|
|
140
|
+
fy += dy * ATTRACTION * e.weight;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Center gravity
|
|
144
|
+
fx -= nodes[i].pos.x * CENTER_GRAVITY;
|
|
145
|
+
fy -= nodes[i].pos.y * CENTER_GRAVITY;
|
|
146
|
+
|
|
147
|
+
nodes[i].vx = (nodes[i].vx + fx) * DAMPING;
|
|
148
|
+
nodes[i].vy = (nodes[i].vy + fy) * DAMPING;
|
|
149
|
+
|
|
150
|
+
const speed = Math.sqrt(nodes[i].vx * nodes[i].vx + nodes[i].vy * nodes[i].vy);
|
|
151
|
+
if (speed > SPEED_LIMIT) {
|
|
152
|
+
nodes[i].vx = (nodes[i].vx / speed) * SPEED_LIMIT;
|
|
153
|
+
nodes[i].vy = (nodes[i].vy / speed) * SPEED_LIMIT;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (speed > MIN_VELOCITY) {
|
|
157
|
+
nodes[i].pos.x += nodes[i].vx;
|
|
158
|
+
nodes[i].pos.y += nodes[i].vy;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memory-map-shapes.tsx — Node shape rendering utilities for MemoryMapTab (D3).
|
|
3
|
+
*
|
|
4
|
+
* Node shape encodes nodeType: checkpoint = filled circle, turn = hollow circle,
|
|
5
|
+
* turn-content = hollow+ring, memory = diamond.
|
|
6
|
+
* Delegate-shell pattern: thin rendering helpers, no state, no side effects.
|
|
7
|
+
*/
|
|
8
|
+
import type React from "react";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Types
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
export type NodeType = "checkpoint" | "turn" | "turn-content" | "memory";
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Constants
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
export const NODE_COLORS: Record<NodeType, string> = {
|
|
21
|
+
checkpoint: "#6366f1",
|
|
22
|
+
turn: "#58a6ff",
|
|
23
|
+
"turn-content": "#22c55e",
|
|
24
|
+
memory: "#f59e0b",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const NODE_TYPE_LABELS: Record<NodeType, string> = {
|
|
28
|
+
checkpoint: "Checkpoint",
|
|
29
|
+
turn: "Turn",
|
|
30
|
+
"turn-content": "Turn Content",
|
|
31
|
+
memory: "Memory",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Helpers
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
/** SVG path for a diamond centered at (0,0) with given half-width/height. */
|
|
39
|
+
function diamondPath(hw: number, hh: number): string {
|
|
40
|
+
return `M 0,-${hh} L ${hw},0 L 0,${hh} L -${hw},0 Z`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function nodeOpacity(dedupStatus: string | undefined): number {
|
|
44
|
+
if (!dedupStatus || dedupStatus === "active") return 1;
|
|
45
|
+
return 0.5;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Render function
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
/** Render an SVG shape for a node based on its nodeType. */
|
|
53
|
+
export function renderNodeShape(
|
|
54
|
+
cx: number,
|
|
55
|
+
cy: number,
|
|
56
|
+
radius: number,
|
|
57
|
+
nodeType: NodeType,
|
|
58
|
+
isSelected: boolean,
|
|
59
|
+
isFiltered: boolean,
|
|
60
|
+
dedupStatus: string | undefined,
|
|
61
|
+
): React.ReactElement {
|
|
62
|
+
const fill = isFiltered ? NODE_COLORS[nodeType] ?? "#6366f1" : "#d1d5db";
|
|
63
|
+
const strokeColor = isSelected ? "#ef4444" : "none";
|
|
64
|
+
const opacity = nodeOpacity(dedupStatus);
|
|
65
|
+
|
|
66
|
+
switch (nodeType) {
|
|
67
|
+
case "checkpoint":
|
|
68
|
+
return (
|
|
69
|
+
<circle
|
|
70
|
+
cx={cx}
|
|
71
|
+
cy={cy}
|
|
72
|
+
r={isSelected ? radius + 3 : radius}
|
|
73
|
+
fill={fill}
|
|
74
|
+
opacity={opacity}
|
|
75
|
+
stroke={strokeColor}
|
|
76
|
+
strokeWidth={isSelected ? 2 : 0}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
case "turn":
|
|
80
|
+
return (
|
|
81
|
+
<circle
|
|
82
|
+
cx={cx}
|
|
83
|
+
cy={cy}
|
|
84
|
+
r={isSelected ? radius + 3 : radius}
|
|
85
|
+
fill="none"
|
|
86
|
+
stroke={fill}
|
|
87
|
+
strokeWidth={2.5}
|
|
88
|
+
opacity={opacity}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
case "turn-content":
|
|
92
|
+
return (
|
|
93
|
+
<g>
|
|
94
|
+
<circle
|
|
95
|
+
cx={cx}
|
|
96
|
+
cy={cy}
|
|
97
|
+
r={isSelected ? radius + 3 : radius}
|
|
98
|
+
fill="none"
|
|
99
|
+
stroke={fill}
|
|
100
|
+
strokeWidth={2.5}
|
|
101
|
+
opacity={opacity}
|
|
102
|
+
/>
|
|
103
|
+
<circle
|
|
104
|
+
cx={cx}
|
|
105
|
+
cy={cy}
|
|
106
|
+
r={Math.max(3, radius * 0.3)}
|
|
107
|
+
fill={fill}
|
|
108
|
+
opacity={opacity}
|
|
109
|
+
/>
|
|
110
|
+
</g>
|
|
111
|
+
);
|
|
112
|
+
case "memory":
|
|
113
|
+
const hw = radius * 0.85;
|
|
114
|
+
const hh = radius * 0.85;
|
|
115
|
+
return (
|
|
116
|
+
<path
|
|
117
|
+
d={diamondPath(hw, hh)}
|
|
118
|
+
transform={`translate(${cx}, ${cy})`}
|
|
119
|
+
fill={fill}
|
|
120
|
+
opacity={opacity}
|
|
121
|
+
stroke={strokeColor}
|
|
122
|
+
strokeWidth={isSelected ? 2 : 0}
|
|
123
|
+
/>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
* 5s polling on provider cache and activity snapshot; 15s on stripes + trend.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import
|
|
15
|
-
import { useCallback } from "react";
|
|
14
|
+
import { useState, useCallback } from "react";
|
|
16
15
|
import type {
|
|
17
16
|
SnapshotResponse,
|
|
18
17
|
ProviderCacheResponse,
|
|
@@ -33,6 +32,7 @@ import { StripeDistributionCard } from "../components/StripeDistributionCard";
|
|
|
33
32
|
import { CacheHitRateTrendCard } from "../components/CacheHitRateTrendCard";
|
|
34
33
|
|
|
35
34
|
export default function CacheTab(): React.ReactElement {
|
|
35
|
+
const [infoExpanded, setInfoExpanded] = useState(false);
|
|
36
36
|
/* --- Provider prompt cache (5s poll) --- */
|
|
37
37
|
const {
|
|
38
38
|
data: providerCache,
|
|
@@ -75,6 +75,33 @@ export default function CacheTab(): React.ReactElement {
|
|
|
75
75
|
|
|
76
76
|
return (
|
|
77
77
|
<div className="cache-tab">
|
|
78
|
+
<div className="card-section">
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
className="info-toggle"
|
|
82
|
+
onClick={() => setInfoExpanded(!infoExpanded)}
|
|
83
|
+
>
|
|
84
|
+
{infoExpanded ? "Hide Guidance" : "Show Cache-Friendly Prompt Ordering Guidance"}
|
|
85
|
+
</button>
|
|
86
|
+
{infoExpanded && (
|
|
87
|
+
<div className="info-panel">
|
|
88
|
+
<h3>Cache-Friendly Prompt Ordering</h3>
|
|
89
|
+
<ul>
|
|
90
|
+
<li>Keep system prompts and stable context at the top of your conversation — the provider caches the leading prefix.</li>
|
|
91
|
+
<li>Tool results and volatile content are automatically moved to the tail by message separation (<code>MEGACOMPACT_MESSAGE_SEPARATION</code>) so they don't invalidate the cache prefix.</li>
|
|
92
|
+
<li>Cache striping (<code>MEGACOMPACT_CACHE_STRIPING</code>) further orders stable context by a stability score so the most durable chunks lead.</li>
|
|
93
|
+
<li>Avoid inserting new instructions mid-conversation — prepend them instead.</li>
|
|
94
|
+
</ul>
|
|
95
|
+
{snapshot && (
|
|
96
|
+
<div className="cache-status-indicator">
|
|
97
|
+
<strong>Status:</strong>{" "}
|
|
98
|
+
{snapshot.config.messageSeparation ? "Separation Enabled" : "Separation Disabled (Check env)"} |{" "}
|
|
99
|
+
{snapshot.config.cacheStriping ? "Striping Enabled" : "Striping Disabled (Check env)"}
|
|
100
|
+
</div>
|
|
101
|
+
)}
|
|
102
|
+
</div>
|
|
103
|
+
)}
|
|
104
|
+
</div>
|
|
78
105
|
{/* ==============================================================
|
|
79
106
|
Section 1 -- Provider Prompt Cache
|
|
80
107
|
============================================================== */}
|
|
@@ -16,12 +16,14 @@ import type {
|
|
|
16
16
|
SchemaHealthResponse,
|
|
17
17
|
MaintenanceAction,
|
|
18
18
|
MaintenanceActionResult,
|
|
19
|
+
DebugBundleResponse,
|
|
19
20
|
} from "@contracts";
|
|
20
21
|
import { useApi } from "../hooks/useApi";
|
|
21
22
|
import {
|
|
22
23
|
fetchDbStats,
|
|
23
24
|
fetchSchemaHealth,
|
|
24
25
|
postMaintenanceAction,
|
|
26
|
+
fetchDebugBundle,
|
|
25
27
|
} from "../api/client";
|
|
26
28
|
|
|
27
29
|
// ---------------------------------------------------------------------------
|
|
@@ -419,6 +421,104 @@ function ActionsCard(): React.ReactElement {
|
|
|
419
421
|
);
|
|
420
422
|
}
|
|
421
423
|
|
|
424
|
+
// ---------------------------------------------------------------------------
|
|
425
|
+
// Debug bundle card — gather diagnostic info for bug reports
|
|
426
|
+
// ---------------------------------------------------------------------------
|
|
427
|
+
|
|
428
|
+
function DebugBundleCard(): React.ReactElement {
|
|
429
|
+
const [bundle, setBundle] = useState<DebugBundleResponse | null>(null);
|
|
430
|
+
const [loading, setLoading] = useState(false);
|
|
431
|
+
const [error, setError] = useState<string | null>(null);
|
|
432
|
+
const [showJson, setShowJson] = useState(false);
|
|
433
|
+
|
|
434
|
+
const gather = useCallback(async () => {
|
|
435
|
+
setLoading(true);
|
|
436
|
+
setError(null);
|
|
437
|
+
try {
|
|
438
|
+
const res = await fetchDebugBundle();
|
|
439
|
+
setBundle(res);
|
|
440
|
+
setShowJson(true);
|
|
441
|
+
} catch (err) {
|
|
442
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
443
|
+
} finally {
|
|
444
|
+
setLoading(false);
|
|
445
|
+
}
|
|
446
|
+
}, []);
|
|
447
|
+
|
|
448
|
+
const copyToClipboard = useCallback(() => {
|
|
449
|
+
if (!bundle) return;
|
|
450
|
+
const text = JSON.stringify(bundle, null, 2);
|
|
451
|
+
void navigator.clipboard.writeText(text).catch(() => {});
|
|
452
|
+
}, [bundle]);
|
|
453
|
+
|
|
454
|
+
const downloadJson = useCallback(() => {
|
|
455
|
+
if (!bundle) return;
|
|
456
|
+
const text = JSON.stringify(bundle, null, 2);
|
|
457
|
+
const blob = new Blob([text], { type: "application/json" });
|
|
458
|
+
const url = URL.createObjectURL(blob);
|
|
459
|
+
const a = document.createElement("a");
|
|
460
|
+
a.href = url;
|
|
461
|
+
a.download = `mega-compact-debug-bundle-${bundle.builtAt}.json`;
|
|
462
|
+
a.click();
|
|
463
|
+
URL.revokeObjectURL(url);
|
|
464
|
+
}, [bundle]);
|
|
465
|
+
|
|
466
|
+
const criticalCount = bundle?.criticalEvents.length ?? 0;
|
|
467
|
+
|
|
468
|
+
return (
|
|
469
|
+
<div className="card">
|
|
470
|
+
<div className="card-header">
|
|
471
|
+
Debug Bundle
|
|
472
|
+
{criticalCount > 0 && (
|
|
473
|
+
<span className="text-warn" style={{ marginLeft: "1em" }}>
|
|
474
|
+
{criticalCount} critical event{criticalCount !== 1 ? "s" : ""} found
|
|
475
|
+
</span>
|
|
476
|
+
)}
|
|
477
|
+
</div>
|
|
478
|
+
<div className="card-body">
|
|
479
|
+
<p className="text-muted" style={{ marginBottom: "0.75em" }}>
|
|
480
|
+
Gather a diagnostic bundle (recent events, config flags, schema health,
|
|
481
|
+
store stats) to attach to a bug report. Critical/compaction events are
|
|
482
|
+
highlighted.
|
|
483
|
+
</p>
|
|
484
|
+
<button onClick={gather} disabled={loading} style={{ marginRight: "0.5em" }}>
|
|
485
|
+
{loading ? "Gathering…" : "Gather Debug Logs"}
|
|
486
|
+
</button>
|
|
487
|
+
{error && <div className="text-error">{error}</div>}
|
|
488
|
+
{bundle && showJson && (
|
|
489
|
+
<>
|
|
490
|
+
<div style={{ marginTop: "0.75em", marginBottom: "0.5em" }}>
|
|
491
|
+
<button onClick={copyToClipboard} style={{ marginRight: "0.5em" }}>
|
|
492
|
+
Copy to clipboard
|
|
493
|
+
</button>
|
|
494
|
+
<button onClick={downloadJson}>Download JSON</button>
|
|
495
|
+
<button
|
|
496
|
+
onClick={() => setShowJson((v) => !v)}
|
|
497
|
+
style={{ marginLeft: "0.5em" }}
|
|
498
|
+
>
|
|
499
|
+
{showJson ? "Hide" : "Show"}
|
|
500
|
+
</button>
|
|
501
|
+
</div>
|
|
502
|
+
{criticalCount > 0 && (
|
|
503
|
+
<div className="stat-section">
|
|
504
|
+
<div className="stat-label text-warn">
|
|
505
|
+
Critical events ({criticalCount})
|
|
506
|
+
</div>
|
|
507
|
+
<pre className="text-mono" style={{ maxHeight: "12em", overflow: "auto" }}>
|
|
508
|
+
{JSON.stringify(bundle.criticalEvents, null, 2)}
|
|
509
|
+
</pre>
|
|
510
|
+
</div>
|
|
511
|
+
)}
|
|
512
|
+
<pre className="text-mono" style={{ maxHeight: "24em", overflow: "auto" }}>
|
|
513
|
+
{JSON.stringify(bundle, null, 2)}
|
|
514
|
+
</pre>
|
|
515
|
+
</>
|
|
516
|
+
)}
|
|
517
|
+
</div>
|
|
518
|
+
</div>
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
|
|
422
522
|
// ---------------------------------------------------------------------------
|
|
423
523
|
// Main tab component
|
|
424
524
|
// ---------------------------------------------------------------------------
|
|
@@ -453,6 +553,7 @@ export default function MaintenanceTab(): React.ReactElement {
|
|
|
453
553
|
<DbStatsCard data={dbStats} loading={statsLoading} error={statsError} />
|
|
454
554
|
</div>
|
|
455
555
|
<ActionsCard />
|
|
556
|
+
<DebugBundleCard />
|
|
456
557
|
</div>
|
|
457
558
|
);
|
|
458
559
|
}
|