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
|
@@ -29,6 +29,8 @@ import {
|
|
|
29
29
|
createTopicStore,
|
|
30
30
|
bumpWikiCompactCounter,
|
|
31
31
|
} from "../../src/topics/index.js";
|
|
32
|
+
import { TrigramEmbedder } from "../../src/embedder.js";
|
|
33
|
+
import type { EmbeddedChunk } from "../../src/topics/types.js";
|
|
32
34
|
import { computeLiveTrimCut, liveTrimSummaryMessage } from "../mega-trim.js";
|
|
33
35
|
import {
|
|
34
36
|
pressureFromPct,
|
|
@@ -37,7 +39,21 @@ import {
|
|
|
37
39
|
} from "../mega-config.js";
|
|
38
40
|
import { appendMirrorMessages } from "./mirror-append.js";
|
|
39
41
|
import { stagedForTail, withRecallTail } from "./recall-tail.js";
|
|
40
|
-
import { buildSeparatedPrompt } from "./separated-prompt.js";
|
|
42
|
+
import { buildSeparatedPrompt, buildCacheOptimizedPrompt } from "./separated-prompt.js";
|
|
43
|
+
|
|
44
|
+
/** Best-effort text extraction from an AgentMessage for analytics/logging.
|
|
45
|
+
* AgentMessage is a discriminated union; .content exists only on some
|
|
46
|
+
* variants (user/assistant/toolResult/custom). Narrow by role, never assume. */
|
|
47
|
+
function messageContentText(m: AgentMessage): string {
|
|
48
|
+
const c = (m as { content?: unknown }).content;
|
|
49
|
+
if (typeof c === "string") return c;
|
|
50
|
+
if (Array.isArray(c)) {
|
|
51
|
+
return c
|
|
52
|
+
.map((b) => (b && typeof b === "object" && "text" in b ? String((b as { text?: string }).text ?? "") : ""))
|
|
53
|
+
.join(" ");
|
|
54
|
+
}
|
|
55
|
+
return "";
|
|
56
|
+
}
|
|
41
57
|
|
|
42
58
|
/** Register the context event handler (live-trim auto-trigger). */
|
|
43
59
|
export function registerContextHandler(
|
|
@@ -71,13 +87,33 @@ export function registerContextHandler(
|
|
|
71
87
|
// reaches raw_transcript (PREVENT-PI: append-only, view-only).
|
|
72
88
|
const tailResult = (msgs?: readonly AgentMessage[]) => {
|
|
73
89
|
const base = msgs ?? messages;
|
|
74
|
-
if (!stagedForTail(runtime, config) && !config.messageSeparation) return undefined;
|
|
90
|
+
if (!stagedForTail(runtime, config) && !config.messageSeparation && !config.cacheStriping) return undefined;
|
|
75
91
|
let result: AgentMessage[] = stagedForTail(runtime, config)
|
|
76
92
|
? withRecallTail(base, runtime, config)
|
|
77
93
|
: (base as AgentMessage[]);
|
|
78
|
-
if (config.
|
|
94
|
+
if (config.cacheStriping) {
|
|
95
|
+
result = buildCacheOptimizedPrompt(result);
|
|
96
|
+
} else if (config.messageSeparation) {
|
|
79
97
|
result = buildSeparatedPrompt(result);
|
|
80
98
|
}
|
|
99
|
+
// P2.5: log prefix stability (fire-and-forget, non-fatal).
|
|
100
|
+
// tailResult is sync, so use .then().catch() on the dynamic import.
|
|
101
|
+
if (result.length > 1) {
|
|
102
|
+
import("../../src/cache-stripe-impl.js").then(({ computeStabilityScore }) => {
|
|
103
|
+
const stableScore = computeStabilityScore(
|
|
104
|
+
{ content: messageContentText(result[0] ?? result[0]), chunkId: "prefix", accessCount: 0, lastAccessedAt: 0 },
|
|
105
|
+
result.slice(0, 2).map((m) => ({ content: messageContentText(m), chunkId: "prefix", accessCount: 0, lastAccessedAt: 0 })),
|
|
106
|
+
);
|
|
107
|
+
runtime.logger.info("prefix_stability", {
|
|
108
|
+
stableScore: Number.isFinite(stableScore) ? stableScore : 0,
|
|
109
|
+
prefixMessages: result.length,
|
|
110
|
+
separation: config.messageSeparation ? "v2" : "off",
|
|
111
|
+
striping: config.cacheStriping ? "v3" : "off",
|
|
112
|
+
});
|
|
113
|
+
}).catch(() => {
|
|
114
|
+
// Non-fatal: stability logging is best-effort.
|
|
115
|
+
});
|
|
116
|
+
}
|
|
81
117
|
return { messages: result };
|
|
82
118
|
};
|
|
83
119
|
// Always track context for the dashboard/widget, even when auto is off.
|
|
@@ -127,6 +163,33 @@ export function registerContextHandler(
|
|
|
127
163
|
epochIdFor(runtime.rt.sessionId),
|
|
128
164
|
runtime.currentTurn,
|
|
129
165
|
);
|
|
166
|
+
// P2.2: populate conversation_thread + tool_results tables for
|
|
167
|
+
// prompt-cache analytics and durable separation. The live-array
|
|
168
|
+
// separation (buildSeparatedPrompt / buildCacheOptimizedPrompt in
|
|
169
|
+
// tailResult above) is sufficient for the prompt-construction path;
|
|
170
|
+
// these DB writes persist the split for post-hoc analysis, dashboard
|
|
171
|
+
// queries, and future readers. Non-fatal — failure here never breaks
|
|
172
|
+
// the agent loop (PREVENT-PI-004: zero network, local SQLite only).
|
|
173
|
+
{
|
|
174
|
+
const sid = runtime.rt.sessionId;
|
|
175
|
+
const turn = runtime.currentTurn;
|
|
176
|
+
const now = Date.now();
|
|
177
|
+
const threadStmt = db.prepare(
|
|
178
|
+
"INSERT OR IGNORE INTO conversation_thread (conversation_id, role, content, turn_index, timestamp) VALUES (?, ?, ?, ?, ?)",
|
|
179
|
+
);
|
|
180
|
+
const toolStmt = db.prepare(
|
|
181
|
+
"INSERT OR IGNORE INTO tool_results (conversation_id, role, content, turn_index, timestamp) VALUES (?, ?, ?, ?, ?)",
|
|
182
|
+
);
|
|
183
|
+
for (const m of messages) {
|
|
184
|
+
const role = m.role;
|
|
185
|
+
const content = messageContentText(m);
|
|
186
|
+
if (role === "user" || role === "assistant") {
|
|
187
|
+
threadStmt.run(sid, role, content, turn, now);
|
|
188
|
+
} else if (role === "toolResult" || role === "bashExecution") {
|
|
189
|
+
toolStmt.run(sid, role, content, turn, now);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
130
193
|
} catch (e) {
|
|
131
194
|
runtime.logger.warn("db-mirror-append-fail", { error: String(e) });
|
|
132
195
|
}
|
|
@@ -306,6 +369,84 @@ export function registerContextHandler(
|
|
|
306
369
|
error: String(wikiErr),
|
|
307
370
|
});
|
|
308
371
|
}
|
|
372
|
+
|
|
373
|
+
// D1: seed the topic model from raw_transcript when context_chunks is
|
|
374
|
+
// thin (pre-compaction). Gated on WIKI_SEED_FROM_TURNS; non-fatal.
|
|
375
|
+
// Seeds buildTopicModel with on-the-fly trigram embeddings from
|
|
376
|
+
// recent raw_transcript rows for the current session.
|
|
377
|
+
try {
|
|
378
|
+
if (
|
|
379
|
+
config.autoWikiEnabled &&
|
|
380
|
+
config.turnsDbEnabled &&
|
|
381
|
+
TurnsConfig.WIKI_SEED_FROM_TURNS
|
|
382
|
+
) {
|
|
383
|
+
const floor = 50;
|
|
384
|
+
const countRow = db
|
|
385
|
+
.prepare(
|
|
386
|
+
`SELECT COUNT(*) AS cnt FROM context_chunks WHERE session_id = ?`,
|
|
387
|
+
)
|
|
388
|
+
.get(runtime.rt.sessionId) as { cnt: number } | undefined;
|
|
389
|
+
const chunkCount = countRow?.cnt ?? 0;
|
|
390
|
+
if (chunkCount < floor) {
|
|
391
|
+
const transcriptRows = db
|
|
392
|
+
.prepare(
|
|
393
|
+
`SELECT DISTINCT content_bytes
|
|
394
|
+
FROM raw_transcript
|
|
395
|
+
WHERE session_id = ?
|
|
396
|
+
AND length(content_bytes) > 10
|
|
397
|
+
ORDER BY seq ASC
|
|
398
|
+
LIMIT 200`,
|
|
399
|
+
)
|
|
400
|
+
.all(runtime.rt.sessionId) as Array<{
|
|
401
|
+
content_bytes: string;
|
|
402
|
+
}>;
|
|
403
|
+
if (transcriptRows.length > 0) {
|
|
404
|
+
const embedder = new TrigramEmbedder();
|
|
405
|
+
const seedChunks: EmbeddedChunk[] = [];
|
|
406
|
+
for (let i = 0; i < transcriptRows.length; i++) {
|
|
407
|
+
const text = transcriptRows[i].content_bytes.trim();
|
|
408
|
+
if (text.length === 0) continue;
|
|
409
|
+
const vec = embedder.embed(text);
|
|
410
|
+
seedChunks.push({
|
|
411
|
+
chunkId: `seed_transcript_${i}`,
|
|
412
|
+
sessionId: runtime.rt.sessionId,
|
|
413
|
+
vec,
|
|
414
|
+
text,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
if (seedChunks.length > 0) {
|
|
418
|
+
const model = buildTopicModel(
|
|
419
|
+
db,
|
|
420
|
+
{
|
|
421
|
+
kRange: [
|
|
422
|
+
TurnsConfig.WIKI_K_MIN,
|
|
423
|
+
TurnsConfig.WIKI_K_MAX,
|
|
424
|
+
],
|
|
425
|
+
labelTopTerms:
|
|
426
|
+
TurnsConfig.WIKI_LABEL_TOP_TERMS,
|
|
427
|
+
restarts: 5,
|
|
428
|
+
seed: 0x9e3779b9,
|
|
429
|
+
},
|
|
430
|
+
seedChunks,
|
|
431
|
+
);
|
|
432
|
+
createTopicStore(
|
|
433
|
+
runtime.currentStateDir,
|
|
434
|
+
).replaceTopicModel(model);
|
|
435
|
+
runtime.logger.info("wiki_seed", {
|
|
436
|
+
clusterCount: model.k,
|
|
437
|
+
sourceChunks: seedChunks.length,
|
|
438
|
+
totalChunks: model.totalChunks,
|
|
439
|
+
method: "kmeans+tfidf",
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
} catch (seedErr) {
|
|
446
|
+
runtime.logger.warn("wiki_seed_failed", {
|
|
447
|
+
error: String(seedErr),
|
|
448
|
+
});
|
|
449
|
+
}
|
|
309
450
|
// S27 Task 6: Fire-and-forget dedup pipeline.
|
|
310
451
|
// Deduplicates raw_transcript rows for the compacted range.
|
|
311
452
|
try {
|
|
@@ -394,6 +535,13 @@ export function registerContextHandler(
|
|
|
394
535
|
compactedFrom: ran.result.compactedFrom,
|
|
395
536
|
summary: ran.result.summary,
|
|
396
537
|
anchorUserMessages,
|
|
538
|
+
// CRITICAL-OVER ESCAPE HATCH: when context is at/over ~90% of the
|
|
539
|
+
// window, relief takes priority over the anchor floor. Without this,
|
|
540
|
+
// computeLiveTrimCut bails to null (can't satisfy the floor) and the
|
|
541
|
+
// model is fed a raw overflow that errors every turn — the
|
|
542
|
+
// "Already compacted" + overflow death-spiral (2026-08-01 incident).
|
|
543
|
+
// A thin anchor is recoverable; an overflowed session is not.
|
|
544
|
+
criticalOver: (pct ?? 0) >= 90,
|
|
397
545
|
});
|
|
398
546
|
if (cut === null) {
|
|
399
547
|
runtime.diagCtxCutNull++;
|
|
@@ -402,6 +550,7 @@ export function registerContextHandler(
|
|
|
402
550
|
compactedFrom: ran.result.compactedFrom,
|
|
403
551
|
viewLen: view.length,
|
|
404
552
|
anchorUserMessages,
|
|
553
|
+
criticalOver: (pct ?? 0) >= 90,
|
|
405
554
|
});
|
|
406
555
|
return tailResult() ?? undefined; // unsafe / below anchor floor — no trim this call
|
|
407
556
|
}
|
|
@@ -225,7 +225,7 @@ export function computeMegaSnapshot(p: SnapshotInput): SnapshotResult {
|
|
|
225
225
|
cachePct,
|
|
226
226
|
megaCacheFlare: p.megaCacheFlare,
|
|
227
227
|
megaCacheFlarePct: p.megaCacheFlarePct,
|
|
228
|
-
perTurnCacheHitPct: p.perTurnCacheHitPct
|
|
228
|
+
perTurnCacheHitPct: p.perTurnCacheHitPct,
|
|
229
229
|
levelUpFlare: p.levelUpFlare,
|
|
230
230
|
achievementFlare: p.achievementFlare,
|
|
231
231
|
achievementFlareTitles: p.achievementFlareTitles,
|
package/extensions/mega-trim.ts
CHANGED
|
@@ -25,6 +25,13 @@ export interface BuildLiveTrimViewOpts {
|
|
|
25
25
|
summary: string;
|
|
26
26
|
/** Min recent user messages to keep as the anchor (PREVENT-PI-001). */
|
|
27
27
|
anchorUserMessages: number;
|
|
28
|
+
/** CRITICAL-OVER ESCAPE HATCH: when true, context is over ~90% of the window
|
|
29
|
+
* and relief takes priority over the anchor floor. computeLiveTrimCut returns
|
|
30
|
+
* the boundary-safe cut even if it keeps fewer than `anchorUserMessages` user
|
|
31
|
+
* messages, rather than bailing to null (which would feed the model a raw
|
|
32
|
+
* overflow). A compacted session with a thin anchor is recoverable; an
|
|
33
|
+
* overflowed session that errors every turn is not. Default false. */
|
|
34
|
+
criticalOver?: boolean;
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
/**
|
|
@@ -68,9 +75,17 @@ export function computeLiveTrimCut(view: EngineMessage[], opts: BuildLiveTrimVie
|
|
|
68
75
|
if (cut > 1) {
|
|
69
76
|
const finalRecent = view.slice(cut);
|
|
70
77
|
if (finalRecent.filter((m) => m.role === "user").length < opts.anchorUserMessages) {
|
|
78
|
+
// CRITICAL-OVER ESCAPE HATCH: when context is critically over the window,
|
|
79
|
+
// return the boundary-safe cut even though it's below the anchor floor.
|
|
80
|
+
// Bailing to null here would feed the model the raw overflow and trap the
|
|
81
|
+
// session in an unrecoverable error loop ("Already compacted" + overflow).
|
|
82
|
+
// A thin anchor is recoverable; an overflowed session is not.
|
|
83
|
+
if (opts.criticalOver && cut > 0) return cut;
|
|
71
84
|
return null; // cannot satisfy the floor without dropping too much — retry next call
|
|
72
85
|
}
|
|
73
86
|
} else {
|
|
87
|
+
// Same escape hatch for the cut > 1 false branch.
|
|
88
|
+
if (opts.criticalOver && cut > 0) return cut;
|
|
74
89
|
return null;
|
|
75
90
|
}
|
|
76
91
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mega-compact",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.9",
|
|
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/turns.ts
CHANGED
|
@@ -37,8 +37,10 @@ export interface TurnsConfigShape {
|
|
|
37
37
|
WIKI_K_MAX: number;
|
|
38
38
|
/** S51: how many TF-IDF terms form a topic label (default 5). */
|
|
39
39
|
WIKI_LABEL_TOP_TERMS: number;
|
|
40
|
-
/** S51: rebuild the topic model every Nth compaction (default
|
|
40
|
+
/** S51: rebuild the topic model every Nth compaction (default 3). */
|
|
41
41
|
WIKI_REBUILD_EVERY_N_COMPACTS: number;
|
|
42
|
+
/** D1: seed the topic model from raw_transcript when context_chunks is thin (default true). */
|
|
43
|
+
WIKI_SEED_FROM_TURNS: boolean;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
function envKRange(defMin: number, defMax: number): [number, number] {
|
|
@@ -62,7 +64,8 @@ export function loadTurnsConfig(): TurnsConfigShape {
|
|
|
62
64
|
WIKI_K_MIN: kMin,
|
|
63
65
|
WIKI_K_MAX: kMax,
|
|
64
66
|
WIKI_LABEL_TOP_TERMS: envNum("MEGACOMPACT_WIKI_LABEL_TOP_TERMS", 5),
|
|
65
|
-
WIKI_REBUILD_EVERY_N_COMPACTS: envNum("MEGACOMPACT_WIKI_REBUILD_EVERY",
|
|
67
|
+
WIKI_REBUILD_EVERY_N_COMPACTS: envNum("MEGACOMPACT_WIKI_REBUILD_EVERY", 3),
|
|
68
|
+
WIKI_SEED_FROM_TURNS: envBool("MEGACOMPACT_WIKI_SEED_FROM_TURNS", true),
|
|
66
69
|
};
|
|
67
70
|
}
|
|
68
71
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* embedding.ts — D3 embedding helpers for memoryGraph source builders.
|
|
3
|
+
*
|
|
4
|
+
* Uses TrigramEmbedder with an in-memory SQLite cache table.
|
|
5
|
+
* Isolated here so the main module stays under the 500-line limit.
|
|
6
|
+
*/
|
|
7
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
8
|
+
import { TrigramEmbedder } from "../embedder.js";
|
|
9
|
+
|
|
10
|
+
let _trigramEmbedder: TrigramEmbedder | null = null;
|
|
11
|
+
|
|
12
|
+
function lazyEmbedder(): TrigramEmbedder {
|
|
13
|
+
if (!_trigramEmbedder) {
|
|
14
|
+
_trigramEmbedder = new TrigramEmbedder();
|
|
15
|
+
}
|
|
16
|
+
return _trigramEmbedder;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function simpleHash(s: string): string {
|
|
20
|
+
let h = 0x811c9dc5;
|
|
21
|
+
for (let i = 0; i < s.length; i++) {
|
|
22
|
+
h ^= s.charCodeAt(i);
|
|
23
|
+
h = Math.imul(h, 0x01000193);
|
|
24
|
+
}
|
|
25
|
+
return h.toString(36);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Compute or retrieve a cached embedding vector for a text string.
|
|
29
|
+
* Uses the embedding_cache table (created by the A2 migration) for
|
|
30
|
+
* persistence. Falls back to uncached TrigramEmbedder on table error. */
|
|
31
|
+
export function getOrComputeEmbedding(db: DatabaseSync, content: string): number[] {
|
|
32
|
+
try {
|
|
33
|
+
const hash = simpleHash(content);
|
|
34
|
+
const cached = db
|
|
35
|
+
.prepare("SELECT embedding FROM embedding_cache WHERE content_hash = ?")
|
|
36
|
+
.get(hash) as { embedding: string } | undefined;
|
|
37
|
+
if (cached && typeof cached.embedding === "string") {
|
|
38
|
+
return JSON.parse(cached.embedding) as number[];
|
|
39
|
+
}
|
|
40
|
+
const vec = lazyEmbedder().embed(content);
|
|
41
|
+
db.prepare(
|
|
42
|
+
"INSERT OR REPLACE INTO embedding_cache (content_hash, content, embedding) VALUES (?, ?, ?)",
|
|
43
|
+
).run(hash, content, JSON.stringify(vec));
|
|
44
|
+
return vec;
|
|
45
|
+
} catch {
|
|
46
|
+
return lazyEmbedder().embed(content);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Cosine similarity between two equal-length vectors. */
|
|
51
|
+
export function cosineSimilarity(a: number[], b: number[]): number {
|
|
52
|
+
let dot = 0;
|
|
53
|
+
let na = 0;
|
|
54
|
+
let nb = 0;
|
|
55
|
+
const len = Math.min(a.length, b.length);
|
|
56
|
+
for (let i = 0; i < len; i++) {
|
|
57
|
+
dot += a[i] * b[i];
|
|
58
|
+
na += a[i] * a[i];
|
|
59
|
+
nb += b[i] * b[i];
|
|
60
|
+
}
|
|
61
|
+
const denom = Math.sqrt(na) * Math.sqrt(nb);
|
|
62
|
+
return denom > 0 ? dot / denom : 0;
|
|
63
|
+
}
|