pi-mega-compact 0.13.7 → 0.14.0
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/README.md +13 -6
- package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +6 -0
- package/dist/extensions/dashboard-server/index-reader.js +49 -26
- package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +7 -0
- package/dist/extensions/dashboard-server/routes-raptor.js +60 -0
- package/dist/extensions/dashboard-server/routes.js +1 -1
- package/dist/extensions/dashboard-server/server.js +3 -1
- package/dist/extensions/mega-pipeline/recall.js +24 -16
- package/dist/src/config/dedup.js +2 -1
- package/dist/src/costApi.js +113 -0
- package/dist/src/dedup/raptor/incremental.js +31 -3
- package/dist/src/dedup/raptor/index.js +10 -6
- package/dist/src/vector-search.js +3 -3
- package/extensions/dashboard-client/dist/assets/{AreaChart-j2VOSRp6.js → AreaChart-C68tGr2J.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AreaChart-j2VOSRp6.js.map → AreaChart-C68tGr2J.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-Dr1HeMfc.js → CacheTab-CDyZqMO-.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-Dr1HeMfc.js.map → CacheTab-CDyZqMO-.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-DRG2k7FS.js → EventsTab-ByPYMJVG.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-DRG2k7FS.js.map → EventsTab-ByPYMJVG.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-DGE9PpW3.js → HealthTab-CeHQR0py.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-DGE9PpW3.js.map → HealthTab-CeHQR0py.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DeYRhF4q.js → MaintenanceTab-ceTD_c1A.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DeYRhF4q.js.map → MaintenanceTab-ceTD_c1A.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-CRUdVR7u.js +2 -0
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-DSnSO3nG.js.map → MemoryMapTab-CRUdVR7u.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-RIzNc1Uj.js → MetricsTab-DP7Kmdu9.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-RIzNc1Uj.js.map → MetricsTab-DP7Kmdu9.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-_1BJ4Bmh.js → OverviewTab-4CGP0djv.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-_1BJ4Bmh.js.map → OverviewTab-4CGP0djv.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-DkU7ywZB.js → ReposTab-DUrP7Efa.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-DkU7ywZB.js.map → ReposTab-DUrP7Efa.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-Bq_F_aOk.js → SessionsTab-Cg-IsijD.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-Bq_F_aOk.js.map → SessionsTab-Cg-IsijD.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-BkBD2Y97.js → SetupTab-ZE1daXjN.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-BkBD2Y97.js.map → SetupTab-ZE1daXjN.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BXzFKU7C.js → TimeSavedCard-BkTgi3Af.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BXzFKU7C.js.map → TimeSavedCard-BkTgi3Af.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-Bjpp3vBH.js → TurnsTab-CZvudHQF.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-Bjpp3vBH.js.map → TurnsTab-CZvudHQF.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{WikiTab-dBN2EA5G.js → WikiTab-BVAnGp8_.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{WikiTab-dBN2EA5G.js.map → WikiTab-BVAnGp8_.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{button-Bt0b5QVp.js → button-CUrzhi2A.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{button-Bt0b5QVp.js.map → button-CUrzhi2A.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-D5CafXtg.js → card-BsfojHfp.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-D5CafXtg.js.map → card-BsfojHfp.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-GPHRnEN7.js → index-OM3w-l2T.js} +11 -11
- package/extensions/dashboard-client/dist/assets/index-OM3w-l2T.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{switch-CY5Gwj1o.js → switch-qSz2Un3t.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-CY5Gwj1o.js.map → switch-qSz2Un3t.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-C9HpkIfH.js → toggle-DyBKcLe-.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-C9HpkIfH.js.map → toggle-DyBKcLe-.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-2JFrLDlE.js → useSSE-DqVouqww.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-2JFrLDlE.js.map → useSSE-DqVouqww.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/api/client.ts +10 -0
- package/extensions/dashboard-client/src/tabs/MemoryMapTab/RaptorTreeView.tsx +58 -4
- package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +9 -1
- package/extensions/dashboard-server/api-contracts/index.ts +2 -2
- package/extensions/dashboard-server/api-contracts/raptor.ts +19 -0
- package/extensions/dashboard-server/index-reader.ts +28 -5
- package/extensions/dashboard-server/routes-rag-settings-helpers.ts +17 -0
- package/extensions/dashboard-server/routes-raptor.ts +88 -1
- package/extensions/dashboard-server/routes.ts +1 -1
- package/extensions/dashboard-server/server.ts +2 -0
- package/extensions/mega-pipeline/recall.ts +30 -21
- package/package.json +1 -1
- package/src/config/dedup.ts +2 -1
- package/src/costApi.ts +131 -0
- package/src/dedup/raptor/incremental.ts +48 -3
- package/src/dedup/raptor/index.ts +10 -6
- package/src/vector-search.ts +3 -3
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-DSnSO3nG.js +0 -2
- package/extensions/dashboard-client/dist/assets/index-GPHRnEN7.js.map +0 -1
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
readProviderCacheForRepo,
|
|
19
19
|
} from "../../src/store/sqlite/perf-samples.js";
|
|
20
20
|
import { lookupModelInputRate } from "../../src/pricing.js";
|
|
21
|
+
import { costApiConfig, lookupCostApiPricing } from "../../src/costApi.js";
|
|
21
22
|
|
|
22
23
|
export function getIndexDir(): string {
|
|
23
24
|
const override = process.env.MEGACOMPACT_INDEX_DIR;
|
|
@@ -41,7 +42,29 @@ export function readIndex(): IndexIndex | null {
|
|
|
41
42
|
const rows = db
|
|
42
43
|
.prepare("SELECT * FROM repo_registry ORDER BY last_seen DESC")
|
|
43
44
|
.all() as Record<string, unknown>[];
|
|
44
|
-
|
|
45
|
+
// Lazily fetch cost-API pricing once per readIndex call (cached for 1 hour
|
|
46
|
+
// inside costApi.ts). Avoids per-row network calls.
|
|
47
|
+
const costApi = costApiConfig();
|
|
48
|
+
const mapped: IndexRepo[] = rows.map((r) => {
|
|
49
|
+
const modelName = String(r.model_name ?? "");
|
|
50
|
+
const localRate = (r.input_rate as number | null) ||
|
|
51
|
+
lookupModelInputRate(modelName) ||
|
|
52
|
+
null;
|
|
53
|
+
let inputRate = localRate;
|
|
54
|
+
let outputRate = (r.output_rate as number | null) ?? null;
|
|
55
|
+
// Fall back to the cost API when the local table has no data.
|
|
56
|
+
if (costApi.enabled && (inputRate == null || outputRate == null) && modelName) {
|
|
57
|
+
const apiPricing = lookupCostApiPricing(modelName);
|
|
58
|
+
if (apiPricing) {
|
|
59
|
+
if (inputRate == null && apiPricing.inputRate != null) {
|
|
60
|
+
inputRate = apiPricing.inputRate;
|
|
61
|
+
}
|
|
62
|
+
if (outputRate == null && apiPricing.outputRate != null) {
|
|
63
|
+
outputRate = apiPricing.outputRate;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
45
68
|
repoRoot: String(r.repo_root ?? ""),
|
|
46
69
|
displayName: String(r.display_name ?? ""),
|
|
47
70
|
stateDir: String(r.state_dir ?? ""),
|
|
@@ -52,9 +75,8 @@ export function readIndex(): IndexIndex | null {
|
|
|
52
75
|
provider: (r.provider as string | null) ?? null,
|
|
53
76
|
providerName: (r.provider_name as string | null) ?? null,
|
|
54
77
|
modelName: (r.model_name as string | null) ?? null,
|
|
55
|
-
inputRate
|
|
56
|
-
|
|
57
|
-
outputRate: (r.output_rate as number | null) ?? null,
|
|
78
|
+
inputRate,
|
|
79
|
+
outputRate,
|
|
58
80
|
lastSeen: Number(r.last_seen ?? 0),
|
|
59
81
|
// Defaults — enriched below from each repo's own store.
|
|
60
82
|
tokensKept: 0,
|
|
@@ -66,7 +88,8 @@ export function readIndex(): IndexIndex | null {
|
|
|
66
88
|
providerCachePct: null,
|
|
67
89
|
providerCacheRead: null,
|
|
68
90
|
providerCacheWrite: null,
|
|
69
|
-
}
|
|
91
|
+
};
|
|
92
|
+
});
|
|
70
93
|
// Enrich each repo with per-store token + model detail read directly via
|
|
71
94
|
// node:sqlite (same zero-dependency invariant as readIndex; no store graph
|
|
72
95
|
// import). Best-effort: a missing/corrupt store degrades to the defaults
|
|
@@ -272,6 +272,23 @@ export const SETTINGS: ReadonlyArray<{
|
|
|
272
272
|
num("MEGACOMPACT_EMBED_CACHE", "Embed Cache Size", "Embedding cache entries (0 = disabled)", 256, 0, 10000),
|
|
273
273
|
],
|
|
274
274
|
},
|
|
275
|
+
{
|
|
276
|
+
name: "Cost API",
|
|
277
|
+
settings: [
|
|
278
|
+
boolDirect(
|
|
279
|
+
"MEGACOMPACT_COST_API_ENABLED",
|
|
280
|
+
"Cost API Lookup",
|
|
281
|
+
"Fetch model pricing from an external API (PREVENT-PI-004 network exception — opt-in). Enriches the dashboard Cache/Repos tabs with real $/token rates for models not in the local pricing table.",
|
|
282
|
+
false,
|
|
283
|
+
),
|
|
284
|
+
str(
|
|
285
|
+
"MEGACOMPACT_COST_API_URL",
|
|
286
|
+
"Cost API URL",
|
|
287
|
+
"OpenRouter-compatible model pricing endpoint (e.g. https://openrouter.ai/api/v1/models). Only contacted when Cost API Lookup is ON.",
|
|
288
|
+
"",
|
|
289
|
+
),
|
|
290
|
+
],
|
|
291
|
+
},
|
|
275
292
|
];
|
|
276
293
|
|
|
277
294
|
/** Flat index of every setting by key for O(1) lookup + validation. */
|
|
@@ -10,7 +10,7 @@ import { createRequire } from "node:module";
|
|
|
10
10
|
import type { ServerResponse, IncomingMessage } from "node:http";
|
|
11
11
|
import { parse as parseUrl } from "node:url";
|
|
12
12
|
import type { RouteContext } from "./routes-core.js";
|
|
13
|
-
import type { RaptorTreeResponse, RaptorNodeDTO } from "./api-contracts/raptor.js";
|
|
13
|
+
import type { RaptorTreeResponse, RaptorNodeDTO, RaptorBuildHistoryResponse, RaptorBuildHistoryDTO } from "./api-contracts/raptor.js";
|
|
14
14
|
import { openStore } from "../../src/store/sqlite.js";
|
|
15
15
|
|
|
16
16
|
const _req = createRequire(import.meta.url);
|
|
@@ -126,3 +126,90 @@ export function handleRaptorTree(
|
|
|
126
126
|
return true;
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
|
+
|
|
130
|
+
/** Find the most recent session that has build history rows (else null). */
|
|
131
|
+
function latestBuildSession(stateDir: string): string | null {
|
|
132
|
+
try {
|
|
133
|
+
const db = openStore(stateDir);
|
|
134
|
+
const row = db
|
|
135
|
+
.prepare(
|
|
136
|
+
"SELECT DISTINCT session_id FROM raptor_build_history ORDER BY completed_at DESC LIMIT 1",
|
|
137
|
+
)
|
|
138
|
+
.get() as { session_id: string } | undefined;
|
|
139
|
+
return row?.session_id ?? null;
|
|
140
|
+
} catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function handleRaptorBuildHistory(
|
|
146
|
+
req: IncomingMessage,
|
|
147
|
+
res: ServerResponse,
|
|
148
|
+
ctx: RouteContext,
|
|
149
|
+
): boolean {
|
|
150
|
+
if (req.method !== "GET") return false;
|
|
151
|
+
const parsed = parseUrl(req.url ?? "", true);
|
|
152
|
+
if (parsed.pathname !== "/api/raptor-build-history") return false;
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const { listBuildHistory } = _req(
|
|
156
|
+
"../../src/dedup/raptor/buildHistory.js",
|
|
157
|
+
) as {
|
|
158
|
+
listBuildHistory: (
|
|
159
|
+
sessionId: string,
|
|
160
|
+
stateDir: string,
|
|
161
|
+
) => Array<{
|
|
162
|
+
buildId: string;
|
|
163
|
+
sessionId: string;
|
|
164
|
+
startedAt: number;
|
|
165
|
+
completedAt: number;
|
|
166
|
+
nodeCount: number;
|
|
167
|
+
leafCount: number;
|
|
168
|
+
depth: number;
|
|
169
|
+
coherenceScore: number | null;
|
|
170
|
+
timedOut: boolean;
|
|
171
|
+
}>;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
let sessionId =
|
|
175
|
+
typeof parsed.query.sessionId === "string" ? parsed.query.sessionId : "";
|
|
176
|
+
|
|
177
|
+
if (!sessionId) {
|
|
178
|
+
sessionId = latestBuildSession(ctx.stateDir) ?? "";
|
|
179
|
+
if (!sessionId) {
|
|
180
|
+
const body: RaptorBuildHistoryResponse = { builds: [], empty: true };
|
|
181
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
182
|
+
res.end(JSON.stringify(body));
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const rows = listBuildHistory(sessionId, ctx.stateDir);
|
|
188
|
+
if (rows.length === 0) {
|
|
189
|
+
const body: RaptorBuildHistoryResponse = { builds: [], empty: true };
|
|
190
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
191
|
+
res.end(JSON.stringify(body));
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const dtos: RaptorBuildHistoryDTO[] = rows.map((r) => ({
|
|
196
|
+
buildId: r.buildId,
|
|
197
|
+
sessionId: r.sessionId,
|
|
198
|
+
startedAt: r.startedAt,
|
|
199
|
+
completedAt: r.completedAt,
|
|
200
|
+
nodeCount: r.nodeCount,
|
|
201
|
+
leafCount: r.leafCount,
|
|
202
|
+
depth: r.depth,
|
|
203
|
+
coherenceScore: r.coherenceScore,
|
|
204
|
+
timedOut: r.timedOut,
|
|
205
|
+
}));
|
|
206
|
+
const body: RaptorBuildHistoryResponse = { builds: dtos, empty: false };
|
|
207
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
208
|
+
res.end(JSON.stringify(body));
|
|
209
|
+
return true;
|
|
210
|
+
} catch (e) {
|
|
211
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
212
|
+
res.end(JSON.stringify({ error: String(e) }));
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -23,7 +23,7 @@ export { handleProviderCache } from "./routes-cache.js";
|
|
|
23
23
|
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
|
-
export { handleRaptorTree } from "./routes-raptor.js";
|
|
26
|
+
export { handleRaptorTree, handleRaptorBuildHistory } from "./routes-raptor.js";
|
|
27
27
|
export { handleCacheStripes } from "./routes-cache.js";
|
|
28
28
|
export { handleContextHealth, handleCachePoison, handleHealthSettings } from "./routes-health.js";
|
|
29
29
|
export { handleEmbedderHealth } from "./routes-embedder-health.js";
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
handleSetupConfigure,
|
|
47
47
|
handleMemoryMap,
|
|
48
48
|
handleRaptorTree,
|
|
49
|
+
handleRaptorBuildHistory,
|
|
49
50
|
handleContextHealth,
|
|
50
51
|
handleCachePoison,
|
|
51
52
|
handleHealthSettings,
|
|
@@ -260,6 +261,7 @@ export async function launchDashboardServer(
|
|
|
260
261
|
if (handleSetupConfigure(req, res, ctx)) return;
|
|
261
262
|
if (handleMemoryMap(req, res, ctx)) return;
|
|
262
263
|
if (handleRaptorTree(req, res, ctx)) return;
|
|
264
|
+
if (handleRaptorBuildHistory(req, res, ctx)) return;
|
|
263
265
|
if (handleContextHealth(req, res, ctx)) return;
|
|
264
266
|
if (handleCachePoison(req, res, ctx)) return;
|
|
265
267
|
if (handleHealthSettings(req, res, ctx)) return;
|
|
@@ -107,16 +107,23 @@ export function doRecall(
|
|
|
107
107
|
);
|
|
108
108
|
runtime.lastWhy = `why: recalled@${scorePct}% (${result.toInject.length} chkpt)`;
|
|
109
109
|
}
|
|
110
|
+
let sumTokens = 0;
|
|
111
|
+
for (const h of result.toInject) sumTokens += h.checkpoint.tokenEstimate;
|
|
110
112
|
if (result.toInject.length > 0) {
|
|
111
|
-
let sumTokens = 0;
|
|
112
|
-
for (const h of result.toInject) sumTokens += h.checkpoint.tokenEstimate;
|
|
113
113
|
runtime.rt.recallInjections += result.toInject.length;
|
|
114
114
|
runtime.rt.cacheHitTokens += sumTokens;
|
|
115
115
|
incRecallInjected(result.toInject.length, runtime.currentStateDir);
|
|
116
116
|
incCacheHitTokens(sumTokens, runtime.currentStateDir);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
}
|
|
118
|
+
// S43: record recall provenance — which checkpoints/summaries served this
|
|
119
|
+
// turn, their score + source path. Linked to the turn row written at
|
|
120
|
+
// turn_end via the conversation+turnIndex. Best-effort + non-fatal.
|
|
121
|
+
// Persists telemetry (HyDE + recall metrics) even when recall returned
|
|
122
|
+
// no hits, so empty-recall HyDE invocations are still visible in the
|
|
123
|
+
// dashboard Turns/Metrics tabs.
|
|
124
|
+
const hasTelemetry =
|
|
125
|
+
result.hydeInfo != null || result.recallMetrics != null;
|
|
126
|
+
if (result.toInject.length > 0 || hasTelemetry) {
|
|
120
127
|
try {
|
|
121
128
|
const convId = ensureConversationIdFor(
|
|
122
129
|
config,
|
|
@@ -136,22 +143,24 @@ export function doRecall(
|
|
|
136
143
|
},
|
|
137
144
|
runtime.currentStateDir,
|
|
138
145
|
);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
146
|
+
if (result.toInject.length > 0) {
|
|
147
|
+
recordRecallWrite(
|
|
148
|
+
config,
|
|
149
|
+
turnId,
|
|
150
|
+
result.toInject.map((h) => ({
|
|
151
|
+
checkpointId: h.checkpoint.checkpointId,
|
|
152
|
+
score: h.score,
|
|
153
|
+
source:
|
|
154
|
+
h.raptorLevel !== undefined
|
|
155
|
+
? "raptor"
|
|
156
|
+
: h.repoId
|
|
157
|
+
? "cross-repo"
|
|
158
|
+
: "flat",
|
|
159
|
+
raptorLevel: h.raptorLevel,
|
|
160
|
+
})),
|
|
161
|
+
runtime.currentStateDir,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
155
164
|
} catch {
|
|
156
165
|
/* non-fatal: recall provenance never breaks the recall path */
|
|
157
166
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mega-compact",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
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",
|
package/src/config/dedup.ts
CHANGED
|
@@ -89,7 +89,8 @@ export function loadDedupConfig(): DedupConfigShape {
|
|
|
89
89
|
L2_ENABLED: envBool("MEGACOMPACT_L2_ENABLED", true),
|
|
90
90
|
// Fix D: RAPTOR promoted to live recall. Default ON; canary.ts sequences it
|
|
91
91
|
// last (L0→L1→L2→RAPTOR) and auto-disables on p95 breach, so promotion is
|
|
92
|
-
// safe.
|
|
92
|
+
// safe. Shadow mode (RAPTOR_SHADOW_MODE=true) opts out of serving while
|
|
93
|
+
// still building + persisting the tree (transition/eval use).
|
|
93
94
|
RAPTOR_ENABLED: envBool("MEGACOMPACT_RAPTOR_ENABLED", true),
|
|
94
95
|
MARK_ONLY_L0: envBool("MEGACOMPACT_MARK_ONLY_L0", false),
|
|
95
96
|
MARK_ONLY_L1: envBool("MEGACOMPACT_MARK_ONLY_L1", false),
|
package/src/costApi.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* costApi.ts — optional model pricing lookup from an external API.
|
|
3
|
+
*
|
|
4
|
+
* PREVENT-PI-004 EXEMPTION: This module makes network calls to fetch model
|
|
5
|
+
* pricing data. It is disabled by default and must be explicitly opted in via
|
|
6
|
+
* MEGACOMPACT_COST_API_ENABLED=true + MEGACOMPACT_COST_API_URL=<url>.
|
|
7
|
+
*
|
|
8
|
+
* The dashboard server uses this to enrich the inputRate/outputRate fields
|
|
9
|
+
* when the local pricing table (pricing.ts) has no data for a model.
|
|
10
|
+
*
|
|
11
|
+
* Results are cached for 1 hour to minimize network calls.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { spawnSync } from "node:child_process"; // guardrails-allow PREVENT-PI-004: user-opted-in cost API — fetches model pricing from a user-configured endpoint, never called unless MEGACOMPACT_COST_API_ENABLED=true
|
|
15
|
+
|
|
16
|
+
export interface ModelPricing {
|
|
17
|
+
inputRate: number | null;
|
|
18
|
+
outputRate: number | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CostApiConfig {
|
|
22
|
+
enabled: boolean;
|
|
23
|
+
url: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let _cache: { timestamp: number; data: Map<string, ModelPricing> } | null = null;
|
|
27
|
+
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
28
|
+
|
|
29
|
+
export function costApiConfig(): CostApiConfig {
|
|
30
|
+
const enabled =
|
|
31
|
+
process.env.MEGACOMPACT_COST_API_ENABLED === "true" ||
|
|
32
|
+
process.env.MEGACOMPACT_COST_API_ENABLED === "1";
|
|
33
|
+
const url = process.env.MEGACOMPACT_COST_API_URL ?? "";
|
|
34
|
+
return { enabled, url };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Fetch model pricing from the configured API endpoint.
|
|
39
|
+
*
|
|
40
|
+
* Expects a JSON response of shape:
|
|
41
|
+
* { "data": [{ "id": "model-name", "pricing": { "prompt": "0.000003", "completion": "0.000015" } }] }
|
|
42
|
+
* (OpenRouter-compatible format). Values are USD per token as strings.
|
|
43
|
+
*
|
|
44
|
+
* Returns a map keyed by lowercase model id → { inputRate, outputRate }.
|
|
45
|
+
* Returns an empty map on any error. Never throws.
|
|
46
|
+
*/
|
|
47
|
+
export function fetchModelPricing(): Map<string, ModelPricing> {
|
|
48
|
+
const cfg = costApiConfig();
|
|
49
|
+
if (!cfg.enabled || !cfg.url) return new Map();
|
|
50
|
+
|
|
51
|
+
// Return cached data if fresh.
|
|
52
|
+
if (_cache && Date.now() - _cache.timestamp < CACHE_TTL_MS) {
|
|
53
|
+
return _cache.data;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const result = _doFetch(cfg.url);
|
|
57
|
+
_cache = { timestamp: Date.now(), data: result };
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function _doFetch(url: string): Map<string, ModelPricing> {
|
|
62
|
+
// Synchronous bridge: spawnSync an inline worker so the call blocks without
|
|
63
|
+
// deadlocking fetch (mirrors HttpEmbedder / RAPTOR summarizer pattern).
|
|
64
|
+
const WORKER = String.raw`
|
|
65
|
+
try {
|
|
66
|
+
const r = await fetch(process.env.COST_API_URL, { // guardrails-allow PREVENT-PI-004: user-opted-in cost API — fetches model pricing from a user-configured endpoint
|
|
67
|
+
method: "GET",
|
|
68
|
+
headers: { "accept": "application/json" },
|
|
69
|
+
});
|
|
70
|
+
const j = await r.json();
|
|
71
|
+
process.stdout.write(JSON.stringify({ ok: r.ok, data: j }));
|
|
72
|
+
} catch (e) {
|
|
73
|
+
process.stdout.write(JSON.stringify({ ok: false, error: String(e && e.message ? e.message : e) }));
|
|
74
|
+
}
|
|
75
|
+
`;
|
|
76
|
+
const res = spawnSync(process.execPath, ["-e", WORKER], {
|
|
77
|
+
encoding: "utf8",
|
|
78
|
+
timeout: 15_000,
|
|
79
|
+
env: { ...process.env, COST_API_URL: url },
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const out = new Map<string, ModelPricing>();
|
|
83
|
+
|
|
84
|
+
let parsed: { ok: boolean; data?: unknown; error?: string };
|
|
85
|
+
try {
|
|
86
|
+
parsed = JSON.parse(res.stdout);
|
|
87
|
+
} catch {
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!parsed.ok || !parsed.data) return out;
|
|
92
|
+
|
|
93
|
+
// OpenRouter-compatible: { data: [{ id, pricing: { prompt, completion } }] }
|
|
94
|
+
const models = (parsed.data as { data?: Array<{ id?: string; pricing?: { prompt?: string; completion?: string } }> })?.data;
|
|
95
|
+
if (!Array.isArray(models)) return out;
|
|
96
|
+
|
|
97
|
+
for (const m of models) {
|
|
98
|
+
if (!m.id) continue;
|
|
99
|
+
const prompt = m.pricing?.prompt;
|
|
100
|
+
const completion = m.pricing?.completion;
|
|
101
|
+
const inputRate = prompt != null ? parseFloat(prompt) : null;
|
|
102
|
+
const outputRate = completion != null ? parseFloat(completion) : null;
|
|
103
|
+
if (inputRate != null || outputRate != null) {
|
|
104
|
+
out.set(m.id.toLowerCase(), { inputRate, outputRate });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Look up pricing for a model name from the cost API cache.
|
|
113
|
+
* Tries exact match, then prefix match (same pattern as lookupModelInputRate).
|
|
114
|
+
*/
|
|
115
|
+
export function lookupCostApiPricing(model: string): ModelPricing | null {
|
|
116
|
+
const data = fetchModelPricing();
|
|
117
|
+
if (data.size === 0) return null;
|
|
118
|
+
const lower = model.toLowerCase();
|
|
119
|
+
// Exact match.
|
|
120
|
+
if (data.has(lower)) return data.get(lower) ?? null;
|
|
121
|
+
// Prefix match.
|
|
122
|
+
for (const [key, pricing] of data) {
|
|
123
|
+
if (lower.startsWith(key)) return pricing;
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Clear the pricing cache (useful for testing). */
|
|
129
|
+
export function clearCostApiCache(): void {
|
|
130
|
+
_cache = null;
|
|
131
|
+
}
|
|
@@ -13,10 +13,12 @@
|
|
|
13
13
|
import type { Embedder, Vector } from "../../embedder.js";
|
|
14
14
|
import { cosineSimilarity } from "../../embedder.js";
|
|
15
15
|
import { defaultEmbedder as getDefaultEmbedder } from "../../embedder.js";
|
|
16
|
+
import type { EngineMessage } from "../../types.js";
|
|
16
17
|
import { buildRaptorTree, type Leaf, type RaptorTree } from "./tree.js";
|
|
17
18
|
import type { QualityMarker } from "./guardrails.js";
|
|
19
|
+
import { applyHallucinationGuardrails, sourceTokenSet } from "./guardrails.js";
|
|
18
20
|
import { meanVector } from "./kmeans.js";
|
|
19
|
-
import { summarizeCluster } from "./summarizer.js";
|
|
21
|
+
import { summarizeCluster, extractiveClusterSummary } from "./summarizer.js";
|
|
20
22
|
import type { Logger } from "../../log.js";
|
|
21
23
|
import {
|
|
22
24
|
saveRaptorTree,
|
|
@@ -27,6 +29,38 @@ import {
|
|
|
27
29
|
/** When >FRESH_THRESHOLD of existing leaves are new, a full rebuild is cheaper. */
|
|
28
30
|
const FRESH_THRESHOLD = 0.5;
|
|
29
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Summarize + apply hallucination guardrails (mirrors tree.ts:summarizeInto).
|
|
34
|
+
* If the guardrail marks the summary as extractive_fallback (drift/hallucination),
|
|
35
|
+
* re-summarize with the deterministic extractive path so we never serve a
|
|
36
|
+
* drifted LLM summary from an incremental update.
|
|
37
|
+
*/
|
|
38
|
+
function summarizeGuarded(
|
|
39
|
+
messages: EngineMessage[],
|
|
40
|
+
centroid: Vector,
|
|
41
|
+
embedder: Embedder,
|
|
42
|
+
consistencyThreshold?: number,
|
|
43
|
+
): { summary: string; tokenEstimate: number; qualityMarker: QualityMarker } {
|
|
44
|
+
let cs = summarizeCluster(messages);
|
|
45
|
+
const sources = messages.map((m) => m.text);
|
|
46
|
+
const guard = applyHallucinationGuardrails({
|
|
47
|
+
summary: cs.summary,
|
|
48
|
+
sources,
|
|
49
|
+
centroid,
|
|
50
|
+
embedder,
|
|
51
|
+
sourceTokens: sourceTokenSet(sources),
|
|
52
|
+
consistencyThreshold,
|
|
53
|
+
});
|
|
54
|
+
if (guard.marker === "extractive_fallback") {
|
|
55
|
+
cs = extractiveClusterSummary(messages);
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
summary: cs.summary,
|
|
59
|
+
tokenEstimate: cs.tokenEstimate,
|
|
60
|
+
qualityMarker: guard.marker === "extractive_fallback" ? "low" : guard.marker,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
30
64
|
/**
|
|
31
65
|
* Incrementally update a persisted RAPTOR tree with new leaves.
|
|
32
66
|
*
|
|
@@ -173,6 +207,7 @@ export function incrementRaptorTree(
|
|
|
173
207
|
for (const l of reallyNewLeaves) leafMap.set(l.id, l);
|
|
174
208
|
|
|
175
209
|
// Recompute affected Level-0 nodes.
|
|
210
|
+
const embedder = opts.embedder ?? getDefaultEmbedder();
|
|
176
211
|
for (const nid of affectedNodeIds) {
|
|
177
212
|
const node = tree.nodes.get(nid);
|
|
178
213
|
if (!node) continue;
|
|
@@ -186,7 +221,12 @@ export function incrementRaptorTree(
|
|
|
186
221
|
// Summarize: flatten messages from all covered leaves.
|
|
187
222
|
const messages = coveredLeaves.flatMap((l) => l.messages);
|
|
188
223
|
if (messages.length > 0) {
|
|
189
|
-
const cs =
|
|
224
|
+
const cs = summarizeGuarded(
|
|
225
|
+
messages,
|
|
226
|
+
node.embedding,
|
|
227
|
+
embedder,
|
|
228
|
+
opts.consistencyThreshold,
|
|
229
|
+
);
|
|
190
230
|
node.summary = cs.summary;
|
|
191
231
|
node.tokenEstimate = cs.tokenEstimate;
|
|
192
232
|
}
|
|
@@ -215,7 +255,12 @@ export function incrementRaptorTree(
|
|
|
215
255
|
);
|
|
216
256
|
const messages = covered.flatMap((l) => l.messages);
|
|
217
257
|
if (messages.length > 0) {
|
|
218
|
-
const cs =
|
|
258
|
+
const cs = summarizeGuarded(
|
|
259
|
+
messages,
|
|
260
|
+
node.embedding,
|
|
261
|
+
embedder,
|
|
262
|
+
opts.consistencyThreshold,
|
|
263
|
+
);
|
|
219
264
|
node.summary = cs.summary;
|
|
220
265
|
node.tokenEstimate = cs.tokenEstimate;
|
|
221
266
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* index.ts — RAPTOR orchestrator (Sprint 13, Phase 6).
|
|
2
|
+
* index.ts — RAPTOR orchestrator (Sprint 13, Phase 6 → promoted to live in S42B).
|
|
3
3
|
*
|
|
4
4
|
* Entry point that turns a session's leaves into a hierarchical summary tree.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The tree is BUILT, PERSISTED to raptor_nodes, and SERVED in recall queries
|
|
6
|
+
* when RAPTOR_ENABLED=true (default). Set RAPTOR_SHADOW_MODE=true to build +
|
|
7
|
+
* persist only without serving (transition/eval use).
|
|
8
8
|
*
|
|
9
9
|
* PREVENT-PI-004: no network here. Any Ollama call lives in summarizer.ts
|
|
10
10
|
* (localhost-only, annotated). This module is pure orchestration.
|
|
@@ -20,9 +20,13 @@ import { saveRaptorTree, listRaptorNodes } from "../../store/sqlite.js";
|
|
|
20
20
|
import { insertBuildHistory, computeCoherenceScore } from "./buildHistory.js";
|
|
21
21
|
import { loadDedupConfig } from "../../config/dedup.js";
|
|
22
22
|
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Shadow mode default OFF: the tree is built, persisted, AND served when
|
|
25
|
+
* RAPTOR_ENABLED=true (the Setup tab toggle). Set RAPTOR_SHADOW_MODE=true to
|
|
26
|
+
* build + persist only (transition/eval use without serving live recall).
|
|
27
|
+
*/
|
|
24
28
|
export function isShadowMode(): boolean {
|
|
25
|
-
return process.env.RAPTOR_SHADOW_MODE
|
|
29
|
+
return process.env.RAPTOR_SHADOW_MODE === "true";
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
export interface RaptorOrchestratorOptions {
|
package/src/vector-search.ts
CHANGED
|
@@ -64,9 +64,9 @@ function raptorSearchHits(
|
|
|
64
64
|
const embedder = store.embedder;
|
|
65
65
|
const record = store.record;
|
|
66
66
|
|
|
67
|
-
// S25 gate (a): honor
|
|
68
|
-
// built + persisted
|
|
69
|
-
//
|
|
67
|
+
// S25 gate (a): honor shadow mode at SERVE time. When RAPTOR_SHADOW_MODE=true,
|
|
68
|
+
// the tree is built + persisted but NOT merged into recall (transition/eval).
|
|
69
|
+
// Default is live: shadow mode is opt-in, not the default.
|
|
70
70
|
if (isShadowMode()) return [];
|
|
71
71
|
// S25 gate (b): freshness + fallback guards with per-session cache.
|
|
72
72
|
// The cache avoids O(n·leaves) rehydrate on every search when the tree
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{j as e,r as i,X as B}from"./index-GPHRnEN7.js";import{T as I}from"./toggle-C9HpkIfH.js";const T={checkpoint:"#6366f1",turn:"#58a6ff","turn-content":"#22c55e",memory:"#f59e0b"},z={checkpoint:"Checkpoint",turn:"Turn","turn-content":"Turn Content",memory:"Memory"};function U(t,n){return`M 0,-${n} L ${t},0 L 0,${n} L -${t},0 Z`}function Y(t){return!t||t==="active"?1:.5}function Q(t,n,c,d,s,u,a){const p=u?T[d]??"#6366f1":"#d1d5db",m=s?"#ef4444":"none",h=Y(a);switch(d){case"checkpoint":return e.jsx("circle",{cx:t,cy:n,r:s?c+3:c,fill:p,opacity:h,stroke:m,strokeWidth:s?2:0});case"turn":return e.jsx("circle",{cx:t,cy:n,r:s?c+3:c,fill:"none",stroke:p,strokeWidth:2.5,opacity:h});case"turn-content":return e.jsxs("g",{children:[e.jsx("circle",{cx:t,cy:n,r:s?c+3:c,fill:"none",stroke:p,strokeWidth:2.5,opacity:h}),e.jsx("circle",{cx:t,cy:n,r:Math.max(3,c*.3),fill:p,opacity:h})]});case"memory":const g=c*.85,j=c*.85;return e.jsx("path",{d:U(g,j),transform:`translate(${t}, ${n})`,fill:p,opacity:h,stroke:m,strokeWidth:s?2:0})}}const X=5e3,P=.005,O=.01,D=.9,Z=.1,L=50;function J(t){const n=t.nodes.map((s,u)=>{const a=2*Math.PI*u/Math.max(t.nodes.length,1),p=200;return{...s,pos:{x:Math.cos(a)*p+300,y:Math.sin(a)*p+200},vx:0,vy:0,pinned:!1}}),c=new Map;n.forEach((s,u)=>c.set(s.id,u));const d=t.edges.map(s=>{const u=c.get(s.source),a=c.get(s.target);return u===void 0||a===void 0||u===a?null:{source:u,target:a,weight:s.weight,type:s.type}}).filter(s=>s!==null);return{nodes:n,edges:d}}function K(t){const{nodes:n,edges:c}=t,d=n.length;if(d!==0)for(let s=0;s<d;s++){if(n[s].pinned)continue;let u=0,a=0;for(let m=0;m<d;m++){if(s===m)continue;const h=n[s].pos.x-n[m].pos.x,g=n[s].pos.y-n[m].pos.y,j=h*h+g*g||1,v=X/j,b=Math.sqrt(j);u+=h/b*v,a+=g/b*v}for(const m of c){let h;if(m.source===s)h=m.target;else if(m.target===s)h=m.source;else continue;const g=n[h].pos.x-n[s].pos.x,j=n[h].pos.y-n[s].pos.y;u+=g*P*m.weight,a+=j*P*m.weight}u-=n[s].pos.x*O,a-=n[s].pos.y*O,n[s].vx=(n[s].vx+u)*D,n[s].vy=(n[s].vy+a)*D;const p=Math.sqrt(n[s].vx*n[s].vx+n[s].vy*n[s].vy);p>L&&(n[s].vx=n[s].vx/p*L,n[s].vy=n[s].vy/p*L),p>Z&&(n[s].pos.x+=n[s].vx,n[s].pos.y+=n[s].vy)}}const ee=300,te=.3,k=900,w=600,x=40;function M(t){switch(t){case"temporal":return"#6366f1";case"semantic":return"#22c55e";case"raptor_parent":return"#f59e0b"}}const N=({color:t,shape:n,label:c})=>{const d={display:"inline-block",width:"10px",height:"10px",verticalAlign:"middle",marginRight:"3px",borderRadius:n.startsWith("hollow")||n==="filled-circle"?"50%":void 0};let s;return n==="line"?e.jsx("span",{style:{color:t,marginRight:"6px"},children:c}):(n==="filled-circle"?s=e.jsx("span",{style:{...d,backgroundColor:t}}):n==="hollow-circle"?s=e.jsx("span",{style:{...d,border:`2px solid ${t}`,backgroundColor:"transparent"}}):n==="hollow-ring"?s=e.jsx("span",{style:{...d,border:`2px solid ${t}`,backgroundColor:"transparent",position:"relative"},children:e.jsx("span",{style:{display:"block",width:"4px",height:"4px",borderRadius:"50%",backgroundColor:t,margin:"2px auto"}})}):s=e.jsx("span",{style:{...d,clipPath:"polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)",backgroundColor:t}}),e.jsxs(e.Fragment,{children:[e.jsxs("span",{className:"memory-map-legend-item",children:[s,c]})," "]}))},re=()=>{const[t,n]=i.useState(null),[c,d]=i.useState(!0),[s,u]=i.useState(null),[a,p]=i.useState(null),[m,h]=i.useState(""),[g]=i.useState(""),[j,v]=i.useState(0),b=i.useRef(null),S=i.useRef(0);i.useEffect(()=>{let r=!1;const o=new URLSearchParams;return g&&o.set("sessionId",g),o.set("threshold","0.7"),o.set("maxEdgesPerNode","4"),d(!0),u(null),fetch(`/api/memory-map?${o.toString()}`).then(l=>{if(!l.ok)throw new Error(`HTTP ${l.status}`);return l.json()}).then(l=>{r||(n(l),b.current=J(l),v(1),d(!1))}).catch(l=>{r||(u(l instanceof Error?l.message:"Unknown error"),d(!1))}),()=>{r=!0,cancelAnimationFrame(S.current)}},[g]),i.useEffect(()=>{if(!b.current||j===0)return;const r=b.current;let o=0;const l=()=>{o>=ee||(K(r),o++,v(o),S.current=requestAnimationFrame(l))};return S.current=requestAnimationFrame(l),()=>cancelAnimationFrame(S.current)},[j]);const W=i.useCallback((r,o)=>{const l=b.current;l&&(l.nodes[o].pinned=!l.nodes[o].pinned,l.nodes[o].pinned||(l.nodes[o].vx=0,l.nodes[o].vy=0),p(f=>f?.id===r.id?null:r),v(f=>f+1))},[]),$=i.useMemo(()=>{const r=b.current;if(!r||!m)return null;const o=m.toLowerCase();return new Set(r.nodes.map((l,f)=>l.summaryTruncated.toLowerCase().includes(o)||l.label.toLowerCase().includes(o)?f:-1).filter(l=>l>=0))},[m]),R=i.useMemo(()=>{const r=t?.validation;return r?r.gatesPassed<r.gatesRun?{level:"red",label:"Gate failure"}:r.warnings.length>0?{level:"yellow",label:`${r.warnings.length} warning(s)`}:{level:"green",label:"Healthy"}:{level:"yellow",label:"No validation"}},[t?.validation]),F={green:"#22c55e",yellow:"#eab308",red:"#ef4444"},G=i.useMemo(()=>{const r=t?.validation;if(!r)return`${t?.metadata.totalNodes??0} nodes`;const o=[];return r.sources.checkpoint>0&&o.push(`${r.sources.checkpoint} checkpoints`),r.sources.turn>0&&o.push(`${r.sources.turn} turns`),r.sources.turnContent>0&&o.push(`${r.sources.turnContent} turn-content`),r.sources.memory>0&&o.push(`${r.sources.memory} memories`),o.length===0&&o.push(`${t?.metadata.totalNodes??0} nodes`),o.join(" · ")},[t?.validation,t?.metadata.totalNodes]),A=i.useMemo(()=>{const r=t?.validation;if(!r)return"";const o=[];return o.push(r.sources.checkpoint>0?"✓ checkpoints":"✗ checkpoints"),o.push(r.sources.turn>0?"✓ turns":"✗ turns"),o.push(r.sources.turnContent>0?"✓ turn-content":"✗ turn-content"),o.push(r.sources.memory>0?"✓ memories":"✗ memories"),o.join(" | ")},[t?.validation]),H=i.useMemo(()=>{const r=t?.validation;return r?r.sources.checkpoint===0&&r.sources.turn===0&&r.sources.turnContent===0&&r.sources.memory===0:t?.metadata.totalNodes===0},[t?.validation,t?.metadata.totalNodes]);if(c)return e.jsx("div",{className:"rounded-lg border border-border bg-bg-card p-6 text-center text-sm text-muted-foreground",children:"Loading memory graph..."});if(s)return e.jsxs("div",{className:"rounded-lg border border-border bg-bg-card p-6 text-center text-sm",children:[e.jsxs("p",{className:"text-red-400",children:["Failed to load memory map: ",s]}),e.jsx("button",{className:"mt-3 rounded-md border border-border px-3 py-1.5 text-sm transition-colors hover:bg-bg-elevated hover:text-foreground",onClick:()=>v(r=>r+1),children:"Retry"})]});if(!t||!b.current||H)return e.jsx("div",{className:"rounded-lg border border-border bg-bg-card p-6 text-sm text-muted-foreground",children:e.jsx("p",{children:"Memories appear after your first compaction. The graph shows checkpoints (compaction summaries) linked by semantic similarity and time. Run a longer session or lower the compaction tier to see it sooner."})});const y=b.current;return e.jsxs("div",{className:"flex flex-col gap-3",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-3",children:[e.jsx("input",{type:"text",placeholder:"Search memories...",value:m,onInput:r=>h(r.target.value),className:"w-56 rounded-md border border-border bg-bg-elevated/50 px-3 py-2 text-sm outline-none transition-colors focus:border-primary"}),e.jsxs("span",{className:"text-xs text-muted-foreground",children:[e.jsx("span",{children:G})," · ",e.jsxs("span",{children:[t.edges.length," edges"]})]}),e.jsx("span",{className:"ml-auto rounded-full px-2 py-1 text-[11px] font-semibold text-white",style:{backgroundColor:F[R.level]??"#6b7280"},title:`Validation: ${R.label}`,children:R.level==="green"?"Healthy":R.level==="yellow"?"Warnings":"Critical"})]}),A?e.jsx("div",{className:"text-[11px] text-muted-foreground",children:A}):null,e.jsxs("div",{className:"flex flex-wrap items-center gap-x-4 gap-y-1 rounded-lg border border-border/60 bg-bg-card px-3 py-2 text-xs",children:[e.jsx("span",{className:"font-semibold text-muted-foreground",children:"Nodes:"}),e.jsx(N,{type:"node",color:T.checkpoint,shape:"filled-circle",label:"Checkpoint"}),e.jsx(N,{type:"node",color:T.turn,shape:"hollow-circle",label:"Turn"}),e.jsx(N,{type:"node",color:T["turn-content"],shape:"hollow-ring",label:"Turn Content"}),e.jsx(N,{type:"node",color:T.memory,shape:"diamond",label:"Memory"}),e.jsx("span",{className:"font-semibold text-muted-foreground",children:"Edges:"}),e.jsx(N,{type:"edge",color:M("semantic"),shape:"line",label:"Semantic"}),e.jsx(N,{type:"edge",color:M("temporal"),shape:"line",label:"Temporal"}),e.jsx(N,{type:"edge",color:M("raptor_parent"),shape:"line",label:"Raptor"})]}),e.jsxs("svg",{width:k,height:w,viewBox:`0 0 ${k} ${w}`,className:"memory-map-svg",children:[y.edges.map((r,o)=>{const l=y.nodes[r.source],f=y.nodes[r.target],C=M(r.type),E=Math.max(1,r.weight*2.5);return e.jsx("line",{x1:x+l.pos.x/(y.nodes.length||1)*(k-2*x),y1:x+l.pos.y/(y.nodes.length||1)*(w-2*x),x2:x+f.pos.x/(y.nodes.length||1)*(k-2*x),y2:x+f.pos.y/(y.nodes.length||1)*(w-2*x),stroke:C,strokeWidth:E,opacity:te},`edge-${o}`)}),y.nodes.map((r,o)=>{const l=x+r.pos.x/(y.nodes.length||1)*(k-2*x),f=x+r.pos.y/(y.nodes.length||1)*(w-2*x),C=Math.min(20,8+r.tokenEstimate/500),E=$?$.has(o):!0,q=a?.id===r.id,V=r.label.length>20?r.label.slice(0,18)+"...":r.label;return e.jsxs("g",{className:"memory-map-node-group",onClick:()=>W(r,o),style:{cursor:"pointer"},children:[Q(l,f,C,r.nodeType,q,E,r.dedupStatus),e.jsx("text",{x:l,y:f+C+12,textAnchor:"middle",fontSize:"9px",fill:"#374151",className:"memory-map-label",children:V})]},`node-${o}`)})]}),a?e.jsxs("div",{className:"rounded-lg border border-border/60 bg-bg-card p-4 text-sm",children:[e.jsxs("h3",{className:"font-heading text-base font-semibold",children:[e.jsx("span",{className:"mr-1.5 text-xs font-normal text-muted-foreground",children:z[a.nodeType]}),a.label]}),e.jsx("p",{className:"mt-1",children:a.summaryTruncated}),a.topicSummary?e.jsxs("p",{className:"mt-1",children:["Topic: ",a.topicSummary]}):null,e.jsxs("p",{className:"mt-1",children:["Tokens: ",a.tokenEstimate," | Decisions: ",a.decisionCount]}),e.jsxs("p",{children:["Session: ",a.sessionId]}),e.jsxs("p",{children:["Raptor Level: ",a.raptorLevel]}),e.jsx("p",{className:"mt-1 text-muted-foreground",children:a.textSnippet})]}):null]})};function se(t){return t===0?"#d4a017":t===1?"#3b82f6":"#14b8a6"}function _(t){return t?new Date(t).toLocaleString():"—"}function ne({node:t}){const n=se(t.level),c=t.children.length>0?`${t.children.length} child${t.children.length===1?"":"ren"}`:"leaf";return e.jsxs("div",{style:{background:"#14142b",border:`1px solid ${n}`,borderRadius:"8px",padding:"0.75rem 1rem",marginBottom:"0.6rem",marginLeft:t.level*24,maxWidth:"90%"},children:[e.jsxs("div",{style:{display:"flex",gap:"0.75rem",alignItems:"center",marginBottom:"0.35rem"},children:[e.jsxs("span",{style:{display:"inline-block",background:n,color:"#fff",borderRadius:"10px",padding:"1px 8px",fontSize:"11px",fontWeight:600},children:["L",t.level]}),e.jsxs("span",{style:{fontSize:"12px",color:"#6b7280"},children:[c," · ",t.tokenEstimate," tok · ",t.qualityMarker]}),e.jsx("span",{style:{fontSize:"12px",color:"#6b7280",marginLeft:"auto"},children:_(t.builtAt)})]}),e.jsx("div",{style:{fontSize:"0.92rem",color:"#e5e7eb"},children:t.summary})]})}function oe(){const[t,n]=i.useState(null),[c,d]=i.useState(!0),[s,u]=i.useState(null);return i.useEffect(()=>{let a=!1;return d(!0),u(null),B().then(p=>{a||(n(p),d(!1))}).catch(p=>{a||(u(p instanceof Error?p.message:"Unknown error"),d(!1))}),()=>{a=!0}},[]),c?e.jsx("div",{className:"rounded-lg border border-border bg-bg-card p-6 text-center text-sm text-muted-foreground",children:"Loading RAPTOR tree..."}):s?e.jsx("div",{className:"rounded-lg border border-dashed border-border bg-bg-card p-8 text-center text-sm",children:e.jsxs("p",{className:"text-red-400",children:["Failed to load RAPTOR tree: ",s]})}):!t||t.empty||t.nodes.length===0?e.jsx("div",{className:"rounded-lg border border-dashed border-border bg-bg-card p-8 text-center text-sm text-muted-foreground",children:e.jsx("p",{children:"No RAPTOR tree built yet — run a compaction (the tree builds during compaction)."})}):e.jsxs("div",{className:"w-full p-4",children:[e.jsxs("div",{className:"mb-4 flex items-center gap-6 text-sm text-muted-foreground",children:[e.jsxs("span",{children:[e.jsx("strong",{children:t.nodes.length})," nodes"]}),e.jsxs("span",{children:[e.jsx("strong",{children:t.levels+1})," levels"]}),e.jsxs("span",{children:["Built ",_(t.builtAt??0)]})]}),t.nodes.map(a=>e.jsx(ne,{node:a},a.id))]})}const ce=()=>{const[t,n]=i.useState("map");return e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsxs("nav",{className:"flex gap-2 border-b border-border pb-2 pt-2","aria-label":"Memory map sections",children:[e.jsx(I,{pressed:t==="map",onClick:()=>n("map"),"aria-label":"Memory Map view",children:"Memory Map"}),e.jsx(I,{pressed:t==="raptor",onClick:()=>n("raptor"),"aria-label":"RAPTOR Tree view",children:"RAPTOR Tree"})]}),t==="map"&&e.jsx(re,{}),t==="raptor"&&e.jsx(oe,{})]})};export{ce as default};
|
|
2
|
-
//# sourceMappingURL=MemoryMapTab-DSnSO3nG.js.map
|