pi-mega-compact 0.13.1 → 0.13.2

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 (207) hide show
  1. package/dist/extensions/dashboard-server/api-contracts/embedder-health.js +11 -0
  2. package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +225 -0
  3. package/dist/extensions/dashboard-server/api-contracts/endpoints/types.js +1 -0
  4. package/dist/extensions/dashboard-server/api-contracts/endpoints.js +5 -224
  5. package/dist/extensions/dashboard-server/api-contracts/raptor.js +8 -0
  6. package/dist/extensions/dashboard-server/api-contracts.test/_helpers.js +32 -0
  7. package/dist/extensions/dashboard-server/routes-cache.test/_helpers.js +48 -0
  8. package/dist/extensions/dashboard-server/routes-embedder-health.js +67 -0
  9. package/dist/extensions/dashboard-server/routes-raptor.js +88 -0
  10. package/dist/extensions/dashboard-server/routes-setup.js +1 -1
  11. package/dist/extensions/dashboard-server/routes.js +2 -0
  12. package/dist/extensions/dashboard-server/server.js +5 -1
  13. package/dist/extensions/mega-cache-replay.test/_helpers.js +175 -0
  14. package/dist/extensions/mega-commands/dataCommands.js +151 -0
  15. package/dist/extensions/mega-commands/helpers.js +61 -0
  16. package/dist/extensions/mega-commands/historyCommands.js +97 -0
  17. package/dist/extensions/mega-commands/setupCommand.js +200 -0
  18. package/dist/extensions/mega-commands.js +14 -499
  19. package/dist/extensions/mega-compact-s38.test/_helpers.js +194 -0
  20. package/dist/extensions/mega-compact.test/_helpers.js +225 -0
  21. package/dist/extensions/mega-events/agent-handlers/agentEndHandler.js +211 -0
  22. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/cacheStripe.js +49 -0
  23. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/contextHealth.js +33 -0
  24. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/errorRetry.js +387 -0
  25. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/event.js +7 -0
  26. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/gameScoring.js +55 -0
  27. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/lengthStop.js +12 -0
  28. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/memoryReview.js +22 -0
  29. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.js +38 -0
  30. package/dist/extensions/mega-events/agent-handlers/turnEndHandler.js +36 -0
  31. package/dist/extensions/mega-events/agent-handlers.js +4 -799
  32. package/dist/extensions/mega-events/context-handler/afterCompact.js +163 -0
  33. package/dist/extensions/mega-events/context-handler/messageText.js +12 -0
  34. package/dist/extensions/mega-events/context-handler/tailResult.js +46 -0
  35. package/dist/extensions/mega-events/context-handler.js +11 -198
  36. package/dist/extensions/mega-events/separated-prompt.test/_helpers.js +25 -0
  37. package/dist/src/config.js +2 -0
  38. package/dist/src/dedup-engine.test/_helpers.js +55 -0
  39. package/dist/src/e2e.test/_helpers.js +52 -0
  40. package/dist/src/embedder.js +1 -0
  41. package/dist/src/httpEmbedder.js +6 -0
  42. package/dist/src/hyde.js +146 -0
  43. package/dist/src/importance.test/_helpers.js +5 -0
  44. package/dist/src/memoryGraph/sources/checkpoints.js +78 -0
  45. package/dist/src/memoryGraph/sources/edges.js +17 -0
  46. package/dist/src/memoryGraph/sources/flags.js +22 -0
  47. package/dist/src/memoryGraph/sources/helpers.js +14 -0
  48. package/dist/src/memoryGraph/sources/memories.js +88 -0
  49. package/dist/src/memoryGraph/sources/raptor.js +26 -0
  50. package/dist/src/memoryGraph/sources/turns.js +176 -0
  51. package/dist/src/memoryGraph/sources.js +15 -441
  52. package/dist/src/memoryGraph-gates.test/_helpers.js +120 -0
  53. package/dist/src/ratio.bench.test/_helpers.js +198 -0
  54. package/dist/src/recall/async.js +112 -0
  55. package/dist/src/recall/format.js +149 -0
  56. package/dist/src/recall/memory.js +62 -0
  57. package/dist/src/recall/reformulate.js +82 -0
  58. package/dist/src/recall/sync.js +169 -0
  59. package/dist/src/recall/types.js +1 -0
  60. package/dist/src/recall.js +13 -530
  61. package/dist/src/store/sqlite/schema/core.js +232 -0
  62. package/dist/src/store/sqlite/schema/game.js +65 -0
  63. package/dist/src/store/sqlite/schema/plan-v2.js +107 -0
  64. package/dist/src/store/sqlite/schema/turns.js +59 -0
  65. package/dist/src/store/sqlite/schema.js +8 -441
  66. package/dist/src/store/turns/contract-compliance.test/_helpers.js +21 -0
  67. package/dist/src/store/turns/sqlite-store/admin.js +171 -0
  68. package/dist/src/store/turns/sqlite-store/ctx.js +1 -0
  69. package/dist/src/store/turns/sqlite-store/read.js +111 -0
  70. package/dist/src/store/turns/sqlite-store/rows.js +42 -0
  71. package/dist/src/store/turns/sqlite-store/write.js +102 -0
  72. package/dist/src/store/turns/sqlite-store.js +31 -367
  73. package/dist/src/vectorStore/class.js +356 -0
  74. package/dist/src/vectorStore/hash.js +10 -0
  75. package/dist/src/vectorStore/types.js +2 -0
  76. package/dist/src/vectorStore.js +8 -359
  77. package/dist/src/vectorStore.test/_helpers.js +17 -0
  78. package/extensions/dashboard-client/dist/assets/{CacheTab-8HRroK9K.js → CacheTab-DPRHjYZ0.js} +2 -2
  79. package/extensions/dashboard-client/dist/assets/{CacheTab-8HRroK9K.js.map → CacheTab-DPRHjYZ0.js.map} +1 -1
  80. package/extensions/dashboard-client/dist/assets/{EventsTab-ncobZw-I.js → EventsTab-CgwYq-Qf.js} +2 -2
  81. package/extensions/dashboard-client/dist/assets/{EventsTab-ncobZw-I.js.map → EventsTab-CgwYq-Qf.js.map} +1 -1
  82. package/extensions/dashboard-client/dist/assets/{HealthTab-C6BI1EZG.js → HealthTab-BgOCjVzR.js} +2 -2
  83. package/extensions/dashboard-client/dist/assets/{HealthTab-C6BI1EZG.js.map → HealthTab-BgOCjVzR.js.map} +1 -1
  84. package/extensions/dashboard-client/dist/assets/MaintenanceTab-Yw1yFpsy.js +2 -0
  85. package/extensions/dashboard-client/dist/assets/MaintenanceTab-Yw1yFpsy.js.map +1 -0
  86. package/extensions/dashboard-client/dist/assets/MemoryMapTab-yHUX7Yzi.js +2 -0
  87. package/extensions/dashboard-client/dist/assets/MemoryMapTab-yHUX7Yzi.js.map +1 -0
  88. package/extensions/dashboard-client/dist/assets/{MetricsTab-B88XTCMk.js → MetricsTab-CcH9fP8P.js} +2 -2
  89. package/extensions/dashboard-client/dist/assets/{MetricsTab-B88XTCMk.js.map → MetricsTab-CcH9fP8P.js.map} +1 -1
  90. package/extensions/dashboard-client/dist/assets/{OverviewTab-CGZrSia-.js → OverviewTab-BOs8ZNev.js} +2 -2
  91. package/extensions/dashboard-client/dist/assets/{OverviewTab-CGZrSia-.js.map → OverviewTab-BOs8ZNev.js.map} +1 -1
  92. package/extensions/dashboard-client/dist/assets/{ReposTab-BZo5cNS1.js → ReposTab-B-MfSPvg.js} +2 -2
  93. package/extensions/dashboard-client/dist/assets/{ReposTab-BZo5cNS1.js.map → ReposTab-B-MfSPvg.js.map} +1 -1
  94. package/extensions/dashboard-client/dist/assets/{SessionsTab-C2RokJcw.js → SessionsTab-BmeN649Q.js} +2 -2
  95. package/extensions/dashboard-client/dist/assets/{SessionsTab-C2RokJcw.js.map → SessionsTab-BmeN649Q.js.map} +1 -1
  96. package/extensions/dashboard-client/dist/assets/SetupTab-nOknoph5.js +2 -0
  97. package/extensions/dashboard-client/dist/assets/SetupTab-nOknoph5.js.map +1 -0
  98. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CewYB2aA.js → TimeSavedCard-DnPg0L0z.js} +2 -2
  99. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CewYB2aA.js.map → TimeSavedCard-DnPg0L0z.js.map} +1 -1
  100. package/extensions/dashboard-client/dist/assets/{TopicsTab-DpVNIuaL.js → TopicsTab-BODHOg_b.js} +2 -2
  101. package/extensions/dashboard-client/dist/assets/{TopicsTab-DpVNIuaL.js.map → TopicsTab-BODHOg_b.js.map} +1 -1
  102. package/extensions/dashboard-client/dist/assets/{TurnsTab-KeuKKVC9.js → TurnsTab-BK0I7hKr.js} +2 -2
  103. package/extensions/dashboard-client/dist/assets/{TurnsTab-KeuKKVC9.js.map → TurnsTab-BK0I7hKr.js.map} +1 -1
  104. package/extensions/dashboard-client/dist/assets/{index-Cucgohga.js → index-mOlljTFI.js} +9 -9
  105. package/extensions/dashboard-client/dist/assets/index-mOlljTFI.js.map +1 -0
  106. package/extensions/dashboard-client/dist/assets/{useSSE-DJsqdPXC.js → useSSE-BYOtv1-3.js} +2 -2
  107. package/extensions/dashboard-client/dist/assets/{useSSE-DJsqdPXC.js.map → useSSE-BYOtv1-3.js.map} +1 -1
  108. package/extensions/dashboard-client/dist/index.html +1 -1
  109. package/extensions/dashboard-client/src/api/client.ts +16 -0
  110. package/extensions/dashboard-client/src/tabs/MaintenanceTab/ActionsCard.tsx +178 -0
  111. package/extensions/dashboard-client/src/tabs/MaintenanceTab/DbStatsCard.tsx +140 -0
  112. package/extensions/dashboard-client/src/tabs/MaintenanceTab/DebugBundleCard.tsx +109 -0
  113. package/extensions/dashboard-client/src/tabs/MaintenanceTab/HealthMitigationCard.tsx +98 -0
  114. package/extensions/dashboard-client/src/tabs/MaintenanceTab/SchemaHealthCard.tsx +108 -0
  115. package/extensions/dashboard-client/src/tabs/MaintenanceTab.tsx +12 -598
  116. package/extensions/dashboard-client/src/tabs/MemoryMapTab/MemoryMapView.tsx +476 -0
  117. package/extensions/dashboard-client/src/tabs/MemoryMapTab/RaptorTreeView.tsx +163 -0
  118. package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +56 -466
  119. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderHealthCard.tsx +109 -0
  120. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderSetup.tsx +468 -0
  121. package/extensions/dashboard-client/src/tabs/SetupTab.tsx +13 -483
  122. package/extensions/dashboard-server/api-contracts/embedder-health.ts +32 -0
  123. package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +360 -0
  124. package/extensions/dashboard-server/api-contracts/endpoints/types.ts +350 -0
  125. package/extensions/dashboard-server/api-contracts/endpoints.ts +27 -648
  126. package/extensions/dashboard-server/api-contracts/index.ts +6 -0
  127. package/extensions/dashboard-server/api-contracts/raptor.ts +49 -0
  128. package/extensions/dashboard-server/api-contracts.test/_helpers.ts +47 -0
  129. package/extensions/dashboard-server/routes-cache.test/_helpers.ts +94 -0
  130. package/extensions/dashboard-server/routes-embedder-health.ts +77 -0
  131. package/extensions/dashboard-server/routes-raptor.ts +128 -0
  132. package/extensions/dashboard-server/routes-setup.ts +1 -1
  133. package/extensions/dashboard-server/routes.ts +2 -0
  134. package/extensions/dashboard-server/server.ts +4 -0
  135. package/extensions/mega-cache-replay.test/_helpers.ts +188 -0
  136. package/extensions/mega-commands/dataCommands.ts +169 -0
  137. package/extensions/mega-commands/helpers.ts +62 -0
  138. package/extensions/mega-commands/historyCommands.ts +117 -0
  139. package/extensions/mega-commands/setupCommand.ts +235 -0
  140. package/extensions/mega-commands.ts +25 -538
  141. package/extensions/mega-compact-s38.test/_helpers.ts +201 -0
  142. package/extensions/mega-compact.test/_helpers.ts +237 -0
  143. package/extensions/mega-events/agent-handlers/agentEndHandler.ts +238 -0
  144. package/extensions/mega-events/agent-handlers/turnEndHandler/cacheStripe.ts +74 -0
  145. package/extensions/mega-events/agent-handlers/turnEndHandler/contextHealth.ts +50 -0
  146. package/extensions/mega-events/agent-handlers/turnEndHandler/errorRetry.ts +421 -0
  147. package/extensions/mega-events/agent-handlers/turnEndHandler/event.ts +16 -0
  148. package/extensions/mega-events/agent-handlers/turnEndHandler/gameScoring.ts +73 -0
  149. package/extensions/mega-events/agent-handlers/turnEndHandler/lengthStop.ts +29 -0
  150. package/extensions/mega-events/agent-handlers/turnEndHandler/memoryReview.ts +42 -0
  151. package/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.ts +61 -0
  152. package/extensions/mega-events/agent-handlers/turnEndHandler.ts +64 -0
  153. package/extensions/mega-events/agent-handlers.ts +15 -861
  154. package/extensions/mega-events/context-handler/afterCompact.ts +213 -0
  155. package/extensions/mega-events/context-handler/messageText.ts +20 -0
  156. package/extensions/mega-events/context-handler/tailResult.ts +63 -0
  157. package/extensions/mega-events/context-handler.ts +17 -235
  158. package/extensions/mega-events/separated-prompt.test/_helpers.ts +35 -0
  159. package/package.json +1 -1
  160. package/src/config.ts +4 -0
  161. package/src/dedup-engine.test/_helpers.ts +79 -0
  162. package/src/e2e.test/_helpers.ts +59 -0
  163. package/src/embedder.ts +4 -0
  164. package/src/httpEmbedder.ts +7 -0
  165. package/src/hyde.ts +162 -0
  166. package/src/importance.test/_helpers.ts +5 -0
  167. package/src/memoryGraph/sources/checkpoints.ts +99 -0
  168. package/src/memoryGraph/sources/edges.ts +23 -0
  169. package/src/memoryGraph/sources/flags.ts +26 -0
  170. package/src/memoryGraph/sources/helpers.ts +13 -0
  171. package/src/memoryGraph/sources/memories.ts +108 -0
  172. package/src/memoryGraph/sources/raptor.ts +37 -0
  173. package/src/memoryGraph/sources/turns.ts +226 -0
  174. package/src/memoryGraph/sources.ts +19 -544
  175. package/src/memoryGraph-gates.test/_helpers.ts +164 -0
  176. package/src/ratio.bench.test/_helpers.ts +268 -0
  177. package/src/recall/async.ts +142 -0
  178. package/src/recall/format.ts +173 -0
  179. package/src/recall/memory.ts +90 -0
  180. package/src/recall/reformulate.ts +111 -0
  181. package/src/recall/sync.ts +196 -0
  182. package/src/recall/types.ts +63 -0
  183. package/src/recall.ts +25 -712
  184. package/src/store/sqlite/schema/core.ts +233 -0
  185. package/src/store/sqlite/schema/game.ts +66 -0
  186. package/src/store/sqlite/schema/plan-v2.ts +108 -0
  187. package/src/store/sqlite/schema/turns.ts +60 -0
  188. package/src/store/sqlite/schema.ts +13 -441
  189. package/src/store/turns/contract-compliance.test/_helpers.ts +38 -0
  190. package/src/store/turns/sqlite-store/admin.ts +249 -0
  191. package/src/store/turns/sqlite-store/ctx.ts +14 -0
  192. package/src/store/turns/sqlite-store/read.ts +174 -0
  193. package/src/store/turns/sqlite-store/rows.ts +55 -0
  194. package/src/store/turns/sqlite-store/write.ts +193 -0
  195. package/src/store/turns/sqlite-store.ts +33 -514
  196. package/src/vectorStore/class.ts +433 -0
  197. package/src/vectorStore/hash.ts +11 -0
  198. package/src/vectorStore/types.ts +58 -0
  199. package/src/vectorStore.test/_helpers.ts +20 -0
  200. package/src/vectorStore.ts +13 -489
  201. package/extensions/dashboard-client/dist/assets/MaintenanceTab-DZi1MnZI.js +0 -2
  202. package/extensions/dashboard-client/dist/assets/MaintenanceTab-DZi1MnZI.js.map +0 -1
  203. package/extensions/dashboard-client/dist/assets/MemoryMapTab-CjUa9zzw.js +0 -2
  204. package/extensions/dashboard-client/dist/assets/MemoryMapTab-CjUa9zzw.js.map +0 -1
  205. package/extensions/dashboard-client/dist/assets/SetupTab-C_BwmebC.js +0 -2
  206. package/extensions/dashboard-client/dist/assets/SetupTab-C_BwmebC.js.map +0 -1
  207. package/extensions/dashboard-client/dist/assets/index-Cucgohga.js.map +0 -1
@@ -0,0 +1,249 @@
1
+ /**
2
+ * admin.ts — TurnAdmin method bodies (extracted from sqlite-store.ts). All
3
+ * queries use bound parameters (PREVENT-002). DELETE/UPDATE only here (the
4
+ * append-only rule applies to turns/turn_recall writes outside migrations).
5
+ */
6
+ import { statSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import type {
9
+ SessionId,
10
+ PruneReport,
11
+ RetentionPolicy,
12
+ StoreSnapshot,
13
+ TurnRecallEntry,
14
+ } from "../types.js";
15
+ import type { SqliteTurnStoreCtx } from "./ctx.js";
16
+ import { normalizeSessionId, rowToEntry, rowToFork } from "./rows.js";
17
+
18
+ export function prune(ctx: SqliteTurnStoreCtx, policy: RetentionPolicy): PruneReport {
19
+ const { db } = ctx;
20
+ const beforeSize = dbSizeBytes(ctx);
21
+ const cutoff = Date.now() - policy.maxTurnAgeMs;
22
+
23
+ // Find conversations with turns to potentially prune
24
+ const convRows = db
25
+ .prepare("SELECT DISTINCT conversation_id FROM turns")
26
+ .all() as Array<{ conversation_id: string }>;
27
+
28
+ let turnsRemoved = 0;
29
+ let recallRemoved = 0;
30
+
31
+ for (const { conversation_id } of convRows) {
32
+ // Count total turns in this conversation
33
+ const total = db
34
+ .prepare("SELECT COUNT(*) as cnt FROM turns WHERE conversation_id = ?")
35
+ .get(conversation_id) as { cnt: number };
36
+
37
+ // Keep at least keepMinPerConversation — delete only the oldest
38
+ const canDelete = Math.max(0, total.cnt - policy.keepMinPerConversation);
39
+ if (canDelete === 0) continue;
40
+
41
+ // Find old turns to delete
42
+ const oldTurns = db
43
+ .prepare(
44
+ `SELECT id FROM turns WHERE conversation_id = ? AND ended_at < ?
45
+ ORDER BY ended_at ASC LIMIT ?`,
46
+ )
47
+ .all(conversation_id, cutoff, canDelete) as Array<{
48
+ id: number;
49
+ }>;
50
+
51
+ if (oldTurns.length === 0) continue;
52
+
53
+ const ids = oldTurns.map((t) => t.id);
54
+ const placeholders = ids.map(() => "?").join(",");
55
+
56
+ // Delete recall for those turns
57
+ const delRecall = db
58
+ .prepare(`DELETE FROM turn_recall WHERE turn_id IN (${placeholders})`)
59
+ .run(...ids);
60
+ recallRemoved += Number(delRecall.changes);
61
+
62
+ // Delete the turns
63
+ const delTurns = db
64
+ .prepare(`DELETE FROM turns WHERE id IN (${placeholders})`)
65
+ .run(...ids);
66
+ turnsRemoved += Number(delTurns.changes);
67
+ }
68
+
69
+ // Count preserved branches
70
+ const branchesPreserved = (
71
+ db
72
+ .prepare("SELECT COUNT(*) as cnt FROM conversation_forks")
73
+ .get() as { cnt: number }
74
+ ).cnt;
75
+
76
+ if (policy.vacuumAfterPrune && (turnsRemoved > 0 || recallRemoved > 0)) {
77
+ vacuum(ctx);
78
+ }
79
+
80
+ const afterSize = dbSizeBytes(ctx);
81
+
82
+ return {
83
+ turnsRemoved,
84
+ recallRemoved,
85
+ branchesPreserved,
86
+ freedBytes: Math.max(0, beforeSize - afterSize),
87
+ };
88
+ }
89
+
90
+ export function vacuum(ctx: SqliteTurnStoreCtx): void {
91
+ ctx.db.exec("VACUUM");
92
+ }
93
+
94
+ export function checkpoint(ctx: SqliteTurnStoreCtx): StoreSnapshot {
95
+ const { db } = ctx;
96
+ // Query turns in row-id order (ended_at ASC with unique constraint)
97
+ const turnRows = db
98
+ .prepare("SELECT * FROM turns ORDER BY ended_at ASC")
99
+ .all() as Array<Record<string, unknown>>;
100
+ const turns = turnRows.map(rowToEntry);
101
+
102
+ // Build mapping: row_id → 1-based position in turns array
103
+ const rowIdToPos = new Map<number, number>();
104
+ for (let i = 0; i < turnRows.length; i++) {
105
+ rowIdToPos.set(turnRows[i].id as number, i + 1);
106
+ }
107
+
108
+ const recallRows = db
109
+ .prepare("SELECT * FROM turn_recall ORDER BY turn_id ASC")
110
+ .all() as Array<Record<string, unknown>>;
111
+
112
+ // Replace recall's turnId with the 1-based position of the referenced turn
113
+ // in the turns array. This makes the TurnId stable across restore.
114
+ const recall = recallRows.map((r) => {
115
+ const originalTurnId = r.turn_id as number;
116
+ const stableTurnId = rowIdToPos.get(originalTurnId) ?? originalTurnId;
117
+ return {
118
+ turnId: String(stableTurnId),
119
+ checkpointId: r.checkpoint_id as string,
120
+ score: r.score as number,
121
+ source: r.source as TurnRecallEntry["source"],
122
+ raptorLevel: (r.raptor_level as number | null) ?? undefined,
123
+ };
124
+ });
125
+
126
+ const forks = (
127
+ db
128
+ .prepare("SELECT * FROM conversation_forks ORDER BY created_at ASC")
129
+ .all() as Array<Record<string, unknown>>
130
+ ).map(rowToFork);
131
+
132
+ return {
133
+ version: 1,
134
+ exportedAt: Date.now(),
135
+ turns,
136
+ recall,
137
+ forks,
138
+ };
139
+ }
140
+
141
+ export function restore(ctx: SqliteTurnStoreCtx, from: StoreSnapshot): void {
142
+ const { db } = ctx;
143
+ clear(ctx);
144
+ const insertTurn = db.prepare(
145
+ `INSERT INTO turns (conversation_id, session_id, turn_index, role, ended_at,
146
+ ctx_tokens, ctx_percent, pressure_band, model, epoch_id)
147
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
148
+ );
149
+ const insertRecall = db.prepare(
150
+ `INSERT INTO turn_recall (turn_id, checkpoint_id, score, source, raptor_level)
151
+ VALUES (?, ?, ?, ?, ?)`,
152
+ );
153
+ const insertFork = db.prepare(
154
+ `INSERT INTO conversation_forks (parent_conversation_id, child_conversation_id, fork_turn_index, created_at)
155
+ VALUES (?, ?, ?, ?)`,
156
+ );
157
+
158
+ db.exec("BEGIN TRANSACTION");
159
+ try {
160
+ // Map: 1-based position → new auto-increment row ID
161
+ const posToNewId = new Map<number, number>();
162
+
163
+ for (let i = 0; i < from.turns.length; i++) {
164
+ const t = from.turns[i];
165
+ insertTurn.run(
166
+ t.conversationId,
167
+ normalizeSessionId(t.sessionId),
168
+ t.turnIndex,
169
+ t.role,
170
+ t.endedAt,
171
+ t.ctxTokens ?? null,
172
+ t.ctxPercent ?? null,
173
+ t.pressureBand ?? null,
174
+ t.model ?? null,
175
+ t.epochId ?? null,
176
+ );
177
+ const row = db
178
+ .prepare(
179
+ "SELECT id FROM turns WHERE conversation_id = ? AND turn_index = ?",
180
+ )
181
+ .get(t.conversationId, t.turnIndex) as { id: number } | undefined;
182
+ if (row) posToNewId.set(i + 1, row.id);
183
+ }
184
+
185
+ // Recall's turnId is now the 1-based position from checkpoint().
186
+ // Map it to the new row ID.
187
+ for (const r of from.recall) {
188
+ const targetTurnId =
189
+ posToNewId.get(Number(r.turnId)) ?? Number(r.turnId);
190
+ insertRecall.run(
191
+ targetTurnId,
192
+ r.checkpointId,
193
+ r.score,
194
+ r.source,
195
+ r.raptorLevel ?? null,
196
+ );
197
+ }
198
+
199
+ for (const f of from.forks) {
200
+ insertFork.run(
201
+ f.parentConversationId,
202
+ f.childConversationId,
203
+ f.forkTurnIndex,
204
+ f.createdAt,
205
+ );
206
+ }
207
+ db.exec("COMMIT");
208
+ } catch (err) {
209
+ db.exec("ROLLBACK");
210
+ throw err;
211
+ }
212
+ }
213
+
214
+ export function clear(ctx: SqliteTurnStoreCtx): void {
215
+ const { db } = ctx;
216
+ db.exec("DELETE FROM turn_recall");
217
+ db.exec("DELETE FROM conversation_forks");
218
+ db.exec("DELETE FROM turns");
219
+ db.exec("DELETE FROM session_conversations");
220
+ // Reset autoincrement
221
+ db.exec(
222
+ "DELETE FROM sqlite_sequence WHERE name IN ('turns','turn_recall','conversation_forks')",
223
+ );
224
+ }
225
+
226
+ export function stampTurnsEpoch(
227
+ ctx: SqliteTurnStoreCtx,
228
+ sessionId: SessionId,
229
+ epochId: string,
230
+ ): number {
231
+ const { db } = ctx;
232
+ const sid = normalizeSessionId(sessionId);
233
+ const res = db
234
+ .prepare(
235
+ "UPDATE turns SET epoch_id = ? WHERE session_id = ? AND epoch_id IS NULL",
236
+ )
237
+ .run(epochId, sid);
238
+ return Number(res.changes ?? 0);
239
+ }
240
+
241
+ function dbSizeBytes(ctx: SqliteTurnStoreCtx): number {
242
+ if (ctx.opts.inMemory) return 0;
243
+ try {
244
+ const path = ctx.opts.dbPath ?? join(ctx.stateDir, "turns.db");
245
+ return statSync(path).size;
246
+ } catch {
247
+ return 0;
248
+ }
249
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * ctx.ts — internal context interface for SqliteTurnStore method bodies
3
+ * (extracted from sqlite-store.ts). Each free function in this directory
4
+ * operates on a SqliteTurnStoreCtx so the shell class can delegate without
5
+ * exposing its private fields.
6
+ */
7
+ import type { DatabaseSync } from "node:sqlite";
8
+ import type { TurnStoreOptions } from "../types.js";
9
+
10
+ export interface SqliteTurnStoreCtx {
11
+ db: DatabaseSync;
12
+ stateDir: string;
13
+ opts: TurnStoreOptions;
14
+ }
@@ -0,0 +1,174 @@
1
+ /**
2
+ * read.ts — TurnReader method bodies (extracted from sqlite-store.ts). All
3
+ * queries use bound parameters (PREVENT-002). Append-only reads.
4
+ */
5
+ import type {
6
+ ConversationId,
7
+ ConversationStats,
8
+ TurnEntry,
9
+ TurnFilter,
10
+ TurnId,
11
+ TurnRecallEntry,
12
+ ConversationFork,
13
+ } from "../types.js";
14
+ import type { SqliteTurnStoreCtx } from "./ctx.js";
15
+ import { normalizeSessionId, rowToEntry, rowToRecall, rowToFork } from "./rows.js";
16
+
17
+ export function query(ctx: SqliteTurnStoreCtx, filter: TurnFilter): TurnEntry[] {
18
+ const { db } = ctx;
19
+ const clauses: string[] = [];
20
+ const params: (string | number)[] = [];
21
+
22
+ if (filter.conversationId !== undefined) {
23
+ clauses.push("conversation_id = ?");
24
+ params.push(filter.conversationId);
25
+ }
26
+ if (filter.sessionId !== undefined) {
27
+ clauses.push("session_id = ?");
28
+ params.push(normalizeSessionId(filter.sessionId));
29
+ }
30
+ if (filter.sinceMs !== undefined) {
31
+ clauses.push("ended_at >= ?");
32
+ params.push(filter.sinceMs);
33
+ }
34
+ if (filter.untilMs !== undefined) {
35
+ clauses.push("ended_at <= ?");
36
+ params.push(filter.untilMs);
37
+ }
38
+ if (filter.pressureBand !== undefined) {
39
+ clauses.push("pressure_band = ?");
40
+ params.push(filter.pressureBand);
41
+ }
42
+
43
+ const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
44
+ const limit = filter.limit ?? 1000;
45
+ const offset = filter.offset ?? 0;
46
+
47
+ const rows = db
48
+ .prepare(
49
+ `SELECT * FROM turns ${where} ORDER BY ended_at ASC LIMIT ? OFFSET ?`,
50
+ )
51
+ .all(...params, limit, offset) as Array<Record<string, unknown>>;
52
+
53
+ return rows.map(rowToEntry);
54
+ }
55
+
56
+ export function getTurn(
57
+ ctx: SqliteTurnStoreCtx,
58
+ turnId: TurnId,
59
+ ): TurnEntry | undefined {
60
+ const { db } = ctx;
61
+ const row = db
62
+ .prepare("SELECT * FROM turns WHERE id = ?")
63
+ .get(Number(turnId)) as Record<string, unknown> | undefined;
64
+ return row ? rowToEntry(row) : undefined;
65
+ }
66
+
67
+ export function getTurnByIndex(
68
+ ctx: SqliteTurnStoreCtx,
69
+ conversationId: ConversationId,
70
+ turnIndex: number,
71
+ ): TurnEntry | undefined {
72
+ const { db } = ctx;
73
+ const row = db
74
+ .prepare(
75
+ "SELECT * FROM turns WHERE conversation_id = ? AND turn_index = ?",
76
+ )
77
+ .get(conversationId, turnIndex) as Record<string, unknown> | undefined;
78
+ return row ? rowToEntry(row) : undefined;
79
+ }
80
+
81
+ export function listRecall(
82
+ ctx: SqliteTurnStoreCtx,
83
+ turnId: TurnId,
84
+ ): TurnRecallEntry[] {
85
+ const { db } = ctx;
86
+ const rows = db
87
+ .prepare(
88
+ "SELECT * FROM turn_recall WHERE turn_id = ? ORDER BY score DESC",
89
+ )
90
+ .all(Number(turnId)) as Array<Record<string, unknown>>;
91
+ return rows.map(rowToRecall);
92
+ }
93
+
94
+ export function listRecallByIndex(
95
+ ctx: SqliteTurnStoreCtx,
96
+ conversationId: ConversationId,
97
+ turnIndex: number,
98
+ ): TurnRecallEntry[] {
99
+ const { db } = ctx;
100
+ const row = db
101
+ .prepare(
102
+ "SELECT id FROM turns WHERE conversation_id = ? AND turn_index = ?",
103
+ )
104
+ .get(conversationId, turnIndex) as { id: number } | undefined;
105
+ if (!row) return [];
106
+ return listRecall(ctx, String(row.id));
107
+ }
108
+
109
+ export function listForks(
110
+ ctx: SqliteTurnStoreCtx,
111
+ conversationId: ConversationId,
112
+ ): ConversationFork[] {
113
+ const { db } = ctx;
114
+ const rows = db
115
+ .prepare(
116
+ "SELECT * FROM conversation_forks WHERE parent_conversation_id = ? ORDER BY created_at ASC",
117
+ )
118
+ .all(conversationId) as Array<Record<string, unknown>>;
119
+ return rows.map(rowToFork);
120
+ }
121
+
122
+ export function countTurns(
123
+ ctx: SqliteTurnStoreCtx,
124
+ conversationId: ConversationId,
125
+ ): number {
126
+ const { db } = ctx;
127
+ const row = db
128
+ .prepare("SELECT COUNT(*) as cnt FROM turns WHERE conversation_id = ?")
129
+ .get(conversationId) as { cnt: number };
130
+ return row.cnt;
131
+ }
132
+
133
+ export function conversationStats(
134
+ ctx: SqliteTurnStoreCtx,
135
+ conversationId: ConversationId,
136
+ ): ConversationStats {
137
+ const { db } = ctx;
138
+ const rows = db
139
+ .prepare(
140
+ "SELECT ended_at, ctx_percent, pressure_band FROM turns WHERE conversation_id = ? ORDER BY ended_at ASC",
141
+ )
142
+ .all(conversationId) as Array<Record<string, unknown>>;
143
+
144
+ if (rows.length === 0) {
145
+ return {
146
+ turnCount: 0,
147
+ firstTurnAt: 0,
148
+ lastTurnAt: 0,
149
+ avgCtxPercent: 0,
150
+ pressureBands: {},
151
+ };
152
+ }
153
+
154
+ let ctxSum = 0;
155
+ let ctxCount = 0;
156
+ const bands: Record<string, number> = {};
157
+
158
+ for (const r of rows) {
159
+ if (r.ctx_percent !== null) {
160
+ ctxSum += r.ctx_percent as number;
161
+ ctxCount++;
162
+ }
163
+ const band = r.pressure_band as string | null;
164
+ if (band) bands[band] = (bands[band] ?? 0) + 1;
165
+ }
166
+
167
+ return {
168
+ turnCount: rows.length,
169
+ firstTurnAt: rows[0].ended_at as number,
170
+ lastTurnAt: rows[rows.length - 1].ended_at as number,
171
+ avgCtxPercent: ctxCount > 0 ? ctxSum / ctxCount : 0,
172
+ pressureBands: bands,
173
+ };
174
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * rows.ts — row ↔ entry mappers + id helpers (extracted from sqlite-store.ts).
3
+ */
4
+ import { randomBytes } from "node:crypto";
5
+ import type {
6
+ ConversationId,
7
+ SessionId,
8
+ TurnEntry,
9
+ TurnRecallEntry,
10
+ ConversationFork,
11
+ } from "../types.js";
12
+
13
+ export function newConversationId(): ConversationId {
14
+ return `conv_${randomBytes(8).toString("hex")}`;
15
+ }
16
+
17
+ export function normalizeSessionId(sid: SessionId): SessionId {
18
+ // Matches the existing normalizeSessionId in src/store.ts
19
+ return sid.replace(/-.*$/, "");
20
+ }
21
+
22
+ export function rowToEntry(r: Record<string, unknown>): TurnEntry {
23
+ return {
24
+ conversationId: r.conversation_id as string,
25
+ sessionId: r.session_id as string,
26
+ turnIndex: r.turn_index as number,
27
+ role: r.role as TurnEntry["role"],
28
+ endedAt: r.ended_at as number,
29
+ ctxTokens: (r.ctx_tokens as number | null) ?? undefined,
30
+ ctxPercent: (r.ctx_percent as number | null) ?? undefined,
31
+ pressureBand:
32
+ (r.pressure_band as "green" | "yellow" | "red" | null) ?? undefined,
33
+ model: (r.model as string | null) ?? undefined,
34
+ epochId: (r.epoch_id as string | null) ?? undefined,
35
+ };
36
+ }
37
+
38
+ export function rowToRecall(r: Record<string, unknown>): TurnRecallEntry {
39
+ return {
40
+ turnId: String(r.turn_id),
41
+ checkpointId: r.checkpoint_id as string,
42
+ score: r.score as number,
43
+ source: r.source as TurnRecallEntry["source"],
44
+ raptorLevel: (r.raptor_level as number | null) ?? undefined,
45
+ };
46
+ }
47
+
48
+ export function rowToFork(r: Record<string, unknown>): ConversationFork {
49
+ return {
50
+ parentConversationId: r.parent_conversation_id as string,
51
+ childConversationId: r.child_conversation_id as string,
52
+ forkTurnIndex: r.fork_turn_index as number,
53
+ createdAt: r.created_at as number,
54
+ };
55
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * write.ts — TurnWriter method bodies (extracted from sqlite-store.ts). All
3
+ * queries use bound parameters (PREVENT-002). Append-only: no UPDATE on
4
+ * turns/turn_recall after INSERT (only in migration).
5
+ */
6
+ import type {
7
+ ConversationId,
8
+ SessionId,
9
+ TurnEntry,
10
+ TurnId,
11
+ TurnRecallEntry,
12
+ } from "../types.js";
13
+ import { DuplicateTurnError } from "../types.js";
14
+ import type { SqliteTurnStoreCtx } from "./ctx.js";
15
+ import { newConversationId, normalizeSessionId } from "./rows.js";
16
+
17
+ export function appendTurn(ctx: SqliteTurnStoreCtx, entry: TurnEntry): TurnId {
18
+ const { db } = ctx;
19
+ const sid = normalizeSessionId(entry.sessionId);
20
+ persistSessionConv(ctx, sid, entry.conversationId);
21
+ try {
22
+ db
23
+ .prepare(
24
+ `INSERT INTO turns (conversation_id, session_id, turn_index, role, ended_at,
25
+ ctx_tokens, ctx_percent, pressure_band, model, epoch_id)
26
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
27
+ )
28
+ .run(
29
+ entry.conversationId,
30
+ sid,
31
+ entry.turnIndex,
32
+ entry.role,
33
+ entry.endedAt,
34
+ entry.ctxTokens ?? null,
35
+ entry.ctxPercent ?? null,
36
+ entry.pressureBand ?? null,
37
+ entry.model ?? null,
38
+ entry.epochId ?? null,
39
+ );
40
+ } catch (e) {
41
+ if (
42
+ e instanceof Error &&
43
+ e.message.includes("UNIQUE constraint") &&
44
+ (e as { code?: string }).code === "ERR_SQLITE_ERROR"
45
+ ) {
46
+ throw new DuplicateTurnError(entry.conversationId, entry.turnIndex);
47
+ }
48
+ throw e;
49
+ }
50
+ const row = db
51
+ .prepare(
52
+ "SELECT id FROM turns WHERE conversation_id = ? AND turn_index = ?",
53
+ )
54
+ .get(entry.conversationId, entry.turnIndex) as { id: number } | undefined;
55
+ return String(row!.id);
56
+ }
57
+
58
+ /** Persist the session → conversation mapping when a turn is written. */
59
+ function persistSessionConv(
60
+ ctx: SqliteTurnStoreCtx,
61
+ sid: string,
62
+ conversationId: string,
63
+ ): void {
64
+ ctx.db
65
+ .prepare(
66
+ "INSERT OR IGNORE INTO session_conversations (session_id, conversation_id) VALUES (?, ?)",
67
+ )
68
+ .run(sid, conversationId);
69
+ }
70
+
71
+ export function appendRecall(
72
+ ctx: SqliteTurnStoreCtx,
73
+ entry: TurnRecallEntry,
74
+ ): void {
75
+ const { db } = ctx;
76
+ db
77
+ .prepare(
78
+ `INSERT OR IGNORE INTO turn_recall (turn_id, checkpoint_id, score, source, raptor_level)
79
+ VALUES (?, ?, ?, ?, ?)`,
80
+ )
81
+ .run(
82
+ Number(entry.turnId),
83
+ entry.checkpointId,
84
+ entry.score,
85
+ entry.source,
86
+ entry.raptorLevel ?? null,
87
+ );
88
+ }
89
+
90
+ export function ensureConversationId(
91
+ ctx: SqliteTurnStoreCtx,
92
+ sessionId: SessionId,
93
+ ): ConversationId {
94
+ const { db } = ctx;
95
+ const sid = normalizeSessionId(sessionId);
96
+ // Check session_conversations table first (persistent mapping)
97
+ const row = db
98
+ .prepare(
99
+ "SELECT conversation_id FROM session_conversations WHERE session_id = ?",
100
+ )
101
+ .get(sid) as { conversation_id: string } | undefined;
102
+ if (row) return row.conversation_id;
103
+
104
+ // Check if any turn exists for this session (fallback)
105
+ const turnRow = db
106
+ .prepare(
107
+ "SELECT conversation_id FROM turns WHERE session_id = ? ORDER BY ended_at DESC LIMIT 1",
108
+ )
109
+ .get(sid) as { conversation_id: string } | undefined;
110
+ if (turnRow) {
111
+ // Persist the mapping so future calls are consistent
112
+ db
113
+ .prepare(
114
+ "INSERT OR IGNORE INTO session_conversations (session_id, conversation_id) VALUES (?, ?)",
115
+ )
116
+ .run(sid, turnRow.conversation_id);
117
+ return turnRow.conversation_id;
118
+ }
119
+
120
+ // No turns yet — generate and persist a new conversation id
121
+ const convId = newConversationId();
122
+ db
123
+ .prepare(
124
+ "INSERT OR IGNORE INTO session_conversations (session_id, conversation_id) VALUES (?, ?)",
125
+ )
126
+ .run(sid, convId);
127
+ return convId;
128
+ }
129
+
130
+ export function forkConversation(
131
+ ctx: SqliteTurnStoreCtx,
132
+ parentId: ConversationId,
133
+ forkTurnIndex: number,
134
+ ): ConversationId {
135
+ const { db } = ctx;
136
+ const childId = newConversationId();
137
+ const now = Date.now();
138
+
139
+ // 1. Record the fork lineage
140
+ db
141
+ .prepare(
142
+ `INSERT INTO conversation_forks (parent_conversation_id, child_conversation_id, fork_turn_index, created_at)
143
+ VALUES (?, ?, ?, ?)`,
144
+ )
145
+ .run(parentId, childId, forkTurnIndex, now);
146
+
147
+ // 2. Find the parent's turn at forkTurnIndex
148
+ const parentTurn = db
149
+ .prepare(
150
+ "SELECT id FROM turns WHERE conversation_id = ? AND turn_index = ?",
151
+ )
152
+ .get(parentId, forkTurnIndex) as { id: number } | undefined;
153
+
154
+ if (!parentTurn) return childId; // no parent turn at that index — nothing to seed
155
+
156
+ // 3. Create a seed turn in the child conversation (turnIndex 0, role system)
157
+ db
158
+ .prepare(
159
+ `INSERT INTO turns (conversation_id, session_id, turn_index, role, ended_at)
160
+ VALUES (?, ?, 0, 'system', ?)`,
161
+ )
162
+ .run(childId, `fork_${childId}`, now);
163
+
164
+ const childTurnRow = db
165
+ .prepare(
166
+ "SELECT id FROM turns WHERE conversation_id = ? AND turn_index = 0",
167
+ )
168
+ .get(childId) as { id: number } | undefined;
169
+ if (!childTurnRow) return childId;
170
+
171
+ // 4. Copy the parent's recall entries into the child's seed turn
172
+ const parentRecall = db
173
+ .prepare(
174
+ "SELECT checkpoint_id, score, source, raptor_level FROM turn_recall WHERE turn_id = ?",
175
+ )
176
+ .all(parentTurn.id) as Array<Record<string, unknown>>;
177
+
178
+ const insertRecall = db.prepare(
179
+ `INSERT OR IGNORE INTO turn_recall (turn_id, checkpoint_id, score, source, raptor_level)
180
+ VALUES (?, ?, ?, ?, ?)`,
181
+ );
182
+ for (const r of parentRecall) {
183
+ insertRecall.run(
184
+ childTurnRow.id,
185
+ r.checkpoint_id as string,
186
+ r.score as number,
187
+ r.source as string,
188
+ (r.raptor_level as number | null) ?? null,
189
+ );
190
+ }
191
+
192
+ return childId;
193
+ }