pi-mega-compact 0.21.2 → 0.21.4
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/routes-rag-settings-helpers.js +1 -0
- package/dist/extensions/mega-compact-child.js +126 -0
- package/dist/extensions/mega-config.js +5 -0
- package/dist/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.js +12 -2
- package/dist/extensions/mega-events/context-handler/afterCompact.js +11 -0
- package/dist/extensions/mega-events/context-handler/dbMirrorAppend.js +8 -0
- package/dist/extensions/mega-events/health-handler.js +12 -2
- package/dist/extensions/mega-events/session-handlers.js +1 -0
- package/dist/extensions/mega-pipeline/compact/run.js +11 -0
- package/dist/extensions/mega-runtime/helpers.js +45 -0
- package/dist/extensions/mega-runtime/internal-errors.js +14 -0
- package/dist/extensions/mega-runtime/runtime-instrumentation.js +166 -0
- package/dist/extensions/mega-runtime/runtime.js +27 -193
- package/dist/extensions/mega-runtime/state.js +1 -1
- package/dist/extensions/mega-runtime/widget.js +1 -1
- package/dist/extensions/mega-turn-store.js +5 -0
- package/dist/src/bridge/factory.js +195 -0
- package/dist/src/bridge/types.js +10 -0
- package/dist/src/bridge.js +1 -0
- package/dist/src/contextHealth.js +13 -5
- package/dist/src/metrics/turns.js +6 -1
- package/dist/src/store/sqlite/context-health-cache-poison.js +60 -0
- package/dist/src/store/sqlite/context-health.js +6 -48
- package/dist/src/store/sqlite/embedding-cache.js +4 -0
- package/dist/src/store/sqlite/schema.js +4 -0
- package/dist/src/store/turns/memory-store/admin.js +119 -0
- package/dist/src/store/turns/memory-store/ctx.js +28 -0
- package/dist/src/store/turns/memory-store/read.js +110 -0
- package/dist/src/store/turns/memory-store/write.js +88 -0
- package/dist/src/store/turns/memory-store.js +44 -273
- package/dist/src/store/turns/migrations.js +4 -0
- package/dist/src/store/turns/schema.js +1 -0
- package/dist/src/store/turns/sqlite-store/read.js +13 -0
- package/dist/src/store/turns/sqlite-store/rows.js +4 -0
- package/dist/src/store/turns/sqlite-store/write.js +3 -3
- package/dist/src/store/turns/sqlite-store.js +4 -0
- package/extensions/dashboard-client/dist/assets/{AdminTab-BsA8-_bX.js → AdminTab-C4Y0_UVx.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AdminTab-BsA8-_bX.js.map → AdminTab-C4Y0_UVx.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{AreaChart-BRHEfWhc.js → AreaChart-MCWK9rn6.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AreaChart-BRHEfWhc.js.map → AreaChart-MCWK9rn6.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{BarChart-C-M0LaFc.js → BarChart-CvSGAiWV.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{BarChart-C-M0LaFc.js.map → BarChart-CvSGAiWV.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-Cj3n59U3.js → CacheTab-DnMEVRro.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-Cj3n59U3.js.map → CacheTab-DnMEVRro.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ConfigTab-C016XTNC.js → ConfigTab-BExuZYy2.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ConfigTab-C016XTNC.js.map → ConfigTab-BExuZYy2.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-DE29fuqe.js → EventsTab-CspsEslY.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-DE29fuqe.js.map → EventsTab-CspsEslY.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/HealthTab-DiWzo2ML.js +2 -0
- package/extensions/dashboard-client/dist/assets/HealthTab-DiWzo2ML.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-4fJ7M4S4.js → MaintenanceTab-9NJuv3dB.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-4fJ7M4S4.js.map → MaintenanceTab-9NJuv3dB.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CDbcK9e4.js → MemoryMapTab-YpJK1h17.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CDbcK9e4.js.map → MemoryMapTab-YpJK1h17.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsCards-VUEcgGzo.js → MetricsCards-D2Mom1M7.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsCards-VUEcgGzo.js.map → MetricsCards-D2Mom1M7.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/MetricsTab-DqWF7wud.js +2 -0
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CKqJ0Zl-.js.map → MetricsTab-DqWF7wud.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-9Kxo79mi.js → OverviewTab-YuRxjV9U.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-9Kxo79mi.js.map → OverviewTab-YuRxjV9U.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-C4iveqrP.js → ReposTab-sXMrR7je.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-C4iveqrP.js.map → ReposTab-sXMrR7je.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-DSEwkMQm.js → SessionsTab-4YCXeGNR.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-DSEwkMQm.js.map → SessionsTab-4YCXeGNR.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-CCyWgy4O.js → SetupTab-67pZcM_W.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-CCyWgy4O.js.map → SetupTab-67pZcM_W.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CyD8e_e_.js → TimeSavedCard-DInjNETP.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CyD8e_e_.js.map → TimeSavedCard-DInjNETP.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnMemoryView-5tLbWkRv.js → TurnMemoryView-C3Bif8ix.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnMemoryView-5tLbWkRv.js.map → TurnMemoryView-C3Bif8ix.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/TurnsTab-BUGfiANp.js +2 -0
- package/extensions/dashboard-client/dist/assets/{TurnsTab-C7pm92Hg.js.map → TurnsTab-BUGfiANp.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{VcStatusBadge-ObhcIyfN.js → VcStatusBadge-DKU7qz9f.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{VcStatusBadge-ObhcIyfN.js.map → VcStatusBadge-DKU7qz9f.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{VectorCortexTab-BtHZb7o7.js → VectorCortexTab-BsVSf4xm.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{VectorCortexTab-BtHZb7o7.js.map → VectorCortexTab-BsVSf4xm.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{WikiTab-lQ4l3QIK.js → WikiTab-DZw9_Fwd.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{WikiTab-lQ4l3QIK.js.map → WikiTab-DZw9_Fwd.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{button-DhQQAJGm.js → button-BLRPN489.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{button-DhQQAJGm.js.map → button-BLRPN489.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-Cfhz9X31.js → card-BGkkcM4Q.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-Cfhz9X31.js.map → card-BGkkcM4Q.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{client-extra-CKNOGEz_.js → client-extra-0OJClxHD.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{client-extra-CKNOGEz_.js.map → client-extra-0OJClxHD.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-JKjK9_cB.js → generateCategoricalChart-Dl5lmrHF.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-JKjK9_cB.js.map → generateCategoricalChart-Dl5lmrHF.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/health-lSeDFTz7.js.map +1 -1
- package/extensions/dashboard-client/dist/assets/{index-iuxgtzEW.js → index-CH_AD-S_.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-iuxgtzEW.js.map → index-CH_AD-S_.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{switch-7EHEQMSP.js → switch-BPP2-d12.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-7EHEQMSP.js.map → switch-BPP2-d12.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-z1lWY4UM.js → toggle-Dez0JaMV.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-z1lWY4UM.js.map → toggle-Dez0JaMV.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-sctOZuRM.js → useSSE-Cx2W7BTo.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-sctOZuRM.js.map → useSSE-Cx2W7BTo.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/api/health.ts +1 -0
- package/extensions/dashboard-client/src/tabs/HealthTab.tsx +1 -0
- package/extensions/dashboard-server/routes-rag-settings-helpers.ts +6 -0
- package/extensions/mega-compact-child.ts +129 -0
- package/extensions/mega-config-types.ts +5 -0
- package/extensions/mega-config.ts +5 -0
- package/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.ts +12 -2
- package/extensions/mega-events/context-handler/afterCompact.ts +11 -0
- package/extensions/mega-events/context-handler/dbMirrorAppend.ts +8 -0
- package/extensions/mega-events/health-handler.ts +13 -3
- package/extensions/mega-events/session-handlers.ts +1 -0
- package/extensions/mega-pipeline/compact/run.ts +11 -0
- package/extensions/mega-runtime/helpers.ts +46 -0
- package/extensions/mega-runtime/internal-errors.ts +30 -0
- package/extensions/mega-runtime/runtime-instrumentation.ts +195 -0
- package/extensions/mega-runtime/runtime.ts +29 -209
- package/extensions/mega-runtime/state.ts +1 -1
- package/extensions/mega-runtime/widget.ts +1 -1
- package/extensions/mega-turn-store.ts +9 -0
- package/package.json +2 -2
- package/src/bridge/factory.ts +245 -0
- package/src/bridge/types.ts +140 -0
- package/src/bridge.ts +24 -0
- package/src/contextHealth.ts +26 -6
- package/src/metrics/turns.ts +6 -1
- package/src/store/sqlite/context-health-cache-poison.ts +97 -0
- package/src/store/sqlite/context-health.ts +12 -84
- package/src/store/sqlite/embedding-cache.ts +4 -0
- package/src/store/sqlite/schema.ts +4 -0
- package/src/store/turns/memory-store/admin.ts +171 -0
- package/src/store/turns/memory-store/ctx.ts +62 -0
- package/src/store/turns/memory-store/read.ts +172 -0
- package/src/store/turns/memory-store/write.ts +126 -0
- package/src/store/turns/memory-store.ts +51 -330
- package/src/store/turns/migrations.ts +4 -0
- package/src/store/turns/schema.ts +1 -0
- package/src/store/turns/sqlite-store/read.ts +19 -0
- package/src/store/turns/sqlite-store/rows.ts +5 -0
- package/src/store/turns/sqlite-store/write.ts +3 -2
- package/src/store/turns/sqlite-store.ts +5 -0
- package/src/store/turns/types.ts +15 -0
- package/extensions/dashboard-client/dist/assets/HealthTab-D4eVI8ar.js +0 -2
- package/extensions/dashboard-client/dist/assets/HealthTab-D4eVI8ar.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/MetricsTab-CKqJ0Zl-.js +0 -2
- package/extensions/dashboard-client/dist/assets/TurnsTab-C7pm92Hg.js +0 -2
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { getStateDir } from "../../store.js";
|
|
16
16
|
import { openStore } from "./utils.js";
|
|
17
|
+
/** A single cache_poison_events row (as stored + returned). */
|
|
18
|
+
// Sprint H split: cache_poison_events accessors live in context-health-cache-poison.ts.
|
|
19
|
+
// Re-exported here so existing importers (barrel `export *`) are unaffected.
|
|
20
|
+
export { recordCachePoisonEvent, readCachePoisonEvents, } from "./context-health-cache-poison.js";
|
|
17
21
|
// ─── context_health accessors ────────────────────────────────────────────────
|
|
18
22
|
/**
|
|
19
23
|
* Record one context health sample. All values are fully parameterized
|
|
@@ -100,7 +104,7 @@ export function readLatestContextHealth(stateDir = getStateDir()) {
|
|
|
100
104
|
try {
|
|
101
105
|
const db = openStore(stateDir);
|
|
102
106
|
const row = db
|
|
103
|
-
.prepare(`SELECT composite, drift_score, output_quality, error_score,
|
|
107
|
+
.prepare(`SELECT composite, drift_score, output_quality, error_score, store_error_score,
|
|
104
108
|
cache_health, cache_poison, ts, model_id
|
|
105
109
|
FROM context_health
|
|
106
110
|
ORDER BY ts DESC
|
|
@@ -113,6 +117,7 @@ export function readLatestContextHealth(stateDir = getStateDir()) {
|
|
|
113
117
|
driftScore: row.drift_score,
|
|
114
118
|
outputQuality: row.output_quality,
|
|
115
119
|
errorScore: row.error_score,
|
|
120
|
+
storeErrorScore: row.store_error_score ?? 1,
|
|
116
121
|
cacheHealth: row.cache_health,
|
|
117
122
|
cachePoison: row.cache_poison,
|
|
118
123
|
ts: row.ts,
|
|
@@ -144,50 +149,3 @@ export function readContextHealthTrend(stateDir = getStateDir(), limit) {
|
|
|
144
149
|
return [];
|
|
145
150
|
}
|
|
146
151
|
}
|
|
147
|
-
// ─── cache_poison_events accessors ───────────────────────────────────────────
|
|
148
|
-
/**
|
|
149
|
-
* Record one cache poison advisory event. Fully parameterized (PREVENT-002).
|
|
150
|
-
* Non-fatal: write errors are logged to stderr and silently swallowed.
|
|
151
|
-
*/
|
|
152
|
-
export function recordCachePoisonEvent(stateDir = getStateDir(), event) {
|
|
153
|
-
try {
|
|
154
|
-
const db = openStore(stateDir);
|
|
155
|
-
db.prepare(`INSERT INTO cache_poison_events
|
|
156
|
-
(ts, turn_index, session_id, layer, detail, severity)
|
|
157
|
-
VALUES (?, ?, ?, ?, ?, ?)`).run(event.ts, event.turnIndex, event.sessionId, event.layer, event.detail, event.severity);
|
|
158
|
-
}
|
|
159
|
-
catch (err) {
|
|
160
|
-
// Non-fatal: advisory logging must never block the agent loop.
|
|
161
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
162
|
-
process.stderr.write(`[context-health] recordCachePoisonEvent error: ${msg}\n`);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Read cache poison events since `sinceTs`, ordered descending by ts (most
|
|
167
|
-
* recent first), capped at 100 rows. Returns an empty array on any error
|
|
168
|
-
* (non-fatal). SQL is fully parameterized (PREVENT-002).
|
|
169
|
-
*/
|
|
170
|
-
export function readCachePoisonEvents(stateDir = getStateDir(), sinceTs) {
|
|
171
|
-
try {
|
|
172
|
-
const db = openStore(stateDir);
|
|
173
|
-
const rows = db
|
|
174
|
-
.prepare(`SELECT id, ts, turn_index, session_id, layer, detail, severity
|
|
175
|
-
FROM cache_poison_events
|
|
176
|
-
WHERE ts >= ?
|
|
177
|
-
ORDER BY ts DESC
|
|
178
|
-
LIMIT 100`)
|
|
179
|
-
.all(sinceTs);
|
|
180
|
-
return rows.map((r) => ({
|
|
181
|
-
id: r.id,
|
|
182
|
-
ts: r.ts,
|
|
183
|
-
turnIndex: r.turn_index,
|
|
184
|
-
sessionId: r.session_id,
|
|
185
|
-
layer: r.layer,
|
|
186
|
-
detail: r.detail,
|
|
187
|
-
severity: r.severity,
|
|
188
|
-
}));
|
|
189
|
-
}
|
|
190
|
-
catch {
|
|
191
|
-
return [];
|
|
192
|
-
}
|
|
193
|
-
}
|
|
@@ -66,6 +66,10 @@ export function getOrComputeEmbedding(db, content) {
|
|
|
66
66
|
// Logger not imported here to keep this leaf self-contained; stderr is
|
|
67
67
|
// acceptable for an error path that signals a bug, not a user‑facing log.
|
|
68
68
|
process.stderr.write(`[embedding-cache] getOrComputeEmbedding error: ${msg}\n`);
|
|
69
|
+
// Sprint H (Option A): this leaf src/ cache-persist path has no runtime
|
|
70
|
+
// handle, so the embedder cache-write failure is recorded by callers that
|
|
71
|
+
// own a runtime, not here.
|
|
72
|
+
// guardrails-allow INTERNAL-ERR: src/ leaf cache writer, no runtime handle — callers record embedder cache failures
|
|
69
73
|
return [];
|
|
70
74
|
}
|
|
71
75
|
}
|
|
@@ -51,6 +51,10 @@ export function initSchema(db) {
|
|
|
51
51
|
// S43: conversation_id + last_turn_id on session_state (legacy DBs have NULL).
|
|
52
52
|
ensureColumn(db, "session_state", "conversation_id", "TEXT");
|
|
53
53
|
ensureColumn(db, "session_state", "last_turn_id", "INTEGER");
|
|
54
|
+
// Sprint H (B2): store_error_score column on context_health — the 6th health
|
|
55
|
+
// axis. Additive; DBs created before this version have NULL → the health
|
|
56
|
+
// handler degrades to 1.0 (no data → healthy), matching the empty-ring rule.
|
|
57
|
+
ensureColumn(db, "context_health", "store_error_score", "REAL");
|
|
54
58
|
// S35: idempotent seed of the 9 achievement rows. ON CONFLICT(id) DO
|
|
55
59
|
// NOTHING so a re-open never clobbers an already-unlocked row's
|
|
56
60
|
// unlocked_at. No user input reaches this SQL (PREVENT-002 safe).
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { allocId, normalizeSessionId, resetIdCounter, } from "./ctx.js";
|
|
2
|
+
export function prune(ctx, policy) {
|
|
3
|
+
const cutoff = Date.now() - policy.maxTurnAgeMs;
|
|
4
|
+
let turnsRemoved = 0;
|
|
5
|
+
let recallRemoved = 0;
|
|
6
|
+
for (const [convId, turnIds] of ctx.convIndex.entries()) {
|
|
7
|
+
const total = turnIds.length;
|
|
8
|
+
const canDelete = Math.max(0, total - policy.keepMinPerConversation);
|
|
9
|
+
if (canDelete === 0)
|
|
10
|
+
continue;
|
|
11
|
+
// Find old turns to delete
|
|
12
|
+
const oldIds = turnIds
|
|
13
|
+
.filter((id) => {
|
|
14
|
+
const row = ctx.turns.get(id);
|
|
15
|
+
return row && row.entry.endedAt < cutoff;
|
|
16
|
+
})
|
|
17
|
+
.slice(0, canDelete);
|
|
18
|
+
for (const id of oldIds) {
|
|
19
|
+
// Remove recall for this turn, in place (preserve ctx.recall reference)
|
|
20
|
+
const before = ctx.recall.length;
|
|
21
|
+
let w = 0;
|
|
22
|
+
for (let i = 0; i < ctx.recall.length; i++) {
|
|
23
|
+
if (ctx.recall[i].turnId !== id)
|
|
24
|
+
ctx.recall[w++] = ctx.recall[i];
|
|
25
|
+
}
|
|
26
|
+
ctx.recall.length = w;
|
|
27
|
+
recallRemoved += before - ctx.recall.length;
|
|
28
|
+
ctx.turns.delete(id);
|
|
29
|
+
turnsRemoved++;
|
|
30
|
+
}
|
|
31
|
+
// Update convIndex
|
|
32
|
+
ctx.convIndex.set(convId, turnIds.filter((id) => ctx.turns.has(id)));
|
|
33
|
+
}
|
|
34
|
+
const branchesPreserved = ctx.forks.length;
|
|
35
|
+
return {
|
|
36
|
+
turnsRemoved,
|
|
37
|
+
recallRemoved,
|
|
38
|
+
branchesPreserved,
|
|
39
|
+
freedBytes: 0, // in-memory — no file size to measure
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function vacuum(_ctx) {
|
|
43
|
+
// No-op for in-memory store
|
|
44
|
+
}
|
|
45
|
+
export function checkpoint(ctx) {
|
|
46
|
+
// Emit turns in a stable order (by internal ID / insertion order)
|
|
47
|
+
const sorted = [...ctx.turns.values()].sort((a, b) => a.id - b.id);
|
|
48
|
+
const turns = sorted.map((r) => r.entry);
|
|
49
|
+
// Build mapping: internal ID → 1-based position
|
|
50
|
+
const idToPos = new Map();
|
|
51
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
52
|
+
idToPos.set(sorted[i].id, i + 1);
|
|
53
|
+
}
|
|
54
|
+
// Replace recall's turnId with the stable 1-based position
|
|
55
|
+
const recall = ctx.recall.map((r) => ({
|
|
56
|
+
turnId: String(idToPos.get(r.turnId) ?? r.turnId),
|
|
57
|
+
checkpointId: r.entry.checkpointId,
|
|
58
|
+
score: r.entry.score,
|
|
59
|
+
source: r.entry.source,
|
|
60
|
+
raptorLevel: r.entry.raptorLevel,
|
|
61
|
+
}));
|
|
62
|
+
return {
|
|
63
|
+
version: 1,
|
|
64
|
+
exportedAt: Date.now(),
|
|
65
|
+
turns,
|
|
66
|
+
recall,
|
|
67
|
+
forks: [...ctx.forks],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export function restore(ctx, from) {
|
|
71
|
+
clear(ctx);
|
|
72
|
+
// Map: 1-based position → new internal ID
|
|
73
|
+
const posToNewId = new Map();
|
|
74
|
+
for (let i = 0; i < from.turns.length; i++) {
|
|
75
|
+
const t = from.turns[i];
|
|
76
|
+
const newId = allocId();
|
|
77
|
+
const sid = normalizeSessionId(t.sessionId);
|
|
78
|
+
const normalized = { ...t, sessionId: sid };
|
|
79
|
+
ctx.turns.set(newId, { id: newId, entry: normalized });
|
|
80
|
+
// Update indices
|
|
81
|
+
const convIds = ctx.convIndex.get(t.conversationId) ?? [];
|
|
82
|
+
convIds.push(newId);
|
|
83
|
+
ctx.convIndex.set(t.conversationId, convIds);
|
|
84
|
+
ctx.sessionConv.set(sid, t.conversationId);
|
|
85
|
+
posToNewId.set(i + 1, newId);
|
|
86
|
+
}
|
|
87
|
+
// Re-attach recall with position-based TurnId → new internal ID mapping
|
|
88
|
+
for (const r of from.recall) {
|
|
89
|
+
const targetInternalId = posToNewId.get(Number(r.turnId));
|
|
90
|
+
if (targetInternalId !== undefined) {
|
|
91
|
+
ctx.recall.push({
|
|
92
|
+
turnId: targetInternalId,
|
|
93
|
+
entry: r,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// In-place replace forks (preserve ctx.forks reference)
|
|
98
|
+
ctx.forks.length = 0;
|
|
99
|
+
ctx.forks.push(...from.forks);
|
|
100
|
+
}
|
|
101
|
+
export function clear(ctx) {
|
|
102
|
+
ctx.turns.clear();
|
|
103
|
+
ctx.recall.length = 0;
|
|
104
|
+
ctx.forks.length = 0;
|
|
105
|
+
ctx.convIndex.clear();
|
|
106
|
+
ctx.sessionConv.clear();
|
|
107
|
+
resetIdCounter();
|
|
108
|
+
}
|
|
109
|
+
export function stampTurnsEpoch(ctx, sessionId, epochId) {
|
|
110
|
+
const sid = normalizeSessionId(sessionId);
|
|
111
|
+
let stamped = 0;
|
|
112
|
+
for (const row of ctx.turns.values()) {
|
|
113
|
+
if (row.epochId === undefined && row.entry.sessionId === sid) {
|
|
114
|
+
row.epochId = epochId;
|
|
115
|
+
stamped++;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return stamped;
|
|
119
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ctx.ts — internal context interface for InMemoryTurnStore method bodies
|
|
3
|
+
* (extracted from memory-store.ts). Each free function in this directory
|
|
4
|
+
* operates on a MemoryTurnStoreCtx so the shell class can delegate without
|
|
5
|
+
* exposing its private fields.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors sqlite-store/ctx.ts. The Map/array references are stable for the
|
|
8
|
+
* life of the store: arrays (recall, forks) are mutated IN PLACE
|
|
9
|
+
* (push/splice/length=0), never reassigned, so a ctx captured at any point
|
|
10
|
+
* sees the current contents. The id counter is module-scoped here so both the
|
|
11
|
+
* shell (writers) and admin.ts share one allocator.
|
|
12
|
+
*/
|
|
13
|
+
import { randomBytes } from "node:crypto";
|
|
14
|
+
// Auto-increment counter — shared by writers (appendTurn/forkConversation) and
|
|
15
|
+
// admin (restore). Module-scoped so a single counter spans the store's life.
|
|
16
|
+
let nextId = 1;
|
|
17
|
+
export function allocId() {
|
|
18
|
+
return nextId++;
|
|
19
|
+
}
|
|
20
|
+
export function resetIdCounter() {
|
|
21
|
+
nextId = 1;
|
|
22
|
+
}
|
|
23
|
+
export function newConversationId() {
|
|
24
|
+
return `conv_${randomBytes(8).toString("hex")}`;
|
|
25
|
+
}
|
|
26
|
+
export function normalizeSessionId(sid) {
|
|
27
|
+
return sid.replace(/-.*$/, "");
|
|
28
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { normalizeSessionId } from "./ctx.js";
|
|
2
|
+
export function query(ctx, filter) {
|
|
3
|
+
let results = [...ctx.turns.values()].map((r) => r.entry);
|
|
4
|
+
if (filter.conversationId !== undefined) {
|
|
5
|
+
results = results.filter((t) => t.conversationId === filter.conversationId);
|
|
6
|
+
}
|
|
7
|
+
if (filter.sessionId !== undefined) {
|
|
8
|
+
results = results.filter((t) => normalizeSessionId(t.sessionId) ===
|
|
9
|
+
normalizeSessionId(filter.sessionId));
|
|
10
|
+
}
|
|
11
|
+
if (filter.sinceMs !== undefined) {
|
|
12
|
+
results = results.filter((t) => t.endedAt >= filter.sinceMs);
|
|
13
|
+
}
|
|
14
|
+
if (filter.untilMs !== undefined) {
|
|
15
|
+
results = results.filter((t) => t.endedAt <= filter.untilMs);
|
|
16
|
+
}
|
|
17
|
+
if (filter.pressureBand !== undefined) {
|
|
18
|
+
results = results.filter((t) => t.pressureBand === filter.pressureBand);
|
|
19
|
+
}
|
|
20
|
+
results.sort((a, b) => a.endedAt - b.endedAt);
|
|
21
|
+
const offset = filter.offset ?? 0;
|
|
22
|
+
const limit = filter.limit ?? 1000;
|
|
23
|
+
return results.slice(offset, offset + limit);
|
|
24
|
+
}
|
|
25
|
+
export function getTurn(ctx, turnId) {
|
|
26
|
+
const row = ctx.turns.get(Number(turnId));
|
|
27
|
+
return row ? { ...row.entry, epochId: row.epochId } : undefined;
|
|
28
|
+
}
|
|
29
|
+
export function getTurnByIndex(ctx, conversationId, turnIndex) {
|
|
30
|
+
const ids = ctx.convIndex.get(conversationId) ?? [];
|
|
31
|
+
for (const id of ids) {
|
|
32
|
+
const row = ctx.turns.get(id);
|
|
33
|
+
if (row && row.entry.turnIndex === turnIndex) {
|
|
34
|
+
return { ...row.entry, epochId: row.epochId };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
export function listRecall(ctx, turnId) {
|
|
40
|
+
return ctx.recall
|
|
41
|
+
.filter((r) => r.turnId === Number(turnId))
|
|
42
|
+
.map((r) => r.entry)
|
|
43
|
+
.sort((a, b) => b.score - a.score);
|
|
44
|
+
}
|
|
45
|
+
export function listRecallByIndex(ctx, conversationId, turnIndex) {
|
|
46
|
+
const turn = getTurnByIndex(ctx, conversationId, turnIndex);
|
|
47
|
+
if (!turn)
|
|
48
|
+
return [];
|
|
49
|
+
// Resolve the numeric row id for this turn via the convIndex.
|
|
50
|
+
const ids = ctx.convIndex.get(conversationId) ?? [];
|
|
51
|
+
for (const id of ids) {
|
|
52
|
+
const row = ctx.turns.get(id);
|
|
53
|
+
if (row && row.entry.turnIndex === turnIndex) {
|
|
54
|
+
return listRecall(ctx, String(id));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
export function listForks(ctx, conversationId) {
|
|
60
|
+
return ctx.forks
|
|
61
|
+
.filter((f) => f.parentConversationId === conversationId)
|
|
62
|
+
.sort((a, b) => a.createdAt - b.createdAt);
|
|
63
|
+
}
|
|
64
|
+
export function countTurns(ctx, conversationId) {
|
|
65
|
+
const ids = ctx.convIndex.get(conversationId);
|
|
66
|
+
return ids?.length ?? 0;
|
|
67
|
+
}
|
|
68
|
+
export function conversationStats(ctx, conversationId) {
|
|
69
|
+
const ids = ctx.convIndex.get(conversationId) ?? [];
|
|
70
|
+
const entries = ids
|
|
71
|
+
.map((id) => ctx.turns.get(id)?.entry)
|
|
72
|
+
.filter((e) => e !== undefined);
|
|
73
|
+
if (entries.length === 0) {
|
|
74
|
+
return {
|
|
75
|
+
turnCount: 0,
|
|
76
|
+
firstTurnAt: 0,
|
|
77
|
+
lastTurnAt: 0,
|
|
78
|
+
avgCtxPercent: 0,
|
|
79
|
+
pressureBands: {},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
let ctxSum = 0;
|
|
83
|
+
let ctxCount = 0;
|
|
84
|
+
const bands = {};
|
|
85
|
+
for (const e of entries) {
|
|
86
|
+
if (e.ctxPercent !== undefined) {
|
|
87
|
+
ctxSum += e.ctxPercent;
|
|
88
|
+
ctxCount++;
|
|
89
|
+
}
|
|
90
|
+
if (e.pressureBand)
|
|
91
|
+
bands[e.pressureBand] = (bands[e.pressureBand] ?? 0) + 1;
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
turnCount: entries.length,
|
|
95
|
+
firstTurnAt: entries[0].endedAt,
|
|
96
|
+
lastTurnAt: entries[entries.length - 1].endedAt,
|
|
97
|
+
avgCtxPercent: ctxCount > 0 ? ctxSum / ctxCount : 0,
|
|
98
|
+
pressureBands: bands,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export function nextTurnIndexFor(ctx, conversationId) {
|
|
102
|
+
let max = -1;
|
|
103
|
+
for (const [, row] of ctx.turns) {
|
|
104
|
+
if (row.entry.conversationId === conversationId &&
|
|
105
|
+
row.entry.turnIndex > max) {
|
|
106
|
+
max = row.entry.turnIndex;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return max + 1;
|
|
110
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* write.ts — TurnWriter method bodies for InMemoryTurnStore (extracted from
|
|
3
|
+
* memory-store.ts). Free functions operating on a MemoryTurnStoreCtx; the
|
|
4
|
+
* shell class delegates here. Mirrors sqlite-store/write.ts.
|
|
5
|
+
*/
|
|
6
|
+
import { DuplicateTurnError, } from "../types.js";
|
|
7
|
+
import { allocId, newConversationId, normalizeSessionId, } from "./ctx.js";
|
|
8
|
+
export function appendTurn(ctx, entry) {
|
|
9
|
+
// Check for duplicate (conversationId, turnIndex)
|
|
10
|
+
for (const [, row] of ctx.turns) {
|
|
11
|
+
if (row.entry.conversationId === entry.conversationId &&
|
|
12
|
+
row.entry.turnIndex === entry.turnIndex) {
|
|
13
|
+
throw new DuplicateTurnError(entry.conversationId, entry.turnIndex);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const id = allocId();
|
|
17
|
+
const sid = normalizeSessionId(entry.sessionId);
|
|
18
|
+
const normalized = { ...entry, sessionId: sid };
|
|
19
|
+
ctx.turns.set(id, { id, entry: normalized });
|
|
20
|
+
// Update indices
|
|
21
|
+
const convIds = ctx.convIndex.get(entry.conversationId) ?? [];
|
|
22
|
+
convIds.push(id);
|
|
23
|
+
ctx.convIndex.set(entry.conversationId, convIds);
|
|
24
|
+
ctx.sessionConv.set(sid, entry.conversationId);
|
|
25
|
+
return String(id);
|
|
26
|
+
}
|
|
27
|
+
export function appendRecall(ctx, entry) {
|
|
28
|
+
// De-dup by (turn_id, checkpoint_id)
|
|
29
|
+
const exists = ctx.recall.some((r) => r.turnId === Number(entry.turnId) &&
|
|
30
|
+
r.entry.checkpointId === entry.checkpointId);
|
|
31
|
+
if (!exists) {
|
|
32
|
+
ctx.recall.push({ turnId: Number(entry.turnId), entry });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function ensureConversationId(ctx, sessionId) {
|
|
36
|
+
const sid = normalizeSessionId(sessionId);
|
|
37
|
+
const existing = ctx.sessionConv.get(sid);
|
|
38
|
+
if (existing)
|
|
39
|
+
return existing;
|
|
40
|
+
// Generate and store a new conversation ID for this session
|
|
41
|
+
const convId = newConversationId();
|
|
42
|
+
ctx.sessionConv.set(sid, convId);
|
|
43
|
+
return convId;
|
|
44
|
+
}
|
|
45
|
+
export function forkConversation(ctx, parentId, forkTurnIndex) {
|
|
46
|
+
const childId = newConversationId();
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
// 1. Record the fork lineage
|
|
49
|
+
ctx.forks.push({
|
|
50
|
+
parentConversationId: parentId,
|
|
51
|
+
childConversationId: childId,
|
|
52
|
+
forkTurnIndex,
|
|
53
|
+
createdAt: now,
|
|
54
|
+
});
|
|
55
|
+
// 2. Find the parent's turn at forkTurnIndex
|
|
56
|
+
const parentTurnIds = ctx.convIndex.get(parentId) ?? [];
|
|
57
|
+
const parentTurnRow = parentTurnIds
|
|
58
|
+
.map((id) => ctx.turns.get(id))
|
|
59
|
+
.find((r) => r?.entry.turnIndex === forkTurnIndex);
|
|
60
|
+
if (!parentTurnRow)
|
|
61
|
+
return childId; // no parent turn — nothing to seed
|
|
62
|
+
// 3. Create a seed turn in the child conversation
|
|
63
|
+
const seedId = allocId();
|
|
64
|
+
const seedEntry = {
|
|
65
|
+
conversationId: childId,
|
|
66
|
+
sessionId: `fork_${childId}`,
|
|
67
|
+
turnIndex: 0,
|
|
68
|
+
role: "system",
|
|
69
|
+
endedAt: now,
|
|
70
|
+
};
|
|
71
|
+
ctx.turns.set(seedId, { id: seedId, entry: seedEntry });
|
|
72
|
+
ctx.convIndex.set(childId, [seedId]);
|
|
73
|
+
// 4. Copy the parent's recall entries into the child's seed turn
|
|
74
|
+
const parentRecall = ctx.recall.filter((r) => r.turnId === parentTurnRow.id);
|
|
75
|
+
for (const r of parentRecall) {
|
|
76
|
+
ctx.recall.push({
|
|
77
|
+
turnId: seedId,
|
|
78
|
+
entry: {
|
|
79
|
+
turnId: String(seedId),
|
|
80
|
+
checkpointId: r.entry.checkpointId,
|
|
81
|
+
score: r.entry.score,
|
|
82
|
+
source: r.entry.source,
|
|
83
|
+
raptorLevel: r.entry.raptorLevel,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return childId;
|
|
88
|
+
}
|