pi-mega-compact 0.21.3 → 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.
Files changed (131) hide show
  1. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.js +12 -2
  2. package/dist/extensions/mega-events/context-handler/afterCompact.js +11 -0
  3. package/dist/extensions/mega-events/context-handler/dbMirrorAppend.js +8 -0
  4. package/dist/extensions/mega-events/health-handler.js +12 -2
  5. package/dist/extensions/mega-events/session-handlers.js +1 -0
  6. package/dist/extensions/mega-pipeline/compact/run.js +11 -0
  7. package/dist/extensions/mega-runtime/helpers.js +45 -0
  8. package/dist/extensions/mega-runtime/internal-errors.js +14 -0
  9. package/dist/extensions/mega-runtime/runtime-instrumentation.js +166 -0
  10. package/dist/extensions/mega-runtime/runtime.js +27 -193
  11. package/dist/extensions/mega-runtime/state.js +1 -1
  12. package/dist/extensions/mega-runtime/widget.js +1 -1
  13. package/dist/extensions/mega-turn-store.js +5 -0
  14. package/dist/src/bridge/factory.js +15 -1
  15. package/dist/src/contextHealth.js +13 -5
  16. package/dist/src/metrics/turns.js +6 -1
  17. package/dist/src/store/sqlite/context-health-cache-poison.js +60 -0
  18. package/dist/src/store/sqlite/context-health.js +6 -48
  19. package/dist/src/store/sqlite/embedding-cache.js +4 -0
  20. package/dist/src/store/sqlite/schema.js +4 -0
  21. package/dist/src/store/turns/memory-store/admin.js +119 -0
  22. package/dist/src/store/turns/memory-store/ctx.js +28 -0
  23. package/dist/src/store/turns/memory-store/read.js +110 -0
  24. package/dist/src/store/turns/memory-store/write.js +88 -0
  25. package/dist/src/store/turns/memory-store.js +44 -273
  26. package/dist/src/store/turns/migrations.js +4 -0
  27. package/dist/src/store/turns/schema.js +1 -0
  28. package/dist/src/store/turns/sqlite-store/read.js +13 -0
  29. package/dist/src/store/turns/sqlite-store/rows.js +4 -0
  30. package/dist/src/store/turns/sqlite-store/write.js +3 -3
  31. package/dist/src/store/turns/sqlite-store.js +4 -0
  32. package/extensions/dashboard-client/dist/assets/{AdminTab-BsA8-_bX.js → AdminTab-C4Y0_UVx.js} +2 -2
  33. package/extensions/dashboard-client/dist/assets/{AdminTab-BsA8-_bX.js.map → AdminTab-C4Y0_UVx.js.map} +1 -1
  34. package/extensions/dashboard-client/dist/assets/{AreaChart-BRHEfWhc.js → AreaChart-MCWK9rn6.js} +2 -2
  35. package/extensions/dashboard-client/dist/assets/{AreaChart-BRHEfWhc.js.map → AreaChart-MCWK9rn6.js.map} +1 -1
  36. package/extensions/dashboard-client/dist/assets/{BarChart-C-M0LaFc.js → BarChart-CvSGAiWV.js} +2 -2
  37. package/extensions/dashboard-client/dist/assets/{BarChart-C-M0LaFc.js.map → BarChart-CvSGAiWV.js.map} +1 -1
  38. package/extensions/dashboard-client/dist/assets/{CacheTab-Cj3n59U3.js → CacheTab-DnMEVRro.js} +2 -2
  39. package/extensions/dashboard-client/dist/assets/{CacheTab-Cj3n59U3.js.map → CacheTab-DnMEVRro.js.map} +1 -1
  40. package/extensions/dashboard-client/dist/assets/{ConfigTab-C016XTNC.js → ConfigTab-BExuZYy2.js} +2 -2
  41. package/extensions/dashboard-client/dist/assets/{ConfigTab-C016XTNC.js.map → ConfigTab-BExuZYy2.js.map} +1 -1
  42. package/extensions/dashboard-client/dist/assets/{EventsTab-DE29fuqe.js → EventsTab-CspsEslY.js} +2 -2
  43. package/extensions/dashboard-client/dist/assets/{EventsTab-DE29fuqe.js.map → EventsTab-CspsEslY.js.map} +1 -1
  44. package/extensions/dashboard-client/dist/assets/HealthTab-DiWzo2ML.js +2 -0
  45. package/extensions/dashboard-client/dist/assets/HealthTab-DiWzo2ML.js.map +1 -0
  46. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-4fJ7M4S4.js → MaintenanceTab-9NJuv3dB.js} +2 -2
  47. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-4fJ7M4S4.js.map → MaintenanceTab-9NJuv3dB.js.map} +1 -1
  48. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CDbcK9e4.js → MemoryMapTab-YpJK1h17.js} +2 -2
  49. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CDbcK9e4.js.map → MemoryMapTab-YpJK1h17.js.map} +1 -1
  50. package/extensions/dashboard-client/dist/assets/{MetricsCards-VUEcgGzo.js → MetricsCards-D2Mom1M7.js} +2 -2
  51. package/extensions/dashboard-client/dist/assets/{MetricsCards-VUEcgGzo.js.map → MetricsCards-D2Mom1M7.js.map} +1 -1
  52. package/extensions/dashboard-client/dist/assets/MetricsTab-DqWF7wud.js +2 -0
  53. package/extensions/dashboard-client/dist/assets/{MetricsTab-CKqJ0Zl-.js.map → MetricsTab-DqWF7wud.js.map} +1 -1
  54. package/extensions/dashboard-client/dist/assets/{OverviewTab-9Kxo79mi.js → OverviewTab-YuRxjV9U.js} +2 -2
  55. package/extensions/dashboard-client/dist/assets/{OverviewTab-9Kxo79mi.js.map → OverviewTab-YuRxjV9U.js.map} +1 -1
  56. package/extensions/dashboard-client/dist/assets/{ReposTab-C4iveqrP.js → ReposTab-sXMrR7je.js} +2 -2
  57. package/extensions/dashboard-client/dist/assets/{ReposTab-C4iveqrP.js.map → ReposTab-sXMrR7je.js.map} +1 -1
  58. package/extensions/dashboard-client/dist/assets/{SessionsTab-DSEwkMQm.js → SessionsTab-4YCXeGNR.js} +2 -2
  59. package/extensions/dashboard-client/dist/assets/{SessionsTab-DSEwkMQm.js.map → SessionsTab-4YCXeGNR.js.map} +1 -1
  60. package/extensions/dashboard-client/dist/assets/{SetupTab-CCyWgy4O.js → SetupTab-67pZcM_W.js} +2 -2
  61. package/extensions/dashboard-client/dist/assets/{SetupTab-CCyWgy4O.js.map → SetupTab-67pZcM_W.js.map} +1 -1
  62. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CyD8e_e_.js → TimeSavedCard-DInjNETP.js} +2 -2
  63. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CyD8e_e_.js.map → TimeSavedCard-DInjNETP.js.map} +1 -1
  64. package/extensions/dashboard-client/dist/assets/{TurnMemoryView-5tLbWkRv.js → TurnMemoryView-C3Bif8ix.js} +2 -2
  65. package/extensions/dashboard-client/dist/assets/{TurnMemoryView-5tLbWkRv.js.map → TurnMemoryView-C3Bif8ix.js.map} +1 -1
  66. package/extensions/dashboard-client/dist/assets/TurnsTab-BUGfiANp.js +2 -0
  67. package/extensions/dashboard-client/dist/assets/{TurnsTab-C7pm92Hg.js.map → TurnsTab-BUGfiANp.js.map} +1 -1
  68. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-ObhcIyfN.js → VcStatusBadge-DKU7qz9f.js} +2 -2
  69. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-ObhcIyfN.js.map → VcStatusBadge-DKU7qz9f.js.map} +1 -1
  70. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-BtHZb7o7.js → VectorCortexTab-BsVSf4xm.js} +2 -2
  71. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-BtHZb7o7.js.map → VectorCortexTab-BsVSf4xm.js.map} +1 -1
  72. package/extensions/dashboard-client/dist/assets/{WikiTab-lQ4l3QIK.js → WikiTab-DZw9_Fwd.js} +2 -2
  73. package/extensions/dashboard-client/dist/assets/{WikiTab-lQ4l3QIK.js.map → WikiTab-DZw9_Fwd.js.map} +1 -1
  74. package/extensions/dashboard-client/dist/assets/{button-DhQQAJGm.js → button-BLRPN489.js} +2 -2
  75. package/extensions/dashboard-client/dist/assets/{button-DhQQAJGm.js.map → button-BLRPN489.js.map} +1 -1
  76. package/extensions/dashboard-client/dist/assets/{card-Cfhz9X31.js → card-BGkkcM4Q.js} +2 -2
  77. package/extensions/dashboard-client/dist/assets/{card-Cfhz9X31.js.map → card-BGkkcM4Q.js.map} +1 -1
  78. package/extensions/dashboard-client/dist/assets/{client-extra-CKNOGEz_.js → client-extra-0OJClxHD.js} +2 -2
  79. package/extensions/dashboard-client/dist/assets/{client-extra-CKNOGEz_.js.map → client-extra-0OJClxHD.js.map} +1 -1
  80. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-JKjK9_cB.js → generateCategoricalChart-Dl5lmrHF.js} +2 -2
  81. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-JKjK9_cB.js.map → generateCategoricalChart-Dl5lmrHF.js.map} +1 -1
  82. package/extensions/dashboard-client/dist/assets/health-lSeDFTz7.js.map +1 -1
  83. package/extensions/dashboard-client/dist/assets/{index-iuxgtzEW.js → index-CH_AD-S_.js} +3 -3
  84. package/extensions/dashboard-client/dist/assets/{index-iuxgtzEW.js.map → index-CH_AD-S_.js.map} +1 -1
  85. package/extensions/dashboard-client/dist/assets/{switch-7EHEQMSP.js → switch-BPP2-d12.js} +2 -2
  86. package/extensions/dashboard-client/dist/assets/{switch-7EHEQMSP.js.map → switch-BPP2-d12.js.map} +1 -1
  87. package/extensions/dashboard-client/dist/assets/{toggle-z1lWY4UM.js → toggle-Dez0JaMV.js} +2 -2
  88. package/extensions/dashboard-client/dist/assets/{toggle-z1lWY4UM.js.map → toggle-Dez0JaMV.js.map} +1 -1
  89. package/extensions/dashboard-client/dist/assets/{useSSE-sctOZuRM.js → useSSE-Cx2W7BTo.js} +2 -2
  90. package/extensions/dashboard-client/dist/assets/{useSSE-sctOZuRM.js.map → useSSE-Cx2W7BTo.js.map} +1 -1
  91. package/extensions/dashboard-client/dist/index.html +1 -1
  92. package/extensions/dashboard-client/src/api/health.ts +1 -0
  93. package/extensions/dashboard-client/src/tabs/HealthTab.tsx +1 -0
  94. package/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.ts +12 -2
  95. package/extensions/mega-events/context-handler/afterCompact.ts +11 -0
  96. package/extensions/mega-events/context-handler/dbMirrorAppend.ts +8 -0
  97. package/extensions/mega-events/health-handler.ts +13 -3
  98. package/extensions/mega-events/session-handlers.ts +1 -0
  99. package/extensions/mega-pipeline/compact/run.ts +11 -0
  100. package/extensions/mega-runtime/helpers.ts +46 -0
  101. package/extensions/mega-runtime/internal-errors.ts +30 -0
  102. package/extensions/mega-runtime/runtime-instrumentation.ts +195 -0
  103. package/extensions/mega-runtime/runtime.ts +29 -209
  104. package/extensions/mega-runtime/state.ts +1 -1
  105. package/extensions/mega-runtime/widget.ts +1 -1
  106. package/extensions/mega-turn-store.ts +9 -0
  107. package/package.json +2 -2
  108. package/src/bridge/factory.ts +15 -1
  109. package/src/bridge/types.ts +2 -0
  110. package/src/contextHealth.ts +26 -6
  111. package/src/metrics/turns.ts +6 -1
  112. package/src/store/sqlite/context-health-cache-poison.ts +97 -0
  113. package/src/store/sqlite/context-health.ts +12 -84
  114. package/src/store/sqlite/embedding-cache.ts +4 -0
  115. package/src/store/sqlite/schema.ts +4 -0
  116. package/src/store/turns/memory-store/admin.ts +171 -0
  117. package/src/store/turns/memory-store/ctx.ts +62 -0
  118. package/src/store/turns/memory-store/read.ts +172 -0
  119. package/src/store/turns/memory-store/write.ts +126 -0
  120. package/src/store/turns/memory-store.ts +51 -330
  121. package/src/store/turns/migrations.ts +4 -0
  122. package/src/store/turns/schema.ts +1 -0
  123. package/src/store/turns/sqlite-store/read.ts +19 -0
  124. package/src/store/turns/sqlite-store/rows.ts +5 -0
  125. package/src/store/turns/sqlite-store/write.ts +3 -2
  126. package/src/store/turns/sqlite-store.ts +5 -0
  127. package/src/store/turns/types.ts +15 -0
  128. package/extensions/dashboard-client/dist/assets/HealthTab-D4eVI8ar.js +0 -2
  129. package/extensions/dashboard-client/dist/assets/HealthTab-D4eVI8ar.js.map +0 -1
  130. package/extensions/dashboard-client/dist/assets/MetricsTab-CKqJ0Zl-.js +0 -2
  131. package/extensions/dashboard-client/dist/assets/TurnsTab-C7pm92Hg.js +0 -2
@@ -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
+ }