pi-mega-compact 0.13.0 → 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 (212) 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.js +12 -2
  21. package/dist/extensions/mega-compact.test/_helpers.js +225 -0
  22. package/dist/extensions/mega-events/agent-handlers/agentEndHandler.js +211 -0
  23. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/cacheStripe.js +49 -0
  24. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/contextHealth.js +33 -0
  25. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/errorRetry.js +387 -0
  26. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/event.js +7 -0
  27. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/gameScoring.js +55 -0
  28. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/lengthStop.js +12 -0
  29. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/memoryReview.js +22 -0
  30. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.js +38 -0
  31. package/dist/extensions/mega-events/agent-handlers/turnEndHandler.js +36 -0
  32. package/dist/extensions/mega-events/agent-handlers.js +4 -799
  33. package/dist/extensions/mega-events/context-handler/afterCompact.js +163 -0
  34. package/dist/extensions/mega-events/context-handler/messageText.js +12 -0
  35. package/dist/extensions/mega-events/context-handler/tailResult.js +46 -0
  36. package/dist/extensions/mega-events/context-handler.js +11 -198
  37. package/dist/extensions/mega-events/separated-prompt.test/_helpers.js +25 -0
  38. package/dist/src/config.js +2 -0
  39. package/dist/src/dedup-engine.test/_helpers.js +55 -0
  40. package/dist/src/e2e.test/_helpers.js +52 -0
  41. package/dist/src/embedder.js +1 -0
  42. package/dist/src/httpEmbedder.js +6 -0
  43. package/dist/src/hyde.js +146 -0
  44. package/dist/src/importance.test/_helpers.js +5 -0
  45. package/dist/src/memoryGraph/sources/checkpoints.js +78 -0
  46. package/dist/src/memoryGraph/sources/edges.js +17 -0
  47. package/dist/src/memoryGraph/sources/flags.js +22 -0
  48. package/dist/src/memoryGraph/sources/helpers.js +14 -0
  49. package/dist/src/memoryGraph/sources/memories.js +88 -0
  50. package/dist/src/memoryGraph/sources/raptor.js +26 -0
  51. package/dist/src/memoryGraph/sources/turns.js +176 -0
  52. package/dist/src/memoryGraph/sources.js +15 -441
  53. package/dist/src/memoryGraph-gates.test/_helpers.js +120 -0
  54. package/dist/src/ratio.bench.test/_helpers.js +198 -0
  55. package/dist/src/recall/async.js +112 -0
  56. package/dist/src/recall/format.js +149 -0
  57. package/dist/src/recall/memory.js +62 -0
  58. package/dist/src/recall/reformulate.js +82 -0
  59. package/dist/src/recall/sync.js +169 -0
  60. package/dist/src/recall/types.js +1 -0
  61. package/dist/src/recall.js +13 -530
  62. package/dist/src/store/sqlite/schema/core.js +232 -0
  63. package/dist/src/store/sqlite/schema/game.js +65 -0
  64. package/dist/src/store/sqlite/schema/plan-v2.js +107 -0
  65. package/dist/src/store/sqlite/schema/turns.js +59 -0
  66. package/dist/src/store/sqlite/schema.js +8 -441
  67. package/dist/src/store/turns/contract-compliance.test/_helpers.js +21 -0
  68. package/dist/src/store/turns/sqlite-store/admin.js +171 -0
  69. package/dist/src/store/turns/sqlite-store/ctx.js +1 -0
  70. package/dist/src/store/turns/sqlite-store/read.js +111 -0
  71. package/dist/src/store/turns/sqlite-store/rows.js +42 -0
  72. package/dist/src/store/turns/sqlite-store/write.js +102 -0
  73. package/dist/src/store/turns/sqlite-store.js +31 -367
  74. package/dist/src/vectorStore/class.js +356 -0
  75. package/dist/src/vectorStore/hash.js +10 -0
  76. package/dist/src/vectorStore/types.js +2 -0
  77. package/dist/src/vectorStore.js +8 -359
  78. package/dist/src/vectorStore.test/_helpers.js +17 -0
  79. package/extensions/dashboard-client/dist/assets/{CacheTab-BZNnKhh5.js → CacheTab-DPRHjYZ0.js} +2 -2
  80. package/extensions/dashboard-client/dist/assets/{CacheTab-BZNnKhh5.js.map → CacheTab-DPRHjYZ0.js.map} +1 -1
  81. package/extensions/dashboard-client/dist/assets/{EventsTab-CzF5L42U.js → EventsTab-CgwYq-Qf.js} +2 -2
  82. package/extensions/dashboard-client/dist/assets/{EventsTab-CzF5L42U.js.map → EventsTab-CgwYq-Qf.js.map} +1 -1
  83. package/extensions/dashboard-client/dist/assets/{HealthTab-BLpidMrX.js → HealthTab-BgOCjVzR.js} +2 -2
  84. package/extensions/dashboard-client/dist/assets/{HealthTab-BLpidMrX.js.map → HealthTab-BgOCjVzR.js.map} +1 -1
  85. package/extensions/dashboard-client/dist/assets/MaintenanceTab-Yw1yFpsy.js +2 -0
  86. package/extensions/dashboard-client/dist/assets/MaintenanceTab-Yw1yFpsy.js.map +1 -0
  87. package/extensions/dashboard-client/dist/assets/MemoryMapTab-yHUX7Yzi.js +2 -0
  88. package/extensions/dashboard-client/dist/assets/MemoryMapTab-yHUX7Yzi.js.map +1 -0
  89. package/extensions/dashboard-client/dist/assets/{MetricsTab-B0PCysqs.js → MetricsTab-CcH9fP8P.js} +2 -2
  90. package/extensions/dashboard-client/dist/assets/{MetricsTab-B0PCysqs.js.map → MetricsTab-CcH9fP8P.js.map} +1 -1
  91. package/extensions/dashboard-client/dist/assets/{OverviewTab-DsOhqFu4.js → OverviewTab-BOs8ZNev.js} +2 -2
  92. package/extensions/dashboard-client/dist/assets/{OverviewTab-DsOhqFu4.js.map → OverviewTab-BOs8ZNev.js.map} +1 -1
  93. package/extensions/dashboard-client/dist/assets/{ReposTab-B3YKYuOf.js → ReposTab-B-MfSPvg.js} +2 -2
  94. package/extensions/dashboard-client/dist/assets/{ReposTab-B3YKYuOf.js.map → ReposTab-B-MfSPvg.js.map} +1 -1
  95. package/extensions/dashboard-client/dist/assets/{SessionsTab-B96FWcrC.js → SessionsTab-BmeN649Q.js} +2 -2
  96. package/extensions/dashboard-client/dist/assets/{SessionsTab-B96FWcrC.js.map → SessionsTab-BmeN649Q.js.map} +1 -1
  97. package/extensions/dashboard-client/dist/assets/SetupTab-nOknoph5.js +2 -0
  98. package/extensions/dashboard-client/dist/assets/SetupTab-nOknoph5.js.map +1 -0
  99. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BfkUVTvA.js → TimeSavedCard-DnPg0L0z.js} +2 -2
  100. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BfkUVTvA.js.map → TimeSavedCard-DnPg0L0z.js.map} +1 -1
  101. package/extensions/dashboard-client/dist/assets/{TopicsTab-DLRsZmXJ.js → TopicsTab-BODHOg_b.js} +2 -2
  102. package/extensions/dashboard-client/dist/assets/{TopicsTab-DLRsZmXJ.js.map → TopicsTab-BODHOg_b.js.map} +1 -1
  103. package/extensions/dashboard-client/dist/assets/{TurnsTab-DKGU2Bf6.js → TurnsTab-BK0I7hKr.js} +2 -2
  104. package/extensions/dashboard-client/dist/assets/{TurnsTab-DKGU2Bf6.js.map → TurnsTab-BK0I7hKr.js.map} +1 -1
  105. package/extensions/dashboard-client/dist/assets/{index-D-lPLRWB.js → index-mOlljTFI.js} +10 -10
  106. package/extensions/dashboard-client/dist/assets/index-mOlljTFI.js.map +1 -0
  107. package/extensions/dashboard-client/dist/assets/{useSSE-ClbbcgIp.js → useSSE-BYOtv1-3.js} +2 -2
  108. package/extensions/dashboard-client/dist/assets/{useSSE-ClbbcgIp.js.map → useSSE-BYOtv1-3.js.map} +1 -1
  109. package/extensions/dashboard-client/dist/index.html +1 -1
  110. package/extensions/dashboard-client/src/App.tsx +0 -4
  111. package/extensions/dashboard-client/src/api/client.ts +16 -0
  112. package/extensions/dashboard-client/src/tabs/MaintenanceTab/ActionsCard.tsx +178 -0
  113. package/extensions/dashboard-client/src/tabs/MaintenanceTab/DbStatsCard.tsx +140 -0
  114. package/extensions/dashboard-client/src/tabs/MaintenanceTab/DebugBundleCard.tsx +109 -0
  115. package/extensions/dashboard-client/src/tabs/MaintenanceTab/HealthMitigationCard.tsx +98 -0
  116. package/extensions/dashboard-client/src/tabs/MaintenanceTab/SchemaHealthCard.tsx +108 -0
  117. package/extensions/dashboard-client/src/tabs/MaintenanceTab.tsx +12 -598
  118. package/extensions/dashboard-client/src/tabs/MemoryMapTab/MemoryMapView.tsx +476 -0
  119. package/extensions/dashboard-client/src/tabs/MemoryMapTab/RaptorTreeView.tsx +163 -0
  120. package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +56 -466
  121. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderHealthCard.tsx +109 -0
  122. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderSetup.tsx +468 -0
  123. package/extensions/dashboard-client/src/tabs/SetupTab.tsx +23 -484
  124. package/extensions/dashboard-server/api-contracts/embedder-health.ts +32 -0
  125. package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +360 -0
  126. package/extensions/dashboard-server/api-contracts/endpoints/types.ts +350 -0
  127. package/extensions/dashboard-server/api-contracts/endpoints.ts +27 -648
  128. package/extensions/dashboard-server/api-contracts/index.ts +6 -0
  129. package/extensions/dashboard-server/api-contracts/raptor.ts +49 -0
  130. package/extensions/dashboard-server/api-contracts.test/_helpers.ts +47 -0
  131. package/extensions/dashboard-server/routes-cache.test/_helpers.ts +94 -0
  132. package/extensions/dashboard-server/routes-embedder-health.ts +77 -0
  133. package/extensions/dashboard-server/routes-raptor.ts +128 -0
  134. package/extensions/dashboard-server/routes-setup.ts +1 -1
  135. package/extensions/dashboard-server/routes.ts +2 -0
  136. package/extensions/dashboard-server/server.ts +4 -0
  137. package/extensions/mega-cache-replay.test/_helpers.ts +188 -0
  138. package/extensions/mega-commands/dataCommands.ts +169 -0
  139. package/extensions/mega-commands/helpers.ts +62 -0
  140. package/extensions/mega-commands/historyCommands.ts +117 -0
  141. package/extensions/mega-commands/setupCommand.ts +235 -0
  142. package/extensions/mega-commands.ts +25 -538
  143. package/extensions/mega-compact-s38.test/_helpers.ts +201 -0
  144. package/extensions/mega-compact.test/_helpers.ts +237 -0
  145. package/extensions/mega-compact.ts +12 -2
  146. package/extensions/mega-events/agent-handlers/agentEndHandler.ts +238 -0
  147. package/extensions/mega-events/agent-handlers/turnEndHandler/cacheStripe.ts +74 -0
  148. package/extensions/mega-events/agent-handlers/turnEndHandler/contextHealth.ts +50 -0
  149. package/extensions/mega-events/agent-handlers/turnEndHandler/errorRetry.ts +421 -0
  150. package/extensions/mega-events/agent-handlers/turnEndHandler/event.ts +16 -0
  151. package/extensions/mega-events/agent-handlers/turnEndHandler/gameScoring.ts +73 -0
  152. package/extensions/mega-events/agent-handlers/turnEndHandler/lengthStop.ts +29 -0
  153. package/extensions/mega-events/agent-handlers/turnEndHandler/memoryReview.ts +42 -0
  154. package/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.ts +61 -0
  155. package/extensions/mega-events/agent-handlers/turnEndHandler.ts +64 -0
  156. package/extensions/mega-events/agent-handlers.ts +15 -861
  157. package/extensions/mega-events/context-handler/afterCompact.ts +213 -0
  158. package/extensions/mega-events/context-handler/messageText.ts +20 -0
  159. package/extensions/mega-events/context-handler/tailResult.ts +63 -0
  160. package/extensions/mega-events/context-handler.ts +17 -235
  161. package/extensions/mega-events/separated-prompt.test/_helpers.ts +35 -0
  162. package/package.json +1 -1
  163. package/src/config.ts +4 -0
  164. package/src/dedup-engine.test/_helpers.ts +79 -0
  165. package/src/e2e.test/_helpers.ts +59 -0
  166. package/src/embedder.ts +4 -0
  167. package/src/httpEmbedder.ts +7 -0
  168. package/src/hyde.ts +162 -0
  169. package/src/importance.test/_helpers.ts +5 -0
  170. package/src/memoryGraph/sources/checkpoints.ts +99 -0
  171. package/src/memoryGraph/sources/edges.ts +23 -0
  172. package/src/memoryGraph/sources/flags.ts +26 -0
  173. package/src/memoryGraph/sources/helpers.ts +13 -0
  174. package/src/memoryGraph/sources/memories.ts +108 -0
  175. package/src/memoryGraph/sources/raptor.ts +37 -0
  176. package/src/memoryGraph/sources/turns.ts +226 -0
  177. package/src/memoryGraph/sources.ts +19 -544
  178. package/src/memoryGraph-gates.test/_helpers.ts +164 -0
  179. package/src/ratio.bench.test/_helpers.ts +268 -0
  180. package/src/recall/async.ts +142 -0
  181. package/src/recall/format.ts +173 -0
  182. package/src/recall/memory.ts +90 -0
  183. package/src/recall/reformulate.ts +111 -0
  184. package/src/recall/sync.ts +196 -0
  185. package/src/recall/types.ts +63 -0
  186. package/src/recall.ts +25 -712
  187. package/src/store/sqlite/schema/core.ts +233 -0
  188. package/src/store/sqlite/schema/game.ts +66 -0
  189. package/src/store/sqlite/schema/plan-v2.ts +108 -0
  190. package/src/store/sqlite/schema/turns.ts +60 -0
  191. package/src/store/sqlite/schema.ts +13 -441
  192. package/src/store/turns/contract-compliance.test/_helpers.ts +38 -0
  193. package/src/store/turns/sqlite-store/admin.ts +249 -0
  194. package/src/store/turns/sqlite-store/ctx.ts +14 -0
  195. package/src/store/turns/sqlite-store/read.ts +174 -0
  196. package/src/store/turns/sqlite-store/rows.ts +55 -0
  197. package/src/store/turns/sqlite-store/write.ts +193 -0
  198. package/src/store/turns/sqlite-store.ts +33 -514
  199. package/src/vectorStore/class.ts +433 -0
  200. package/src/vectorStore/hash.ts +11 -0
  201. package/src/vectorStore/types.ts +58 -0
  202. package/src/vectorStore.test/_helpers.ts +20 -0
  203. package/src/vectorStore.ts +13 -489
  204. package/extensions/dashboard-client/dist/assets/ConfigTab-DG0WkuuG.js +0 -2
  205. package/extensions/dashboard-client/dist/assets/ConfigTab-DG0WkuuG.js.map +0 -1
  206. package/extensions/dashboard-client/dist/assets/MaintenanceTab-BQF7W7xX.js +0 -2
  207. package/extensions/dashboard-client/dist/assets/MaintenanceTab-BQF7W7xX.js.map +0 -1
  208. package/extensions/dashboard-client/dist/assets/MemoryMapTab-CPtHLv0A.js +0 -2
  209. package/extensions/dashboard-client/dist/assets/MemoryMapTab-CPtHLv0A.js.map +0 -1
  210. package/extensions/dashboard-client/dist/assets/SetupTab-hbzQ_NZv.js +0 -2
  211. package/extensions/dashboard-client/dist/assets/SetupTab-hbzQ_NZv.js.map +0 -1
  212. package/extensions/dashboard-client/dist/assets/index-D-lPLRWB.js.map +0 -1
@@ -1,8 +1,13 @@
1
1
  /**
2
2
  * sqlite-store.ts — SqliteTurnStore: the reference implementation.
3
3
  *
4
- * Private schema (SQL lives here + connection.ts, not in types.ts).
5
- * All methods satisfy the TurnStore contract from types.ts.
4
+ * Thin delegate shell: each method body lives as a free function in ./sqlite-store/*
5
+ * (read.ts / write.ts / admin.ts) operating on an internal ctx ({db, stateDir,
6
+ * opts}); this class wires those to the contract. The public API surface —
7
+ * SqliteTurnStore implements TurnStore, all methods, asReader/asWriter/asAdmin,
8
+ * close — is unchanged for existing callers.
9
+ *
10
+ * Private schema (SQL lives in the impl files + connection.ts, not in types.ts).
6
11
  * Append-only: no UPDATE on turns/turn_recall after INSERT (only in migration).
7
12
  *
8
13
  * PREVENT-PI-004: node:sqlite in-process only.
@@ -10,9 +15,6 @@
10
15
  * PREVENT-001: safeJson not needed — no JSON columns in turns.db.
11
16
  */
12
17
 
13
- import { randomBytes } from "node:crypto";
14
- import { statSync } from "node:fs";
15
- import { join } from "node:path";
16
18
  import type { DatabaseSync } from "node:sqlite";
17
19
  import { openTurnDb, closeTurnDb } from "./connection.js";
18
20
  import type {
@@ -33,55 +35,10 @@ import type {
33
35
  ConversationStats,
34
36
  TurnStoreOptions,
35
37
  } from "./types.js";
36
- import { DuplicateTurnError } from "./types.js";
37
-
38
- // ─── Helpers ──────────────────────────────────────────────────────
39
-
40
- function newConversationId(): ConversationId {
41
- return `conv_${randomBytes(8).toString("hex")}`;
42
- }
43
-
44
- function normalizeSessionId(sid: SessionId): SessionId {
45
- // Matches the existing normalizeSessionId in src/store.ts
46
- return sid.replace(/-.*$/, "");
47
- }
48
-
49
- function rowToEntry(r: Record<string, unknown>): TurnEntry {
50
- return {
51
- conversationId: r.conversation_id as string,
52
- sessionId: r.session_id as string,
53
- turnIndex: r.turn_index as number,
54
- role: r.role as TurnEntry["role"],
55
- endedAt: r.ended_at as number,
56
- ctxTokens: (r.ctx_tokens as number | null) ?? undefined,
57
- ctxPercent: (r.ctx_percent as number | null) ?? undefined,
58
- pressureBand:
59
- (r.pressure_band as "green" | "yellow" | "red" | null) ?? undefined,
60
- model: (r.model as string | null) ?? undefined,
61
- epochId: (r.epoch_id as string | null) ?? undefined,
62
- };
63
- }
64
-
65
- function rowToRecall(r: Record<string, unknown>): TurnRecallEntry {
66
- return {
67
- turnId: String(r.turn_id),
68
- checkpointId: r.checkpoint_id as string,
69
- score: r.score as number,
70
- source: r.source as TurnRecallEntry["source"],
71
- raptorLevel: (r.raptor_level as number | null) ?? undefined,
72
- };
73
- }
74
-
75
- function rowToFork(r: Record<string, unknown>): ConversationFork {
76
- return {
77
- parentConversationId: r.parent_conversation_id as string,
78
- childConversationId: r.child_conversation_id as string,
79
- forkTurnIndex: r.fork_turn_index as number,
80
- createdAt: r.created_at as number,
81
- };
82
- }
83
-
84
- // ─── SqliteTurnStore ──────────────────────────────────────────────
38
+ import type { SqliteTurnStoreCtx } from "./sqlite-store/ctx.js";
39
+ import * as read from "./sqlite-store/read.js";
40
+ import * as write from "./sqlite-store/write.js";
41
+ import * as admin from "./sqlite-store/admin.js";
85
42
 
86
43
  export class SqliteTurnStore implements TurnStore {
87
44
  private db: DatabaseSync;
@@ -97,521 +54,95 @@ export class SqliteTurnStore implements TurnStore {
97
54
  });
98
55
  }
99
56
 
57
+ private ctx(): SqliteTurnStoreCtx {
58
+ return { db: this.db, stateDir: this.stateDir, opts: this.opts };
59
+ }
60
+
100
61
  // ── TurnReader ──────────────────────────────────────────────
101
62
 
102
63
  query(filter: TurnFilter): TurnEntry[] {
103
- const clauses: string[] = [];
104
- const params: (string | number)[] = [];
105
-
106
- if (filter.conversationId !== undefined) {
107
- clauses.push("conversation_id = ?");
108
- params.push(filter.conversationId);
109
- }
110
- if (filter.sessionId !== undefined) {
111
- clauses.push("session_id = ?");
112
- params.push(normalizeSessionId(filter.sessionId));
113
- }
114
- if (filter.sinceMs !== undefined) {
115
- clauses.push("ended_at >= ?");
116
- params.push(filter.sinceMs);
117
- }
118
- if (filter.untilMs !== undefined) {
119
- clauses.push("ended_at <= ?");
120
- params.push(filter.untilMs);
121
- }
122
- if (filter.pressureBand !== undefined) {
123
- clauses.push("pressure_band = ?");
124
- params.push(filter.pressureBand);
125
- }
126
-
127
- const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
128
- const limit = filter.limit ?? 1000;
129
- const offset = filter.offset ?? 0;
130
-
131
- const rows = this.db
132
- .prepare(
133
- `SELECT * FROM turns ${where} ORDER BY ended_at ASC LIMIT ? OFFSET ?`,
134
- )
135
- .all(...params, limit, offset) as Array<Record<string, unknown>>;
136
-
137
- return rows.map(rowToEntry);
64
+ return read.query(this.ctx(), filter);
138
65
  }
139
66
 
140
67
  getTurn(turnId: TurnId): TurnEntry | undefined {
141
- const row = this.db
142
- .prepare("SELECT * FROM turns WHERE id = ?")
143
- .get(Number(turnId)) as Record<string, unknown> | undefined;
144
- return row ? rowToEntry(row) : undefined;
68
+ return read.getTurn(this.ctx(), turnId);
145
69
  }
146
70
 
147
71
  getTurnByIndex(
148
72
  conversationId: ConversationId,
149
73
  turnIndex: number,
150
74
  ): TurnEntry | undefined {
151
- const row = this.db
152
- .prepare(
153
- "SELECT * FROM turns WHERE conversation_id = ? AND turn_index = ?",
154
- )
155
- .get(conversationId, turnIndex) as Record<string, unknown> | undefined;
156
- return row ? rowToEntry(row) : undefined;
75
+ return read.getTurnByIndex(this.ctx(), conversationId, turnIndex);
157
76
  }
158
77
 
159
78
  listRecall(turnId: TurnId): TurnRecallEntry[] {
160
- const rows = this.db
161
- .prepare(
162
- "SELECT * FROM turn_recall WHERE turn_id = ? ORDER BY score DESC",
163
- )
164
- .all(Number(turnId)) as Array<Record<string, unknown>>;
165
- return rows.map(rowToRecall);
79
+ return read.listRecall(this.ctx(), turnId);
166
80
  }
167
81
 
168
82
  listRecallByIndex(
169
83
  conversationId: ConversationId,
170
84
  turnIndex: number,
171
85
  ): TurnRecallEntry[] {
172
- const row = this.db
173
- .prepare(
174
- "SELECT id FROM turns WHERE conversation_id = ? AND turn_index = ?",
175
- )
176
- .get(conversationId, turnIndex) as { id: number } | undefined;
177
- if (!row) return [];
178
- return this.listRecall(String(row.id));
86
+ return read.listRecallByIndex(this.ctx(), conversationId, turnIndex);
179
87
  }
180
88
 
181
89
  listForks(conversationId: ConversationId): ConversationFork[] {
182
- const rows = this.db
183
- .prepare(
184
- "SELECT * FROM conversation_forks WHERE parent_conversation_id = ? ORDER BY created_at ASC",
185
- )
186
- .all(conversationId) as Array<Record<string, unknown>>;
187
- return rows.map(rowToFork);
90
+ return read.listForks(this.ctx(), conversationId);
188
91
  }
189
92
 
190
93
  countTurns(conversationId: ConversationId): number {
191
- const row = this.db
192
- .prepare("SELECT COUNT(*) as cnt FROM turns WHERE conversation_id = ?")
193
- .get(conversationId) as { cnt: number };
194
- return row.cnt;
94
+ return read.countTurns(this.ctx(), conversationId);
195
95
  }
196
96
 
197
97
  conversationStats(conversationId: ConversationId): ConversationStats {
198
- const rows = this.db
199
- .prepare(
200
- "SELECT ended_at, ctx_percent, pressure_band FROM turns WHERE conversation_id = ? ORDER BY ended_at ASC",
201
- )
202
- .all(conversationId) as Array<Record<string, unknown>>;
203
-
204
- if (rows.length === 0) {
205
- return {
206
- turnCount: 0,
207
- firstTurnAt: 0,
208
- lastTurnAt: 0,
209
- avgCtxPercent: 0,
210
- pressureBands: {},
211
- };
212
- }
213
-
214
- let ctxSum = 0;
215
- let ctxCount = 0;
216
- const bands: Record<string, number> = {};
217
-
218
- for (const r of rows) {
219
- if (r.ctx_percent !== null) {
220
- ctxSum += r.ctx_percent as number;
221
- ctxCount++;
222
- }
223
- const band = r.pressure_band as string | null;
224
- if (band) bands[band] = (bands[band] ?? 0) + 1;
225
- }
226
-
227
- return {
228
- turnCount: rows.length,
229
- firstTurnAt: rows[0].ended_at as number,
230
- lastTurnAt: rows[rows.length - 1].ended_at as number,
231
- avgCtxPercent: ctxCount > 0 ? ctxSum / ctxCount : 0,
232
- pressureBands: bands,
233
- };
98
+ return read.conversationStats(this.ctx(), conversationId);
234
99
  }
235
100
 
236
101
  // ── TurnWriter ──────────────────────────────────────────────
237
102
 
238
103
  appendTurn(entry: TurnEntry): TurnId {
239
- const sid = normalizeSessionId(entry.sessionId);
240
- this.persistSessionConv(sid, entry.conversationId);
241
- try {
242
- this.db
243
- .prepare(
244
- `INSERT INTO turns (conversation_id, session_id, turn_index, role, ended_at,
245
- ctx_tokens, ctx_percent, pressure_band, model, epoch_id)
246
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
247
- )
248
- .run(
249
- entry.conversationId,
250
- sid,
251
- entry.turnIndex,
252
- entry.role,
253
- entry.endedAt,
254
- entry.ctxTokens ?? null,
255
- entry.ctxPercent ?? null,
256
- entry.pressureBand ?? null,
257
- entry.model ?? null,
258
- entry.epochId ?? null,
259
- );
260
- } catch (e) {
261
- if (
262
- e instanceof Error &&
263
- e.message.includes("UNIQUE constraint") &&
264
- (e as { code?: string }).code === "ERR_SQLITE_ERROR"
265
- ) {
266
- throw new DuplicateTurnError(entry.conversationId, entry.turnIndex);
267
- }
268
- throw e;
269
- }
270
- const row = this.db
271
- .prepare(
272
- "SELECT id FROM turns WHERE conversation_id = ? AND turn_index = ?",
273
- )
274
- .get(entry.conversationId, entry.turnIndex) as { id: number } | undefined;
275
- return String(row!.id);
276
- }
277
-
278
- /** Persist the session → conversation mapping when a turn is written. */
279
- private persistSessionConv(sid: string, conversationId: string): void {
280
- this.db
281
- .prepare(
282
- "INSERT OR IGNORE INTO session_conversations (session_id, conversation_id) VALUES (?, ?)",
283
- )
284
- .run(sid, conversationId);
104
+ return write.appendTurn(this.ctx(), entry);
285
105
  }
286
106
 
287
107
  appendRecall(entry: TurnRecallEntry): void {
288
- this.db
289
- .prepare(
290
- `INSERT OR IGNORE INTO turn_recall (turn_id, checkpoint_id, score, source, raptor_level)
291
- VALUES (?, ?, ?, ?, ?)`,
292
- )
293
- .run(
294
- Number(entry.turnId),
295
- entry.checkpointId,
296
- entry.score,
297
- entry.source,
298
- entry.raptorLevel ?? null,
299
- );
108
+ write.appendRecall(this.ctx(), entry);
300
109
  }
301
110
 
302
111
  ensureConversationId(sessionId: SessionId): ConversationId {
303
- const sid = normalizeSessionId(sessionId);
304
- // Check session_conversations table first (persistent mapping)
305
- const row = this.db
306
- .prepare(
307
- "SELECT conversation_id FROM session_conversations WHERE session_id = ?",
308
- )
309
- .get(sid) as { conversation_id: string } | undefined;
310
- if (row) return row.conversation_id;
311
-
312
- // Check if any turn exists for this session (fallback)
313
- const turnRow = this.db
314
- .prepare(
315
- "SELECT conversation_id FROM turns WHERE session_id = ? ORDER BY ended_at DESC LIMIT 1",
316
- )
317
- .get(sid) as { conversation_id: string } | undefined;
318
- if (turnRow) {
319
- // Persist the mapping so future calls are consistent
320
- this.db
321
- .prepare(
322
- "INSERT OR IGNORE INTO session_conversations (session_id, conversation_id) VALUES (?, ?)",
323
- )
324
- .run(sid, turnRow.conversation_id);
325
- return turnRow.conversation_id;
326
- }
327
-
328
- // No turns yet — generate and persist a new conversation id
329
- const convId = newConversationId();
330
- this.db
331
- .prepare(
332
- "INSERT OR IGNORE INTO session_conversations (session_id, conversation_id) VALUES (?, ?)",
333
- )
334
- .run(sid, convId);
335
- return convId;
112
+ return write.ensureConversationId(this.ctx(), sessionId);
336
113
  }
337
114
 
338
115
  forkConversation(
339
116
  parentId: ConversationId,
340
117
  forkTurnIndex: number,
341
118
  ): ConversationId {
342
- const childId = newConversationId();
343
- const now = Date.now();
344
-
345
- // 1. Record the fork lineage
346
- this.db
347
- .prepare(
348
- `INSERT INTO conversation_forks (parent_conversation_id, child_conversation_id, fork_turn_index, created_at)
349
- VALUES (?, ?, ?, ?)`,
350
- )
351
- .run(parentId, childId, forkTurnIndex, now);
352
-
353
- // 2. Find the parent's turn at forkTurnIndex
354
- const parentTurn = this.db
355
- .prepare(
356
- "SELECT id FROM turns WHERE conversation_id = ? AND turn_index = ?",
357
- )
358
- .get(parentId, forkTurnIndex) as { id: number } | undefined;
359
-
360
- if (!parentTurn) return childId; // no parent turn at that index — nothing to seed
361
-
362
- // 3. Create a seed turn in the child conversation (turnIndex 0, role system)
363
- this.db
364
- .prepare(
365
- `INSERT INTO turns (conversation_id, session_id, turn_index, role, ended_at)
366
- VALUES (?, ?, 0, 'system', ?)`,
367
- )
368
- .run(childId, `fork_${childId}`, now);
369
-
370
- const childTurnRow = this.db
371
- .prepare(
372
- "SELECT id FROM turns WHERE conversation_id = ? AND turn_index = 0",
373
- )
374
- .get(childId) as { id: number } | undefined;
375
- if (!childTurnRow) return childId;
376
-
377
- // 4. Copy the parent's recall entries into the child's seed turn
378
- const parentRecall = this.db
379
- .prepare(
380
- "SELECT checkpoint_id, score, source, raptor_level FROM turn_recall WHERE turn_id = ?",
381
- )
382
- .all(parentTurn.id) as Array<Record<string, unknown>>;
383
-
384
- const insertRecall = this.db.prepare(
385
- `INSERT OR IGNORE INTO turn_recall (turn_id, checkpoint_id, score, source, raptor_level)
386
- VALUES (?, ?, ?, ?, ?)`,
387
- );
388
- for (const r of parentRecall) {
389
- insertRecall.run(
390
- childTurnRow.id,
391
- r.checkpoint_id as string,
392
- r.score as number,
393
- r.source as string,
394
- (r.raptor_level as number | null) ?? null,
395
- );
396
- }
397
-
398
- return childId;
119
+ return write.forkConversation(this.ctx(), parentId, forkTurnIndex);
399
120
  }
400
121
 
401
122
  // ── TurnAdmin ───────────────────────────────────────────────
402
123
 
403
124
  prune(policy: RetentionPolicy): PruneReport {
404
- const beforeSize = this.dbSizeBytes();
405
- const cutoff = Date.now() - policy.maxTurnAgeMs;
406
-
407
- // Find conversations with turns to potentially prune
408
- const convRows = this.db
409
- .prepare("SELECT DISTINCT conversation_id FROM turns")
410
- .all() as Array<{ conversation_id: string }>;
411
-
412
- let turnsRemoved = 0;
413
- let recallRemoved = 0;
414
-
415
- for (const { conversation_id } of convRows) {
416
- // Count total turns in this conversation
417
- const total = this.db
418
- .prepare("SELECT COUNT(*) as cnt FROM turns WHERE conversation_id = ?")
419
- .get(conversation_id) as { cnt: number };
420
-
421
- // Keep at least keepMinPerConversation — delete only the oldest
422
- const canDelete = Math.max(0, total.cnt - policy.keepMinPerConversation);
423
- if (canDelete === 0) continue;
424
-
425
- // Find old turns to delete
426
- const oldTurns = this.db
427
- .prepare(
428
- `SELECT id FROM turns WHERE conversation_id = ? AND ended_at < ?
429
- ORDER BY ended_at ASC LIMIT ?`,
430
- )
431
- .all(conversation_id, cutoff, canDelete) as Array<{
432
- id: number;
433
- }>;
434
-
435
- if (oldTurns.length === 0) continue;
436
-
437
- const ids = oldTurns.map((t) => t.id);
438
- const placeholders = ids.map(() => "?").join(",");
439
-
440
- // Delete recall for those turns
441
- const delRecall = this.db
442
- .prepare(`DELETE FROM turn_recall WHERE turn_id IN (${placeholders})`)
443
- .run(...ids);
444
- recallRemoved += Number(delRecall.changes);
445
-
446
- // Delete the turns
447
- const delTurns = this.db
448
- .prepare(`DELETE FROM turns WHERE id IN (${placeholders})`)
449
- .run(...ids);
450
- turnsRemoved += Number(delTurns.changes);
451
- }
452
-
453
- // Count preserved branches
454
- const branchesPreserved = (
455
- this.db
456
- .prepare("SELECT COUNT(*) as cnt FROM conversation_forks")
457
- .get() as { cnt: number }
458
- ).cnt;
459
-
460
- if (policy.vacuumAfterPrune && (turnsRemoved > 0 || recallRemoved > 0)) {
461
- this.vacuum();
462
- }
463
-
464
- const afterSize = this.dbSizeBytes();
465
-
466
- return {
467
- turnsRemoved,
468
- recallRemoved,
469
- branchesPreserved,
470
- freedBytes: Math.max(0, beforeSize - afterSize),
471
- };
125
+ return admin.prune(this.ctx(), policy);
472
126
  }
473
127
 
474
128
  vacuum(): void {
475
- this.db.exec("VACUUM");
129
+ admin.vacuum(this.ctx());
476
130
  }
477
131
 
478
132
  checkpoint(): StoreSnapshot {
479
- // Query turns in row-id order (ended_at ASC with unique constraint)
480
- const turnRows = this.db
481
- .prepare("SELECT * FROM turns ORDER BY ended_at ASC")
482
- .all() as Array<Record<string, unknown>>;
483
- const turns = turnRows.map(rowToEntry);
484
-
485
- // Build mapping: row_id → 1-based position in turns array
486
- const rowIdToPos = new Map<number, number>();
487
- for (let i = 0; i < turnRows.length; i++) {
488
- rowIdToPos.set(turnRows[i].id as number, i + 1);
489
- }
490
-
491
- const recallRows = this.db
492
- .prepare("SELECT * FROM turn_recall ORDER BY turn_id ASC")
493
- .all() as Array<Record<string, unknown>>;
494
-
495
- // Replace recall's turnId with the 1-based position of the referenced turn
496
- // in the turns array. This makes the TurnId stable across restore.
497
- const recall = recallRows.map((r) => {
498
- const originalTurnId = r.turn_id as number;
499
- const stableTurnId = rowIdToPos.get(originalTurnId) ?? originalTurnId;
500
- return {
501
- turnId: String(stableTurnId),
502
- checkpointId: r.checkpoint_id as string,
503
- score: r.score as number,
504
- source: r.source as TurnRecallEntry["source"],
505
- raptorLevel: (r.raptor_level as number | null) ?? undefined,
506
- };
507
- });
508
-
509
- const forks = (
510
- this.db
511
- .prepare("SELECT * FROM conversation_forks ORDER BY created_at ASC")
512
- .all() as Array<Record<string, unknown>>
513
- ).map(rowToFork);
514
-
515
- return {
516
- version: 1,
517
- exportedAt: Date.now(),
518
- turns,
519
- recall,
520
- forks,
521
- };
133
+ return admin.checkpoint(this.ctx());
522
134
  }
523
135
 
524
136
  restore(from: StoreSnapshot): void {
525
- this.clear();
526
- const insertTurn = this.db.prepare(
527
- `INSERT INTO turns (conversation_id, session_id, turn_index, role, ended_at,
528
- ctx_tokens, ctx_percent, pressure_band, model, epoch_id)
529
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
530
- );
531
- const insertRecall = this.db.prepare(
532
- `INSERT INTO turn_recall (turn_id, checkpoint_id, score, source, raptor_level)
533
- VALUES (?, ?, ?, ?, ?)`,
534
- );
535
- const insertFork = this.db.prepare(
536
- `INSERT INTO conversation_forks (parent_conversation_id, child_conversation_id, fork_turn_index, created_at)
537
- VALUES (?, ?, ?, ?)`,
538
- );
539
-
540
- this.db.exec("BEGIN TRANSACTION");
541
- try {
542
- // Map: 1-based position → new auto-increment row ID
543
- const posToNewId = new Map<number, number>();
544
-
545
- for (let i = 0; i < from.turns.length; i++) {
546
- const t = from.turns[i];
547
- insertTurn.run(
548
- t.conversationId,
549
- normalizeSessionId(t.sessionId),
550
- t.turnIndex,
551
- t.role,
552
- t.endedAt,
553
- t.ctxTokens ?? null,
554
- t.ctxPercent ?? null,
555
- t.pressureBand ?? null,
556
- t.model ?? null,
557
- t.epochId ?? null,
558
- );
559
- const row = this.db
560
- .prepare(
561
- "SELECT id FROM turns WHERE conversation_id = ? AND turn_index = ?",
562
- )
563
- .get(t.conversationId, t.turnIndex) as { id: number } | undefined;
564
- if (row) posToNewId.set(i + 1, row.id);
565
- }
566
-
567
- // Recall's turnId is now the 1-based position from checkpoint().
568
- // Map it to the new row ID.
569
- for (const r of from.recall) {
570
- const targetTurnId =
571
- posToNewId.get(Number(r.turnId)) ?? Number(r.turnId);
572
- insertRecall.run(
573
- targetTurnId,
574
- r.checkpointId,
575
- r.score,
576
- r.source,
577
- r.raptorLevel ?? null,
578
- );
579
- }
580
-
581
- for (const f of from.forks) {
582
- insertFork.run(
583
- f.parentConversationId,
584
- f.childConversationId,
585
- f.forkTurnIndex,
586
- f.createdAt,
587
- );
588
- }
589
- this.db.exec("COMMIT");
590
- } catch (err) {
591
- this.db.exec("ROLLBACK");
592
- throw err;
593
- }
137
+ admin.restore(this.ctx(), from);
594
138
  }
595
139
 
596
140
  clear(): void {
597
- this.db.exec("DELETE FROM turn_recall");
598
- this.db.exec("DELETE FROM conversation_forks");
599
- this.db.exec("DELETE FROM turns");
600
- this.db.exec("DELETE FROM session_conversations");
601
- // Reset autoincrement
602
- this.db.exec(
603
- "DELETE FROM sqlite_sequence WHERE name IN ('turns','turn_recall','conversation_forks')",
604
- );
141
+ admin.clear(this.ctx());
605
142
  }
606
143
 
607
144
  stampTurnsEpoch(sessionId: SessionId, epochId: string): number {
608
- const sid = normalizeSessionId(sessionId);
609
- const res = this.db
610
- .prepare(
611
- "UPDATE turns SET epoch_id = ? WHERE session_id = ? AND epoch_id IS NULL",
612
- )
613
- .run(epochId, sid);
614
- return Number(res.changes ?? 0);
145
+ return admin.stampTurnsEpoch(this.ctx(), sessionId, epochId);
615
146
  }
616
147
 
617
148
  // ── Capability gating ───────────────────────────────────────
@@ -655,16 +186,4 @@ export class SqliteTurnStore implements TurnStore {
655
186
  inMemory: this.opts.inMemory,
656
187
  });
657
188
  }
658
-
659
- // ── Private helpers ─────────────────────────────────────────
660
-
661
- private dbSizeBytes(): number {
662
- if (this.opts.inMemory) return 0;
663
- try {
664
- const path = this.opts.dbPath ?? join(this.stateDir, "turns.db");
665
- return statSync(path).size;
666
- } catch {
667
- return 0;
668
- }
669
- }
670
189
  }