@cortexkit/opencode-magic-context 0.49.25 → 0.49.27

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 (124) hide show
  1. package/dist/auto-search-worker.js +4 -4
  2. package/dist/chunk-embedding-write-worker.js +7 -7
  3. package/dist/{compartment-chunk-embedding-eypvn607.js → compartment-chunk-embedding-qgp0tbf7.js} +3 -3
  4. package/dist/config/schema/magic-context.d.ts +393 -393
  5. package/dist/{conflict-warning-hook-a27bhggg.js → conflict-warning-hook-tgnw019g.js} +4 -4
  6. package/dist/{context-keb00be8.js → context-t2k97d6v.js} +116 -116
  7. package/dist/{embedding-backfill-vddgmem3.js → embedding-backfill-cwnyr4fj.js} +5 -5
  8. package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
  9. package/dist/features/magic-context/message-index-async.d.ts +1 -4
  10. package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
  11. package/dist/features/magic-context/message-index.d.ts +1 -28
  12. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  13. package/dist/features/magic-context/session-retention.fixture.d.ts +4 -0
  14. package/dist/features/magic-context/session-retention.fixture.d.ts.map +1 -0
  15. package/dist/features/magic-context/storage-session-tables.d.ts +5 -12
  16. package/dist/features/magic-context/storage-session-tables.d.ts.map +1 -1
  17. package/dist/hooks/magic-context/caveman-cleanup.d.ts +14 -0
  18. package/dist/hooks/magic-context/caveman-cleanup.d.ts.map +1 -1
  19. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts +11 -0
  20. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  21. package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
  22. package/dist/hooks/magic-context/foreign-text-parts.d.ts +47 -0
  23. package/dist/hooks/magic-context/foreign-text-parts.d.ts.map +1 -0
  24. package/dist/hooks/magic-context/historian-history-wait.d.ts +10 -0
  25. package/dist/hooks/magic-context/historian-history-wait.d.ts.map +1 -0
  26. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  27. package/dist/hooks/magic-context/lkg-recovery-fence.d.ts +0 -5
  28. package/dist/hooks/magic-context/lkg-recovery-fence.d.ts.map +1 -1
  29. package/dist/hooks/magic-context/raw-ordinal-warmup.d.ts +0 -10
  30. package/dist/hooks/magic-context/raw-ordinal-warmup.d.ts.map +1 -1
  31. package/dist/hooks/magic-context/read-session-chunk.d.ts +21 -0
  32. package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
  33. package/dist/hooks/magic-context/read-session-raw.d.ts +20 -0
  34. package/dist/hooks/magic-context/read-session-raw.d.ts.map +1 -1
  35. package/dist/hooks/magic-context/tag-messages.d.ts +15 -0
  36. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  37. package/dist/hooks/magic-context/todo-view.d.ts +2 -2
  38. package/dist/hooks/magic-context/transform.d.ts +7 -0
  39. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  40. package/dist/{index-jcr6pe7z.js → index-077ht5cq.js} +1 -1
  41. package/dist/{index-ebsvmc5s.js → index-31y1mznz.js} +4 -4
  42. package/dist/{index-q7ywt6xt.js → index-4cpc59jv.js} +5 -5
  43. package/dist/{index-t6j3qxnd.js → index-4ypqzvhf.js} +1 -1
  44. package/dist/index-7gbcg8xv.js +559 -0
  45. package/dist/{index-9q1g3c5q.js → index-7kkphb6c.js} +1 -1
  46. package/dist/{index-bhyksfyy.js → index-89k1w6x2.js} +3 -3
  47. package/dist/{index-z05n7702.js → index-a2fz9hpa.js} +1 -1
  48. package/dist/{index-0s8454t2.js → index-dcqk0y27.js} +1560 -91
  49. package/dist/{index-n18sch77.js → index-dzfevacx.js} +9 -9
  50. package/dist/{index-axjgxeww.js → index-fq2p158w.js} +169 -13
  51. package/dist/{index-3d5wnkq7.js → index-mm93m7y6.js} +1 -1
  52. package/dist/{index-zzrzjnx5.js → index-n8qmmd7f.js} +3 -3
  53. package/dist/{index-whcgcfrv.js → index-np3rgfw4.js} +1 -1
  54. package/dist/{index-y6xpzjzr.js → index-qzbpbnqt.js} +585 -602
  55. package/dist/{index-7tdtpqrg.js → index-rverk2vz.js} +39 -8
  56. package/dist/{index-cfaet9cg.js → index-s1n8dd2n.js} +1 -1
  57. package/dist/{index-z928hmnp.js → index-t3rbb8wr.js} +2 -2
  58. package/dist/{index-2haxh8h3.js → index-tvzvjggy.js} +1 -1
  59. package/dist/{index-c9zrgcnt.js → index-v0dhhxsq.js} +1 -1
  60. package/dist/{index-szf8r0ba.js → index-v15ne3a3.js} +2 -5
  61. package/dist/{index-x9k3mqrf.js → index-xrnsygfq.js} +1 -1
  62. package/dist/{index-yrrsp47d.js → index-yzjhx10y.js} +20 -6
  63. package/dist/index.js +40 -38
  64. package/dist/{message-fts-rowid-map-9e1getgs.js → message-fts-rowid-map-va36knh4.js} +2 -4
  65. package/dist/{message-index-bounded-vaeqhp9w.js → message-index-bounded-7ayzb2ah.js} +22 -25
  66. package/dist/migration-worker.js +6 -6
  67. package/dist/plugin/dream-timer.d.ts.map +1 -1
  68. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  69. package/dist/{project-embedding-registry-rstzmb60.js → project-embedding-registry-94nncs5g.js} +4 -4
  70. package/dist/raw-ordinal-warmup-worker.js +7 -30
  71. package/dist/{read-session-chunk-z5gnc0zb.js → read-session-chunk-jzb6w9w5.js} +8 -6
  72. package/dist/{read-session-raw-s4amywnh.js → read-session-raw-b2sd67v4.js} +3 -1
  73. package/dist/{recomp-orchestrator-qwqeskhq.js → recomp-orchestrator-02sa2ngr.js} +17 -17
  74. package/dist/{search-archive-mfabzyn2.js → search-archive-q1vw7zg4.js} +7 -7
  75. package/dist/{search-84zfqeky.js → search-spq6nfyf.js} +14 -15
  76. package/dist/{send-session-notification-ygj4mja0.js → send-session-notification-afzzqy5b.js} +3 -3
  77. package/dist/{storage-tags-a77q4v77.js → storage-tags-6hvkk01c.js} +2 -2
  78. package/dist/tools/ctx-reduce/tools.d.ts.map +1 -1
  79. package/dist/{transform-postprocess-phase-hr1ntmjj.js → transform-postprocess-phase-6menyyxc.js} +17 -17
  80. package/dist/v2/auto-search-worker.js +4 -4
  81. package/dist/v2/chunk-embedding-write-worker.js +7 -7
  82. package/dist/v2/{compartment-chunk-embedding-tk6g4q80.js → compartment-chunk-embedding-63dzs31c.js} +3 -3
  83. package/dist/v2/{context-y7wfvdve.js → context-na0esngr.js} +342 -218
  84. package/dist/v2/{embedding-backfill-w5qp7t28.js → embedding-backfill-rh5dgsva.js} +5 -5
  85. package/dist/v2/hooks/context.d.ts.map +1 -1
  86. package/dist/v2/{message-fts-rowid-map-pqk3a55v.js → message-fts-rowid-map-78awh03v.js} +2 -4
  87. package/dist/v2/{message-index-bounded-te303atj.js → message-index-bounded-n09b74vr.js} +21 -24
  88. package/dist/v2/migration-worker.js +6 -6
  89. package/dist/v2/{project-embedding-registry-e7zftfvj.js → project-embedding-registry-6pak822h.js} +4 -4
  90. package/dist/v2/raw-ordinal-warmup-worker.js +7 -30
  91. package/dist/v2/{read-session-chunk-knvkq1hf.js → read-session-chunk-vpt69tw9.js} +7 -5
  92. package/dist/v2/{read-session-raw-ppjxnqk4.js → read-session-raw-5x088amc.js} +3 -1
  93. package/dist/v2/{recomp-orchestrator-e8a4jpvr.js → recomp-orchestrator-txsmbnvw.js} +14 -14
  94. package/dist/v2/{search-archive-vrwgxv6z.js → search-archive-t9bcz4ht.js} +6 -6
  95. package/dist/v2/{search-hav4dggf.js → search-dcy9b6gd.js} +13 -14
  96. package/dist/v2/{send-session-notification-f8scq6ae.js → send-session-notification-vdsgnf4c.js} +1 -1
  97. package/dist/v2/{server-pw21ypwb.js → server-4sn3at8p.js} +1 -1
  98. package/dist/v2/{server-8j2e7t31.js → server-6avtp9jp.js} +1 -1
  99. package/dist/v2/{server-wwaw5npa.js → server-6g98c227.js} +1 -1
  100. package/dist/v2/{server-swvhxhxz.js → server-6vd2n183.js} +39 -8
  101. package/dist/v2/{server-wxrdher8.js → server-awr8qa4c.js} +2 -5
  102. package/dist/v2/{server-7d6m1p2b.js → server-cqjvxyrp.js} +1 -1
  103. package/dist/v2/{server-e75vag9d.js → server-e3hc93wk.js} +5 -5
  104. package/dist/v2/{server-7weh65vd.js → server-jj9qye8y.js} +1520 -79
  105. package/dist/v2/{server-fa7vtp40.js → server-nxas8v1d.js} +1 -1
  106. package/dist/v2/{server-tfawsj08.js → server-txbc3czn.js} +9 -9
  107. package/dist/v2/{server-rqebawx1.js → server-v77xa5wj.js} +1 -1
  108. package/dist/v2/{server-5brp2rfn.js → server-xkvxss5g.js} +19 -5
  109. package/dist/v2/{server-q5z0gag0.js → server-yqek3s4k.js} +1 -1
  110. package/dist/v2/server-z2n2xaeh.js +538 -0
  111. package/dist/v2/{server-9xbbaekh.js → server-z3mk9jdc.js} +2 -2
  112. package/dist/v2/{server-m6yyn961.js → server-zmcrvpm3.js} +2 -2
  113. package/dist/v2/server.js +2 -2
  114. package/dist/v2/{storage-tags-q71rnr9f.js → storage-tags-18nbaanf.js} +2 -2
  115. package/dist/v2/store-reader.d.ts +0 -2
  116. package/dist/v2/store-reader.d.ts.map +1 -1
  117. package/dist/v2/{wrapup-orchestrator-ycs8y6n4.js → wrapup-orchestrator-gjkszser.js} +14 -14
  118. package/dist/{wrapup-orchestrator-f642w8n3.js → wrapup-orchestrator-n5h5sr1x.js} +18 -18
  119. package/package.json +1 -1
  120. package/src/shared/write-transaction-attribution.test.ts +1 -6
  121. package/dist/index-awcftgmx.js +0 -2251
  122. package/dist/message-index-3g966xn7.js +0 -61
  123. package/dist/v2/message-index-fz6b4jeh.js +0 -60
  124. package/dist/v2/server-6a8pxpxg.js +0 -2220
@@ -1,2220 +0,0 @@
1
- import {
2
- getHarness2
3
- } from "./server-pavzsdc6.js";
4
- import {
5
- getMagicContextStorageDir
6
- } from "./server-6495nmn9.js";
7
- import {
8
- isPidAlive,
9
- readProcessStartTime
10
- } from "./server-k3t06fxr.js";
11
- import {
12
- resolveOpenCodeDbPath,
13
- assertOpenCodeStoreGeneration
14
- } from "./server-2ra6pwf3.js";
15
- import {
16
- Database2,
17
- getSqliteDatabasePath2,
18
- prepareCachedStatement2,
19
- withSqliteBackgroundWriter2,
20
- withoutSqliteTransformPass2
21
- } from "./server-gzy5w8ya.js";
22
- import {
23
- removeSystemReminders,
24
- removeNestedBlocks,
25
- hasMeaningfulUserText2,
26
- extractTexts2
27
- } from "./server-3mz2qh5a.js";
28
- import {
29
- TAG_TOMBSTONE_OWNER_PREFIX,
30
- tagTombstoneOwnerRange,
31
- tagTombstoneOwnerKey,
32
- tagRevisionKey,
33
- tagIdentityRevisionKey,
34
- deleteTagIdentityRevisions
35
- } from "./server-rqebawx1.js";
36
- import {
37
- cleanUserText2
38
- } from "./server-5brp2rfn.js";
39
- import {
40
- BoundedSessionMap
41
- } from "./server-7d6m1p2b.js";
42
- import {
43
- recordMessageFtsRowid2,
44
- recordIndexedMessageTime2,
45
- messageFtsOrdinalRangeIsMapped2,
46
- getMessageFtsRowidMapBackfillProgress2,
47
- deleteUnmappedMessageFtsRows2
48
- } from "./server-wxrdher8.js";
49
- import {
50
- deleteChunkEmbedBackoffForSession2
51
- } from "./server-pw21ypwb.js";
52
- import {
53
- sameProjectDirectory
54
- } from "./server-knvkbfvh.js";
55
- import {
56
- closeQuietly
57
- } from "./server-8ajstryz.js";
58
- import {
59
- logSlowWriteTransaction
60
- } from "./server-gyy4k6mf.js";
61
- import {
62
- ensurePrivateStorageDirectorySync,
63
- writeStorageFileSync,
64
- writeStorageFileAtomicSync,
65
- syncStorageDirectorySync,
66
- writeStorageFileDurableAtomicSync
67
- } from "./server-fvnzcnww.js";
68
-
69
- // src/hooks/magic-context/lkg-recovery-fence.ts
70
- import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
71
- import {
72
- lstatSync,
73
- opendirSync,
74
- readdirSync as readdirSync2,
75
- readFileSync as readFileSync2,
76
- rmdirSync,
77
- rmSync as rmSync2
78
- } from "node:fs";
79
- import { basename, dirname, join as join2 } from "node:path";
80
-
81
- // src/shared/storage-file-mutex.ts
82
- import { execFileSync } from "node:child_process";
83
- import { createHash, randomUUID } from "node:crypto";
84
- import { readdirSync, readFileSync, rmSync } from "node:fs";
85
- import { join } from "node:path";
86
- var ticketChecksum = (name, ticket) => createHash("sha256").update(JSON.stringify([name, ticket.choosing, ticket.number, ticket.birth])).digest("hex");
87
- function processBirth(pid) {
88
- if (process.platform === "linux") {
89
- try {
90
- const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
91
- const ticks = stat.slice(stat.lastIndexOf(")") + 2).trim().split(/\s+/)[19];
92
- if (!ticks || !/^\d+$/.test(ticks))
93
- return null;
94
- return `${readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim()}:${ticks}`;
95
- } catch {
96
- return null;
97
- }
98
- }
99
- if (process.platform === "darwin") {
100
- try {
101
- return execFileSync("ps", ["-p", String(pid), "-o", "lstart="], {
102
- encoding: "utf8",
103
- timeout: 1000,
104
- windowsHide: true,
105
- env: { ...process.env, TZ: "UTC", LC_ALL: "C" },
106
- stdio: ["ignore", "pipe", "pipe"]
107
- }).trim() || null;
108
- } catch {
109
- return null;
110
- }
111
- }
112
- const start = readProcessStartTime(pid);
113
- return start === null ? null : String(start);
114
- }
115
- var pause = new Int32Array(new SharedArrayBuffer(4));
116
- function withStorageFileMutex(directory, work, timeoutMs = 1000) {
117
- ensurePrivateStorageDirectorySync(directory);
118
- const name = `${process.pid}-${randomUUID()}.lock`;
119
- const ownPath = join(directory, name);
120
- const birth = processBirth(process.pid);
121
- const ticket = { choosing: true, number: 0, birth, checksum: "" };
122
- const encodedTicket = () => JSON.stringify({ ...ticket, checksum: ticketChecksum(name, ticket) });
123
- for (let attempt = 0;; attempt++) {
124
- try {
125
- writeStorageFileSync(ownPath, encodedTicket(), { flag: "wx", forcePrivate: true });
126
- break;
127
- } catch (error) {
128
- if (error.code !== "ENOENT" || attempt >= 2)
129
- throw error;
130
- ensurePrivateStorageDirectorySync(directory);
131
- }
132
- }
133
- const deadline = performance.now() + timeoutMs;
134
- const peers = () => {
135
- const found = [];
136
- for (const other of readdirSync(directory)) {
137
- if (other === name || !/^\d+-[\da-f-]+\.lock$/.test(other))
138
- continue;
139
- const pid = Number(other.split("-")[0]);
140
- const otherPath = join(directory, other);
141
- let value = null;
142
- try {
143
- const parsed = JSON.parse(readFileSync(otherPath, "utf8"));
144
- if (typeof parsed.choosing === "boolean" && Number.isSafeInteger(parsed.number) && parsed.number >= 0 && (parsed.birth === null || typeof parsed.birth === "string") && parsed.checksum === ticketChecksum(other, parsed))
145
- value = parsed;
146
- } catch (error) {
147
- if (error.code === "ENOENT")
148
- continue;
149
- }
150
- const alive = isPidAlive(pid);
151
- const actualBirth = alive === "alive" && value?.birth ? processBirth(pid) : null;
152
- if (alive === "dead" || actualBirth !== null && value?.birth && actualBirth !== value.birth) {
153
- rmSync(otherPath, { force: true });
154
- continue;
155
- }
156
- found.push([other, value]);
157
- }
158
- return found;
159
- };
160
- try {
161
- ticket.number = 1 + Math.max(0, ...peers().map(([, value]) => value?.number ?? 0));
162
- ticket.choosing = false;
163
- writeStorageFileAtomicSync(ownPath, encodedTicket(), true);
164
- while (peers().some(([other, value]) => !value || value.choosing || value.number < ticket.number || value.number === ticket.number && other < name)) {
165
- if (performance.now() >= deadline)
166
- throw new Error("Storage safety publication lock is unavailable");
167
- Atomics.wait(pause, 0, 0, 10);
168
- }
169
- const result = work();
170
- if (result && typeof result.then === "function")
171
- throw new TypeError("Storage publication mutex requires synchronous work");
172
- return result;
173
- } finally {
174
- rmSync(ownPath, { force: true });
175
- }
176
- }
177
-
178
- // src/hooks/magic-context/lkg-recovery-fence.ts
179
- class LkgRecoveryFenceError extends Error {
180
- constructor(message) {
181
- super(message);
182
- this.name = "LkgRecoveryFenceError";
183
- }
184
- }
185
- var platform = process.platform;
186
- var observer;
187
- var knownFences = new BoundedSessionMap(1000);
188
- var ioObserver;
189
- var cacheKey = (sessionId) => `${getMagicContextStorageDir()}\x00${getHarness2()}\x00${sessionId}`;
190
- function remember(sessionId, value) {
191
- knownFences.set(cacheKey(sessionId), value);
192
- return value;
193
- }
194
- function known(sessionId) {
195
- const prior = knownFences.get(cacheKey(sessionId));
196
- if (prior)
197
- return prior;
198
- const record = read(sessionId);
199
- if (record)
200
- return remember(sessionId, { state: record.state, record });
201
- try {
202
- lstatSync(lkgRecoveryFencePath(sessionId));
203
- } catch (error) {
204
- if (error.code === "ENOENT")
205
- return remember(sessionId, { state: "clean", record: null });
206
- }
207
- return remember(sessionId, { state: "invalid", record: null });
208
- }
209
- function clearLkgFenceProcessState(sessionId) {
210
- if (sessionId)
211
- knownFences.delete(cacheKey(sessionId));
212
- else
213
- knownFences.clear();
214
- }
215
- var durableFencesSupported = () => platform !== "win32";
216
- var hash = (value) => createHash2("sha256").update(value).digest("hex");
217
- var lkgServedArrayDigest = hash;
218
- function lkgRecoveryFencePath(sessionId) {
219
- return join2(getMagicContextStorageDir(), "lkg-recovery-fences", `${hash(sessionId)}.json`);
220
- }
221
- var lockSweepDirectory;
222
- function closeLkgLockSweepDirectory() {
223
- lockSweepDirectory?.dir.closeSync();
224
- lockSweepDirectory = undefined;
225
- }
226
- function takeLkgRecoveryLockSweepPage(maxEntries = 200) {
227
- const path = join2(getMagicContextStorageDir(), "lkg-recovery-fences");
228
- if (lockSweepDirectory?.path !== path)
229
- closeLkgLockSweepDirectory();
230
- const names = [];
231
- try {
232
- lockSweepDirectory ??= { path, dir: opendirSync(path) };
233
- for (let i = 0;i < Math.min(200, Math.max(1, maxEntries)); i++) {
234
- const entry = lockSweepDirectory.dir.readSync();
235
- if (!entry) {
236
- closeLkgLockSweepDirectory();
237
- break;
238
- }
239
- if (entry.isDirectory() && /^[a-f0-9]{64}\.json\.locks$/.test(entry.name))
240
- names.push(entry.name);
241
- }
242
- } catch {
243
- closeLkgLockSweepDirectory();
244
- }
245
- return names;
246
- }
247
- function sweepEmptyLkgRecoveryLockDirectories(names, retainedSessionIds) {
248
- if (!durableFencesSupported())
249
- return 0;
250
- const retainedHashes = new Set(Array.from(retainedSessionIds, hash));
251
- const root = join2(getMagicContextStorageDir(), "lkg-recovery-fences");
252
- let removed = 0;
253
- for (const name of names.slice(0, 200)) {
254
- if (!/^[a-f0-9]{64}\.json\.locks$/.test(name) || retainedHashes.has(name.slice(0, 64)))
255
- continue;
256
- const path = join2(root, name);
257
- try {
258
- try {
259
- lstatSync(path.slice(0, -".locks".length));
260
- continue;
261
- } catch (error) {
262
- if (error.code !== "ENOENT")
263
- continue;
264
- }
265
- if (!lstatSync(path).isDirectory())
266
- continue;
267
- rmdirSync(path);
268
- removed++;
269
- } catch {}
270
- }
271
- return removed;
272
- }
273
- function metadata(slot) {
274
- return [
275
- slot.inputIdSeq,
276
- slot.inputContentDigests,
277
- slot.inputContentSignatures ?? null,
278
- slot.piOutputEntryIds ?? null,
279
- slot.lastInputMessageId,
280
- slot.modelKey,
281
- slot.providerKey
282
- ];
283
- }
284
- function representationKey(slot) {
285
- return createHash2("sha256").update(slot.jsonPrefix).update(JSON.stringify(metadata(slot))).digest("hex");
286
- }
287
- function lkgRecoverySlotKey(slot) {
288
- return hash(JSON.stringify([
289
- representationKey(slot),
290
- slot.capturedAt,
291
- slot.rowVersion ?? null,
292
- slot.captureSequence ?? null
293
- ]));
294
- }
295
- function payload(record) {
296
- return [
297
- record.version,
298
- record.sessionId,
299
- record.host,
300
- record.modelKey,
301
- record.providerKey,
302
- record.slotKey,
303
- record.servedDigest,
304
- record.revision,
305
- record.state
306
- ];
307
- }
308
- function publishRecord(record) {
309
- ioObserver?.("write");
310
- try {
311
- if (record.state === "clean")
312
- observer?.("clean");
313
- writeStorageFileDurableAtomicSync(lkgRecoveryFencePath(record.sessionId), JSON.stringify({ ...record, checksum: hash(JSON.stringify(payload(record))) }), () => observer?.("temp-synced"));
314
- if (record.state === "dirty")
315
- observer?.("dirty");
316
- remember(record.sessionId, {
317
- state: record.state,
318
- record: { ...record, checksum: hash(JSON.stringify(payload(record))) }
319
- });
320
- } catch (error) {
321
- const visible = read(record.sessionId);
322
- if (record.state === "clean" && visible?.state === "clean" && visible.revision === record.revision && visible.slotKey === record.slotKey) {
323
- const dirty = { ...record, state: "dirty" };
324
- writeStorageFileDurableAtomicSync(lkgRecoveryFencePath(record.sessionId), JSON.stringify({ ...dirty, checksum: hash(JSON.stringify(payload(dirty))) }));
325
- }
326
- remember(record.sessionId, { state: "invalid", record: null });
327
- throw error;
328
- }
329
- }
330
- function read(sessionId) {
331
- ioObserver?.("read");
332
- try {
333
- const path = lkgRecoveryFencePath(sessionId);
334
- const stat = lstatSync(path);
335
- const directory = lstatSync(dirname(path));
336
- if (!stat.isFile() || stat.size > 4096 || !directory.isDirectory() || directory.isSymbolicLink() || process.platform !== "win32" && ((stat.mode & 63) !== 0 || (directory.mode & 63) !== 0 || process.getuid && stat.uid !== process.getuid()))
337
- return null;
338
- const value = JSON.parse(readFileSync2(path, "utf8"));
339
- if (value.version !== 1 || value.sessionId !== sessionId || value.host !== getHarness2() || value.modelKey !== null && typeof value.modelKey !== "string" || value.providerKey !== null && typeof value.providerKey !== "string" || !/^[a-f0-9]{64}$/.test(value.slotKey) || !/^[a-f0-9]{64}$/.test(value.servedDigest) || typeof value.revision !== "string" || !["clean", "dirty"].includes(value.state) || value.checksum !== hash(JSON.stringify(payload(value))))
340
- return null;
341
- return value;
342
- } catch {
343
- return null;
344
- }
345
- }
346
- function publish(sessionId, slot, state, revision) {
347
- const record = {
348
- version: 1,
349
- sessionId,
350
- host: getHarness2(),
351
- modelKey: slot.modelKey,
352
- providerKey: slot.providerKey,
353
- slotKey: lkgRecoverySlotKey(slot),
354
- servedDigest: hash(slot.jsonPrefix),
355
- revision,
356
- state
357
- };
358
- publishRecord(record);
359
- }
360
- function locked(sessionId, work) {
361
- ioObserver?.("mutex");
362
- return withStorageFileMutex(`${lkgRecoveryFencePath(sessionId)}.locks`, () => {
363
- const path = lkgRecoveryFencePath(sessionId);
364
- for (const name of readdirSync2(dirname(path))) {
365
- if (name.startsWith(`${basename(path)}.`) && name.endsWith(".tmp"))
366
- rmSync2(join2(dirname(path), name), { force: true });
367
- }
368
- return work();
369
- });
370
- }
371
- function hasCleanLkgRecoveryFence(sessionId, _slot) {
372
- if (!durableFencesSupported())
373
- return false;
374
- const state = known(sessionId);
375
- return state.state === "clean";
376
- }
377
- function lkgFenceRequiresRebuild(sessionId, slot, processLocalProof) {
378
- return !processLocalProof && (durableFencesSupported() ? !hasCleanLkgRecoveryFence(sessionId, slot) : true);
379
- }
380
- function lkgRecoveryFenceUnclean(sessionId) {
381
- return durableFencesSupported() && known(sessionId).state !== "clean";
382
- }
383
- function dirtyLkgRecoveryRevision(sessionId, _slot) {
384
- if (!durableFencesSupported())
385
- return;
386
- const record = known(sessionId).record;
387
- return record?.state === "dirty" ? record.revision : undefined;
388
- }
389
- function ownsLkgDirtyFence(sessionId, revision) {
390
- if (!durableFencesSupported())
391
- return revision === "process-local";
392
- const record = known(sessionId).record;
393
- return revision !== undefined && record?.state === "dirty" && record.revision === revision;
394
- }
395
- function invalidateCleanLkgRecoveryFence(sessionId, load, ownRevision) {
396
- if (!durableFencesSupported())
397
- return "process-local";
398
- const prior = known(sessionId);
399
- if (prior.state === "dirty")
400
- return prior.record?.revision === ownRevision ? ownRevision : undefined;
401
- return locked(sessionId, () => {
402
- const slot = load() ?? {
403
- jsonPrefix: "",
404
- inputIdSeq: [],
405
- inputContentDigests: [],
406
- lastInputMessageId: "",
407
- modelKey: null,
408
- providerKey: null,
409
- capturedAt: 0
410
- };
411
- const record = read(sessionId);
412
- if (record?.state !== "dirty") {
413
- const revision = randomUUID2();
414
- publish(sessionId, slot, "dirty", revision);
415
- return revision;
416
- }
417
- return record?.state === "dirty" && record.revision === ownRevision ? ownRevision : undefined;
418
- });
419
- }
420
- function prepareLkgCaptureFence(args) {
421
- if (!durableFencesSupported())
422
- return "process-local";
423
- return locked(args.sessionId, () => {
424
- const before = read(args.sessionId);
425
- const owns = before?.state === "dirty" && before.revision === args.ownDirtyRevision;
426
- const unchangedBytes = before?.state === "clean" && before.servedDigest === hash(args.slot.jsonPrefix);
427
- if (!args.rebuild && before && !owns && !unchangedBytes && before.state !== "clean")
428
- throw new LkgRecoveryFenceError(`A concurrent recovery requires a declared prefix rebuild (state=${before?.state}, owned=${owns}, previous=${!!args.previousSlot}, bytes=${unchangedBytes})`);
429
- const revision = randomUUID2();
430
- if (before)
431
- publishRecord({ ...before, state: "dirty", revision });
432
- else
433
- publish(args.sessionId, args.previousSlot ?? args.slot, "dirty", revision);
434
- return revision;
435
- });
436
- }
437
- function certifyCommittedLkgCapture(args) {
438
- if (!durableFencesSupported())
439
- return false;
440
- const state = known(args.sessionId);
441
- if (state.state !== "dirty" || state.record?.revision !== args.revision)
442
- return false;
443
- return locked(args.sessionId, () => {
444
- const before = read(args.sessionId);
445
- if (before?.state !== "dirty" || before.revision !== args.revision)
446
- return false;
447
- const saved = args.load();
448
- if (!saved || representationKey(saved) !== representationKey(args.slot) || hash(saved.jsonPrefix) !== args.latestServedDigest)
449
- return false;
450
- publish(args.sessionId, saved, "clean", args.revision);
451
- return true;
452
- });
453
- }
454
- function markLkgRecoveryDirty(args) {
455
- if (!durableFencesSupported()) {
456
- if (!args.processLocalProof)
457
- throw new LkgRecoveryFenceError("Restart replay requires a healthy rebuild on Windows");
458
- return "process-local";
459
- }
460
- const state = known(args.sessionId);
461
- if (state.state === "dirty" && args.processLocalProof && state.record && state.record.revision === args.ownDirtyRevision)
462
- return state.record.revision;
463
- if (state.state === "invalid")
464
- throw new LkgRecoveryFenceError("No clean durable recovery certificate");
465
- return locked(args.sessionId, () => {
466
- const slot = args.slot;
467
- const before = read(args.sessionId);
468
- if (before?.state === "dirty") {
469
- if (!args.processLocalProof || before.revision !== args.ownDirtyRevision)
470
- throw new LkgRecoveryFenceError(`Recovery superseded this process's last-good request (local=${args.processLocalProof}, owned=${before.revision === args.ownDirtyRevision})`);
471
- return before.revision;
472
- }
473
- if (before && before.state !== "clean")
474
- throw new LkgRecoveryFenceError("No clean durable recovery certificate");
475
- const revision = randomUUID2();
476
- publish(args.sessionId, slot, "dirty", revision);
477
- return revision;
478
- });
479
- }
480
- function commitLkgWithRecoveryFence(args) {
481
- if (!durableFencesSupported())
482
- return args.commit();
483
- const state = known(args.sessionId);
484
- if (state.state === "clean" && !args.requiredRevision && !args.retainDirtyRevision) {
485
- const committed = args.commit();
486
- if (committed) {
487
- observer?.("committed");
488
- remember(args.sessionId, { state: "clean", record: null });
489
- }
490
- return committed;
491
- }
492
- if (state.state === "invalid" && !args.rebuild)
493
- throw new LkgRecoveryFenceError("Unproven prior request requires a prefix rebuild");
494
- return locked(args.sessionId, () => {
495
- const before = read(args.sessionId);
496
- if (args.requiredRevision && before?.revision !== args.requiredRevision)
497
- throw new LkgRecoveryFenceError("A later publisher superseded the rollback snapshot");
498
- const ownsDirty = before?.state === "dirty" && (before.revision === args.retainDirtyRevision || before.revision === args.ownDirtyRevision);
499
- if (before?.state === "dirty" && !ownsDirty && !args.rebuild)
500
- throw new LkgRecoveryFenceError("A concurrent recovery requires a declared prefix rebuild");
501
- if (before?.state !== "dirty" && state.state === "invalid")
502
- publish(args.sessionId, args.load() ?? args.slot, "dirty", randomUUID2());
503
- if (!args.commit())
504
- return false;
505
- observer?.("committed");
506
- const saved = args.load();
507
- if (!saved || representationKey(saved) !== representationKey(args.slot))
508
- throw new LkgRecoveryFenceError("Last-good commit did not match its certificate");
509
- if (args.retainDirtyRevision || args.cleanDigest !== undefined && hash(saved.jsonPrefix) !== args.cleanDigest)
510
- return true;
511
- const dirty = read(args.sessionId);
512
- if (dirty?.state === "dirty")
513
- publish(args.sessionId, saved, "clean", dirty.revision);
514
- else
515
- remember(args.sessionId, { state: "clean", record: null });
516
- return true;
517
- });
518
- }
519
- function clearLkgRecoveryFence(sessionId, remove, shouldClear) {
520
- if (!durableFencesSupported())
521
- return remove?.();
522
- const path = lkgRecoveryFencePath(sessionId);
523
- const missing = (file) => {
524
- try {
525
- lstatSync(file);
526
- return false;
527
- } catch (error) {
528
- return error.code === "ENOENT";
529
- }
530
- };
531
- if (!remove && !shouldClear && missing(path) && missing(`${path}.locks`)) {
532
- clearLkgFenceProcessState(sessionId);
533
- return;
534
- }
535
- try {
536
- return locked(sessionId, () => {
537
- const result = remove?.();
538
- if (shouldClear && !shouldClear(result))
539
- return result;
540
- rmSync2(lkgRecoveryFencePath(sessionId), { force: true });
541
- syncStorageDirectorySync(dirname(lkgRecoveryFencePath(sessionId)));
542
- clearLkgFenceProcessState(sessionId);
543
- return result;
544
- });
545
- } finally {
546
- try {
547
- rmdirSync(`${path}.locks`);
548
- } catch {}
549
- }
550
- }
551
-
552
- // src/v2/store-reader.ts
553
- import { createHash as createHash3 } from "node:crypto";
554
- import { statSync } from "node:fs";
555
- import { hostname } from "node:os";
556
- import { resolve } from "node:path";
557
- function gaDatabasePath(dataHome, channel = "latest", env = process.env) {
558
- return resolveOpenCodeDbPath("v2", { dataHome, channel, env }).path;
559
- }
560
- var RAW_MESSAGE_TYPES = [
561
- "user",
562
- "synthetic",
563
- "assistant",
564
- "skill",
565
- "shell",
566
- "system"
567
- ];
568
- var RAW_MESSAGE_TYPE_PARAMETERS = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
569
- var V2_MESSAGE_PAGE_SQL = `WITH bounds AS (
570
- SELECT
571
- CASE WHEN ? = 0 THEN -1 ELSE COALESCE((
572
- SELECT seq FROM session_message
573
- WHERE session_id = ? AND type IN (${RAW_MESSAGE_TYPE_PARAMETERS})
574
- ORDER BY seq ASC LIMIT 1 OFFSET ?
575
- ), (SELECT MAX(seq) FROM session_message WHERE session_id = ?), -1) END AS after_seq,
576
- COALESCE((
577
- SELECT seq FROM session_message
578
- WHERE session_id = ? AND type IN (${RAW_MESSAGE_TYPE_PARAMETERS})
579
- ORDER BY seq ASC LIMIT 1 OFFSET ?
580
- ), ?) AS watermark_seq
581
- )
582
- SELECT id, session_id, type, seq, time_created, data FROM session_message, bounds
583
- WHERE session_id = ?
584
- AND type IN (${RAW_MESSAGE_TYPE_PARAMETERS})
585
- AND seq > bounds.after_seq
586
- AND seq <= bounds.watermark_seq
587
- ORDER BY seq ASC LIMIT ?`;
588
- var V2_STORE_READER_DEBUG_COUNTER_KEY = "magic-context.v2.store-reader-debug";
589
- var debugSymbol = Symbol.for(V2_STORE_READER_DEBUG_COUNTER_KEY);
590
- var debugGlobal = globalThis;
591
- var QUERY_STATEMENTS = {
592
- messagePage: V2_MESSAGE_PAGE_SQL,
593
- messageCount: "SELECT COUNT(*) AS count FROM session_message WHERE session_id = ? AND type IN (?)",
594
- storedMessageCount: "SELECT COUNT(*) AS count FROM session_message WHERE session_id = ?",
595
- messageById: "SELECT id, session_id, type, seq, time_created, data FROM session_message WHERE session_id = ? AND id = ? AND type IN (?) LIMIT 1",
596
- messageExistsById: "SELECT 1 FROM session_message WHERE id = ? AND session_id = ? AND type IN (?) LIMIT 1",
597
- messageOrdinalById: `WITH target AS (
598
- SELECT seq FROM session_message WHERE session_id = ? AND id = ? AND type IN (?) LIMIT 1
599
- )
600
- SELECT (SELECT COUNT(*) FROM session_message WHERE session_id = ? AND type IN (?) AND seq <= target.seq) AS ordinal FROM target`,
601
- messageIdOrdinals: "SELECT id FROM session_message WHERE session_id = ? AND type IN (?) ORDER BY seq ASC LIMIT ? OFFSET ?",
602
- messageOrdinalPage: "SELECT id, type, seq, json_valid(data) AS valid FROM session_message WHERE session_id = ? AND seq > ? ORDER BY seq ASC, id ASC LIMIT ?",
603
- rawRowsThrough: "SELECT id, session_id, type, seq, time_created, data FROM session_message WHERE session_id = ? AND type IN (?) AND seq <= ? ORDER BY seq DESC LIMIT ?"
604
- };
605
- function debugCounters() {
606
- const counters = debugGlobal[debugSymbol] ??= {
607
- decodedRows: 0,
608
- operations: {},
609
- openReaders: 0,
610
- maxOpenReaders: 0,
611
- readersOpened: 0,
612
- readersClosed: 0
613
- };
614
- counters.openReaders ??= 0;
615
- counters.maxOpenReaders ??= 0;
616
- counters.readersOpened ??= 0;
617
- counters.readersClosed ??= 0;
618
- return counters;
619
- }
620
- function returnedRowCount(value) {
621
- if (Array.isArray(value))
622
- return value.length;
623
- if (value === null || value === undefined || value === false)
624
- return 0;
625
- return 1;
626
- }
627
- function trackDecodeOperation(name, operation) {
628
- const counters = debugCounters();
629
- const before = counters.decodedRows;
630
- const started = performance.now();
631
- let value;
632
- try {
633
- value = operation();
634
- return value;
635
- } finally {
636
- const decodedRows = counters.decodedRows - before;
637
- const current = counters.operations[name] ?? {
638
- calls: 0,
639
- decodedRows: 0,
640
- maxDecodedRows: 0
641
- };
642
- current.calls += 1;
643
- current.decodedRows += decodedRows;
644
- current.maxDecodedRows = Math.max(current.maxDecodedRows, decodedRows);
645
- counters.operations[name] = current;
646
- const statement = QUERY_STATEMENTS[name];
647
- if (counters.captureQueries && statement) {
648
- (counters.queries ??= []).push({
649
- operation: name,
650
- statement,
651
- rows: returnedRowCount(value),
652
- elapsedMs: performance.now() - started
653
- });
654
- }
655
- }
656
- }
657
- function decode(row) {
658
- debugCounters().decodedRows += 1;
659
- const data = JSON.parse(row.data);
660
- if (!data || typeof data !== "object" || Array.isArray(data)) {
661
- throw new Error(`Invalid session_message data at seq ${row.seq}`);
662
- }
663
- if (row.type === "idle" && !["succeeded", "failed", "interrupted"].includes(String(data.outcome))) {
664
- throw new Error(`Invalid idle outcome at seq ${row.seq}`);
665
- }
666
- return { ...row, data };
667
- }
668
- function connect(path, identity = "") {
669
- const stat = statSync(path);
670
- const acquiredIdentity = identity || `${stat.dev}:${stat.ino}:${stat.birthtimeMs}`;
671
- const db = new Database2(path, { readonly: true, fileMustExist: true });
672
- try {
673
- assertOpenCodeStoreGeneration(db, "v2", path);
674
- } catch (error) {
675
- db.close();
676
- throw error;
677
- }
678
- const counters = debugCounters();
679
- counters.openReaders += 1;
680
- counters.readersOpened += 1;
681
- counters.maxOpenReaders = Math.max(counters.maxOpenReaders, counters.openReaders);
682
- return { db, statements: new Map, identity: acquiredIdentity, leases: 0, retired: false };
683
- }
684
- function closeConnection(connection) {
685
- connection.statements.clear();
686
- try {
687
- connection.db.close();
688
- } finally {
689
- const counters = debugCounters();
690
- counters.openReaders = Math.max(0, counters.openReaders - 1);
691
- counters.readersClosed += 1;
692
- }
693
- }
694
-
695
- class V2StoreReaderPool {
696
- capacity;
697
- connections = new Map;
698
- closed = false;
699
- closing = false;
700
- holds = 0;
701
- idleWaiters = new Set;
702
- constructor(capacity = 4) {
703
- this.capacity = capacity;
704
- if (!Number.isSafeInteger(capacity) || capacity < 1)
705
- throw new Error("Invalid reader capacity");
706
- }
707
- open(path) {
708
- return new V2StoreReader(path, this);
709
- }
710
- acquire(path) {
711
- if (this.closed)
712
- throw new Error("Store reader pool is closed");
713
- const key = resolve(path);
714
- const stat = statSync(key);
715
- const identity = `${stat.dev}:${stat.ino}:${stat.birthtimeMs}`;
716
- let connection = this.connections.get(key);
717
- if (connection && connection.identity !== identity) {
718
- this.connections.delete(key);
719
- this.retire(connection);
720
- connection = undefined;
721
- }
722
- if (!connection)
723
- connection = connect(key, identity);
724
- connection.leases++;
725
- this.connections.delete(key);
726
- this.connections.set(key, connection);
727
- while (this.connections.size > this.capacity) {
728
- const oldest = this.connections.entries().next().value;
729
- if (!oldest)
730
- break;
731
- this.connections.delete(oldest[0]);
732
- this.retire(oldest[1]);
733
- }
734
- return connection;
735
- }
736
- release(connection) {
737
- connection.leases--;
738
- if (connection.retired && connection.leases === 0)
739
- closeConnection(connection);
740
- }
741
- retire(connection) {
742
- connection.retired = true;
743
- if (connection.leases === 0)
744
- closeConnection(connection);
745
- }
746
- hold() {
747
- if (this.closed || this.closing)
748
- throw new Error("Store reader pool is closed");
749
- this.holds++;
750
- let released = false;
751
- return () => {
752
- if (released)
753
- return;
754
- released = true;
755
- this.holds--;
756
- if (this.holds === 0 && this.closing)
757
- this.finishClose();
758
- };
759
- }
760
- isClosed() {
761
- return this.closed;
762
- }
763
- holdCount() {
764
- return this.holds;
765
- }
766
- close() {
767
- if (this.closed || this.closing)
768
- return;
769
- this.closing = true;
770
- if (this.holds === 0)
771
- this.finishClose();
772
- }
773
- async closeWhenIdle(timeoutMs) {
774
- this.close();
775
- if (this.closed)
776
- return true;
777
- let timer;
778
- let waiter;
779
- try {
780
- await new Promise((resolve) => {
781
- waiter = resolve;
782
- this.idleWaiters.add(resolve);
783
- timer = setTimeout(resolve, Math.max(0, timeoutMs));
784
- });
785
- } finally {
786
- if (timer)
787
- clearTimeout(timer);
788
- if (waiter)
789
- this.idleWaiters.delete(waiter);
790
- }
791
- return this.closed;
792
- }
793
- finishClose() {
794
- if (this.closed)
795
- return;
796
- this.closed = true;
797
- for (const connection of this.connections.values())
798
- this.retire(connection);
799
- this.connections.clear();
800
- for (const resolve of this.idleWaiters)
801
- resolve();
802
- this.idleWaiters.clear();
803
- }
804
- }
805
- function messageTimesQuery(count) {
806
- return `SELECT id, COALESCE(time_created, json_extract(data, '$.time.created')) AS created,
807
- json_extract(data, '$.time.completed') AS completed
808
- FROM session_message WHERE +session_id = ? AND id IN (${Array.from({ length: count }, () => "?").join(",")})`;
809
- }
810
-
811
- class V2StoreReader {
812
- path;
813
- pool;
814
- db;
815
- connection;
816
- closed = false;
817
- constructor(path, pool) {
818
- this.path = path;
819
- this.pool = pool;
820
- this.connection = pool ? pool.acquire(path) : connect(path);
821
- this.db = this.connection.db;
822
- }
823
- hostIdentity() {
824
- return createHash3("sha256").update(JSON.stringify([hostname(), resolve(this.path), this.connection.identity])).digest("hex");
825
- }
826
- prepare(sql) {
827
- if (this.closed)
828
- throw new Error("Store reader is closed");
829
- const statements = this.connection.statements;
830
- let statement = statements.get(sql);
831
- if (!statement) {
832
- statement = this.db.prepare(sql);
833
- if (statements.size >= 128)
834
- statements.clear();
835
- statements.set(sql, statement);
836
- }
837
- return statement;
838
- }
839
- close() {
840
- if (this.closed)
841
- return;
842
- this.closed = true;
843
- if (this.pool)
844
- this.pool.release(this.connection);
845
- else
846
- closeConnection(this.connection);
847
- }
848
- page(sessionID, options = {}) {
849
- return trackDecodeOperation("page", () => {
850
- const limit = options.limit ?? 100;
851
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e4)
852
- throw new Error("Invalid page limit");
853
- const after = options.after ?? -1;
854
- if (!Number.isSafeInteger(after))
855
- throw new Error("Invalid seq cursor");
856
- if (options.through !== undefined && !Number.isSafeInteger(options.through))
857
- throw new Error("Invalid seq upper bound");
858
- const predicates = ["session_id = ?", "seq > ?"];
859
- const parameters = [sessionID, after];
860
- if (options.through !== undefined) {
861
- predicates.push("seq <= ?");
862
- parameters.push(options.through);
863
- }
864
- if (options.type) {
865
- predicates.push("type = ?");
866
- parameters.push(options.type);
867
- }
868
- const rows = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
869
- WHERE ${predicates.join(" AND ")}
870
- ORDER BY seq ASC LIMIT ?`).all(...parameters, limit).map(decode);
871
- return { rows, cursor: rows.at(-1)?.seq };
872
- });
873
- }
874
- messagePage(sessionID, afterOrdinal, limit, finalWatermark, after) {
875
- return trackDecodeOperation("messagePage", () => {
876
- if (!Number.isSafeInteger(afterOrdinal) || afterOrdinal < 0)
877
- throw new Error("Invalid raw-message ordinal cursor");
878
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e4)
879
- throw new Error("Invalid page limit");
880
- if (!Number.isSafeInteger(finalWatermark) || finalWatermark < 0)
881
- throw new Error("Invalid raw-message watermark");
882
- const pageSize = Math.min(limit, finalWatermark - afterOrdinal);
883
- if (pageSize <= 0)
884
- return [];
885
- if (after) {
886
- const anchor = this.prepare("SELECT seq FROM session_message WHERE session_id = ? AND id = ?").get(sessionID, after.id);
887
- if (anchor) {
888
- return this.prepare(`SELECT id, session_id, type, seq, time_created, data
889
- FROM session_message WHERE session_id = ? AND seq > ?
890
- AND type IN (${RAW_MESSAGE_TYPE_PARAMETERS}) ORDER BY seq ASC LIMIT ?`).all(sessionID, anchor.seq, ...RAW_MESSAGE_TYPES, pageSize).map(decode);
891
- }
892
- }
893
- const maximumSeq = Number.MAX_SAFE_INTEGER;
894
- const rows = this.prepare(V2_MESSAGE_PAGE_SQL).all(afterOrdinal, sessionID, ...RAW_MESSAGE_TYPES, Math.max(0, afterOrdinal - 1), sessionID, sessionID, ...RAW_MESSAGE_TYPES, finalWatermark - 1, maximumSeq, sessionID, ...RAW_MESSAGE_TYPES, pageSize);
895
- return rows.map(decode);
896
- });
897
- }
898
- messageCount(sessionID) {
899
- return trackDecodeOperation("messageCount", () => {
900
- const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
901
- const row = this.prepare(`SELECT COUNT(*) AS count FROM session_message
902
- WHERE session_id = ? AND type IN (${rawTypes})`).get(sessionID, ...RAW_MESSAGE_TYPES);
903
- return typeof row?.count === "number" ? row.count : 0;
904
- });
905
- }
906
- messageCountAfterSequence(sessionID, after, through) {
907
- const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
908
- const row = this.prepare(`SELECT COUNT(*) AS count FROM session_message
909
- WHERE session_id = ? AND seq > ? AND seq <= ? AND type IN (${rawTypes})`).get(sessionID, after, through, ...RAW_MESSAGE_TYPES);
910
- return typeof row?.count === "number" ? row.count : 0;
911
- }
912
- sessionDirectoryPage(afterSessionID, limit) {
913
- const rows = this.prepare(`SELECT id, COALESCE(directory, '') AS directory FROM session_v2
914
- WHERE id > ? ORDER BY id ASC LIMIT ?`).all(afterSessionID ?? "", limit);
915
- return rows.map((row) => ({ sessionId: row.id, directory: row.directory }));
916
- }
917
- latestMessageTime(sessionID) {
918
- const row = this.prepare("SELECT MAX(time_created) AS time FROM session_message WHERE session_id = ?").get(sessionID);
919
- return row?.time ?? undefined;
920
- }
921
- isSubagent(sessionID) {
922
- const row = this.prepare("SELECT parent_id FROM session_v2 WHERE id = ?").get(sessionID);
923
- if (!row)
924
- return null;
925
- return typeof row.parent_id === "string" && row.parent_id.length > 0;
926
- }
927
- sessionIds() {
928
- return this.prepare("SELECT id FROM session_v2").all().map((row) => row.id);
929
- }
930
- latestRootSessionInDirectory(directory) {
931
- const columns = new Set(this.prepare("PRAGMA table_info(session_v2)").all().map((column) => column.name));
932
- const archived = columns.has("time_archived") ? " AND time_archived IS NULL" : "";
933
- const order = columns.has("time_updated") ? "time_updated DESC, id DESC" : "id DESC";
934
- const page = this.prepare(`SELECT id, COALESCE(directory, '') AS directory FROM session_v2
935
- WHERE parent_id IS NULL${archived}
936
- AND COALESCE(json_extract(metadata, '$.magic_context'), '') <> 'hidden-run'
937
- ORDER BY ${order} LIMIT ? OFFSET ?`);
938
- const pageSize = 500;
939
- for (let offset = 0;; offset += pageSize) {
940
- const rows = page.all(pageSize, offset);
941
- const match = rows.find((row) => sameProjectDirectory(row.directory, directory));
942
- if (match)
943
- return match.id;
944
- if (rows.length < pageSize)
945
- return;
946
- }
947
- }
948
- rootSessionActivity(sessionIDs) {
949
- const roots = new Map;
950
- for (let offset = 0;offset < sessionIDs.length; offset += 500) {
951
- const chunk = sessionIDs.slice(offset, offset + 500);
952
- const rows = this.prepare(`SELECT id, (
953
- SELECT MAX(time_created) FROM session_message
954
- WHERE session_id = session_v2.id AND type = 'user'
955
- ) AS time FROM session_v2
956
- WHERE parent_id IS NULL
957
- AND COALESCE(json_extract(metadata, '$.magic_context'), '') <> 'hidden-run'
958
- AND id IN (${chunk.map(() => "?").join(",")})`).all(...chunk);
959
- for (const row of rows)
960
- if (row.time !== null)
961
- roots.set(row.id, row.time);
962
- }
963
- return roots;
964
- }
965
- oldestUserMessageTimesSince(sessionIDs, sinceMs) {
966
- const result = new Map;
967
- for (let offset = 0;offset < sessionIDs.length; offset += 500) {
968
- const chunk = sessionIDs.slice(offset, offset + 500);
969
- const rows = this.prepare(`SELECT session_id, MIN(time_created) AS time FROM session_message
970
- WHERE type = 'user' AND time_created > ? AND session_id IN (${chunk.map(() => "?").join(",")})
971
- GROUP BY session_id`).all(sinceMs, ...chunk);
972
- for (const row of rows)
973
- result.set(row.session_id, row.time);
974
- }
975
- return result;
976
- }
977
- retrospectiveUserPage(sessionID, options) {
978
- const { boundaryMs, before, limit, maxChars, truncationMarker } = options;
979
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e4)
980
- throw new Error("Invalid page limit");
981
- if (!Number.isSafeInteger(maxChars) || maxChars <= truncationMarker.length)
982
- throw new Error("Invalid text limit");
983
- const direction = before ? "DESC" : "ASC";
984
- const head = Math.floor((maxChars - truncationMarker.length) / 2);
985
- const tail = maxChars - truncationMarker.length - head;
986
- return this.prepare(`WITH users AS (
987
- SELECT session_id, seq, time_created, data FROM session_message
988
- WHERE session_id = ? AND type = 'user' AND time_created ${before ? "<=" : ">"}
989
- ?
990
- ORDER BY time_created ${direction}, seq ${direction} LIMIT ?
991
- ), texts AS (
992
- SELECT session_id, seq, time_created, CASE
993
- WHEN json_extract(data, '$.synthetic') = 1 OR json_extract(data, '$.ignored') = 1 THEN ''
994
- WHEN json_type(data, '$.text') = 'text' THEN json_extract(data, '$.text')
995
- ELSE COALESCE((SELECT group_concat(json_extract(value, '$.text'), char(10))
996
- FROM json_each(users.data, '$.content')
997
- WHERE json_extract(value, '$.type') IN ('text', 'input_text')
998
- AND COALESCE(json_extract(value, '$.synthetic'), 0) <> 1
999
- AND COALESCE(json_extract(value, '$.ignored'), 0) <> 1), '') END AS text
1000
- FROM users WHERE json_valid(data)
1001
- ) SELECT seq, time_created, CASE WHEN length(text) > ?
1002
- THEN substr(text, 1, ?) || ? || substr(text, -?) ELSE text END AS text,
1003
- EXISTS (
1004
- SELECT 1 FROM session_message assistant_reply
1005
- WHERE assistant_reply.session_id = texts.session_id
1006
- AND assistant_reply.type = 'assistant'
1007
- AND assistant_reply.seq < texts.seq
1008
- AND EXISTS (
1009
- SELECT 1 FROM session_message prior_user
1010
- WHERE prior_user.session_id = texts.session_id
1011
- AND prior_user.type = 'user'
1012
- AND prior_user.seq < assistant_reply.seq
1013
- )
1014
- ) AS follows_assistant_reply
1015
- FROM texts ORDER BY time_created ${direction}, seq ${direction}`).all(sessionID, boundaryMs, limit, maxChars, head, truncationMarker, tail);
1016
- }
1017
- storedRowsAfter(sessionID, after) {
1018
- const row = this.prepare("SELECT COUNT(*) AS count FROM session_message WHERE session_id = ? AND seq > ?").get(sessionID, after);
1019
- return row.count;
1020
- }
1021
- storedMessageCount(sessionID) {
1022
- return trackDecodeOperation("storedMessageCount", () => {
1023
- const row = this.prepare("SELECT COUNT(*) AS count FROM session_message WHERE session_id = ?").get(sessionID);
1024
- return typeof row?.count === "number" ? row.count : 0;
1025
- });
1026
- }
1027
- messageById(sessionID, id) {
1028
- return trackDecodeOperation("messageById", () => {
1029
- const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
1030
- const row = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
1031
- WHERE session_id = ? AND id = ? AND type IN (${rawTypes}) LIMIT 1`).get(sessionID, id, ...RAW_MESSAGE_TYPES);
1032
- return row ? decode(row) : null;
1033
- });
1034
- }
1035
- messageTimes(sessionID, ids) {
1036
- const times = new Map;
1037
- for (let offset = 0;offset < ids.length; offset += 500) {
1038
- const batch = ids.slice(offset, offset + 500);
1039
- const rows = this.prepare(messageTimesQuery(batch.length)).all(sessionID, ...batch);
1040
- for (const row of rows)
1041
- times.set(row.id, {
1042
- created: typeof row.created === "number" ? row.created : undefined,
1043
- completed: typeof row.completed === "number" ? row.completed : undefined
1044
- });
1045
- }
1046
- return times;
1047
- }
1048
- messageExistsById(sessionID, id) {
1049
- return trackDecodeOperation("messageExistsById", () => {
1050
- const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
1051
- const row = this.prepare(`SELECT 1 FROM session_message
1052
- WHERE id = ? AND session_id = ? AND type IN (${rawTypes}) LIMIT 1`).get(id, sessionID, ...RAW_MESSAGE_TYPES);
1053
- return row != null;
1054
- });
1055
- }
1056
- messageOrdinalById(sessionID, id) {
1057
- return trackDecodeOperation("messageOrdinalById", () => {
1058
- const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
1059
- const row = this.prepare(`WITH target AS (
1060
- SELECT seq FROM session_message
1061
- WHERE session_id = ? AND id = ? AND type IN (${rawTypes})
1062
- LIMIT 1
1063
- )
1064
- SELECT (
1065
- SELECT COUNT(*) FROM session_message
1066
- WHERE session_id = ?
1067
- AND type IN (${rawTypes})
1068
- AND seq <= target.seq
1069
- ) AS ordinal
1070
- FROM target`).get(sessionID, id, ...RAW_MESSAGE_TYPES, sessionID, ...RAW_MESSAGE_TYPES);
1071
- return typeof row?.ordinal === "number" ? row.ordinal : null;
1072
- });
1073
- }
1074
- messageIdOrdinals(sessionID, fromOrdinal, toOrdinal) {
1075
- return trackDecodeOperation("messageIdOrdinals", () => {
1076
- if (!Number.isSafeInteger(fromOrdinal) || fromOrdinal < 1)
1077
- throw new Error("Invalid raw-message range start");
1078
- if (!Number.isSafeInteger(toOrdinal) || toOrdinal < fromOrdinal)
1079
- throw new Error("Invalid raw-message range end");
1080
- const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
1081
- const rows = this.prepare(`SELECT id FROM session_message
1082
- WHERE session_id = ? AND type IN (${rawTypes})
1083
- ORDER BY seq ASC LIMIT ? OFFSET ?`).all(sessionID, ...RAW_MESSAGE_TYPES, toOrdinal - fromOrdinal + 1, fromOrdinal - 1);
1084
- return new Map(rows.map((row, index) => [row.id, fromOrdinal + index]));
1085
- });
1086
- }
1087
- messageOrdinalPage(sessionID, after, limit) {
1088
- return trackDecodeOperation("messageOrdinalPage", () => {
1089
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e4)
1090
- throw new Error("Invalid page limit");
1091
- const rows = after ? this.prepare(`SELECT id, type, seq, json_valid(data) AS valid
1092
- FROM session_message
1093
- WHERE session_id = ? AND (seq > ? OR (seq = ? AND id > ?))
1094
- ORDER BY seq ASC, id ASC LIMIT ?`).all(sessionID, after.timeCreated, after.timeCreated, after.id, limit) : this.prepare(`SELECT id, type, seq, json_valid(data) AS valid
1095
- FROM session_message
1096
- WHERE session_id = ?
1097
- ORDER BY seq ASC, id ASC LIMIT ?`).all(sessionID, limit);
1098
- const rawTypes = new Set(RAW_MESSAGE_TYPES);
1099
- return rows.map((row) => ({
1100
- id: row.id,
1101
- timeCreated: row.seq,
1102
- contributesOrdinal: rawTypes.has(row.type),
1103
- hasValidInfo: row.valid === 1
1104
- }));
1105
- });
1106
- }
1107
- range(sessionID, after, through) {
1108
- return trackDecodeOperation("range", () => through <= after ? [] : this.all(sessionID, after, undefined, through));
1109
- }
1110
- spanRowStamps(sessionID, after, through) {
1111
- if (!Number.isSafeInteger(after) || !Number.isSafeInteger(through))
1112
- throw new Error("Invalid seq span");
1113
- const rows = this.prepare(`SELECT seq, id, time_updated AS updated, length(CAST(data AS BLOB)) AS bytes
1114
- FROM session_message WHERE session_id = ? AND seq > ? AND seq <= ?
1115
- ORDER BY seq ASC`).all(sessionID, after, through);
1116
- const stamps = new Map;
1117
- for (const row of rows)
1118
- stamps.set(row.seq, `${row.id}\x00${row.updated ?? ""}\x00${row.bytes ?? ""}`);
1119
- return stamps;
1120
- }
1121
- rawRowsThrough(sessionID, throughSeq, limit) {
1122
- return trackDecodeOperation("rawRowsThrough", () => {
1123
- if (!Number.isSafeInteger(throughSeq))
1124
- throw new Error("Invalid seq upper bound");
1125
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e4)
1126
- throw new Error("Invalid page limit");
1127
- const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
1128
- return this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
1129
- WHERE session_id = ? AND type IN (${rawTypes}) AND seq <= ?
1130
- ORDER BY seq DESC LIMIT ?`).all(sessionID, ...RAW_MESSAGE_TYPES, throughSeq, limit).map(decode);
1131
- });
1132
- }
1133
- instructionRowsThrough(sessionID, throughSeq) {
1134
- return trackDecodeOperation("instructionRowsThrough", () => {
1135
- if (!Number.isSafeInteger(throughSeq))
1136
- throw new Error("Invalid seq upper bound");
1137
- return this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
1138
- WHERE session_id = ? AND type = 'system' AND seq <= ?
1139
- ORDER BY seq ASC`).all(sessionID, throughSeq).map(decode);
1140
- });
1141
- }
1142
- sequenceForId(sessionID, id) {
1143
- if (!id)
1144
- return;
1145
- const row = this.prepare("SELECT seq FROM session_message WHERE session_id = ? AND id = ? LIMIT 1").get(sessionID, id);
1146
- return typeof row?.seq === "number" ? row.seq : undefined;
1147
- }
1148
- syntheticMessageIDs(sessionID) {
1149
- const rows = this.prepare("SELECT id FROM session_message WHERE session_id = ? AND type = 'synthetic'").all(sessionID);
1150
- return new Set(rows.map((row) => row.id));
1151
- }
1152
- forkOrigin(sessionID) {
1153
- const columns = new Set(this.prepare("PRAGMA table_info(session_v2)").all().map((column) => column.name));
1154
- if (!columns.has("fork_session_id") || !columns.has("fork_boundary"))
1155
- return null;
1156
- const row = this.prepare("SELECT fork_session_id, fork_boundary FROM session_v2 WHERE id = ?").get(sessionID);
1157
- if (!row?.fork_session_id || !row.fork_boundary)
1158
- return null;
1159
- try {
1160
- const boundary = JSON.parse(row.fork_boundary);
1161
- if (boundary.type !== "before" && boundary.type !== "through" || typeof boundary.messageID !== "string" || boundary.messageID.length === 0)
1162
- return null;
1163
- return {
1164
- parentSessionID: row.fork_session_id,
1165
- boundary: { type: boundary.type, messageID: boundary.messageID }
1166
- };
1167
- } catch {
1168
- return null;
1169
- }
1170
- }
1171
- sessionExists(sessionID) {
1172
- return this.prepare("SELECT 1 FROM session_v2 WHERE id = ?").get(sessionID) != null;
1173
- }
1174
- rowStampsThrough(sessionID, throughSeq) {
1175
- return this.prepare(`SELECT id, type, seq, time_created FROM session_message
1176
- WHERE session_id = ? AND seq <= ? ORDER BY seq ASC`).all(sessionID, throughSeq);
1177
- }
1178
- latestSequenceForIds(sessionID, ids) {
1179
- let latest = -1;
1180
- for (let offset = 0;offset < ids.length; offset += 500) {
1181
- const chunk = ids.slice(offset, offset + 500);
1182
- const row = this.prepare(`SELECT MAX(seq) AS seq FROM session_message
1183
- WHERE session_id = ? AND id IN (${chunk.map(() => "?").join(", ")})`).get(sessionID, ...chunk);
1184
- if (typeof row?.seq === "number")
1185
- latest = Math.max(latest, row.seq);
1186
- }
1187
- return latest;
1188
- }
1189
- compactionByStatus(sessionID, status) {
1190
- const row = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
1191
- WHERE session_id = ? AND type = 'compaction'
1192
- AND json_extract(data, '$.status') = ?
1193
- ORDER BY seq DESC LIMIT 1`).get(sessionID, status);
1194
- return row ? decode(row) : undefined;
1195
- }
1196
- latestCompaction(sessionID) {
1197
- return trackDecodeOperation("latestCompaction", () => this.compactionByStatus(sessionID, "completed"));
1198
- }
1199
- latestRunningCompaction(sessionID) {
1200
- return trackDecodeOperation("latestRunningCompaction", () => this.compactionByStatus(sessionID, "running"));
1201
- }
1202
- idleRows(sessionID, after = -1) {
1203
- return trackDecodeOperation("idleRows", () => this.all(sessionID, after, "idle"));
1204
- }
1205
- earliestSequence(sessionID) {
1206
- const row = this.prepare("SELECT MIN(seq) AS seq FROM session_message WHERE session_id = ?").get(sessionID);
1207
- return typeof row?.seq === "number" ? row.seq : undefined;
1208
- }
1209
- latestSequence(sessionID) {
1210
- const row = this.prepare("SELECT MAX(seq) AS seq FROM session_message WHERE session_id = ?").get(sessionID);
1211
- return typeof row?.seq === "number" ? row.seq : -1;
1212
- }
1213
- assistantSince(sessionID, afterSeq) {
1214
- return trackDecodeOperation("assistantSince", () => {
1215
- const rows = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
1216
- WHERE session_id = ? AND type = 'assistant' AND seq > ?
1217
- ORDER BY seq ASC`).all(sessionID, afterSeq);
1218
- return rows.map((row) => decode(row));
1219
- });
1220
- }
1221
- latestAssistant(sessionID) {
1222
- return trackDecodeOperation("latestAssistant", () => {
1223
- const row = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
1224
- WHERE session_id = ? AND type = 'assistant'
1225
- ORDER BY seq DESC LIMIT 1`).get(sessionID);
1226
- return row ? decode(row) : undefined;
1227
- });
1228
- }
1229
- latestIdle(sessionID) {
1230
- return trackDecodeOperation("latestIdle", () => {
1231
- const row = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
1232
- WHERE session_id = ? AND type = 'idle'
1233
- ORDER BY seq DESC LIMIT 1`).get(sessionID);
1234
- return row ? decode(row) : undefined;
1235
- });
1236
- }
1237
- window(sessionID) {
1238
- return trackDecodeOperation("window", () => this.db.transaction(() => {
1239
- const cut = this.latestCompaction(sessionID);
1240
- return this.all(sessionID, cut ? cut.seq - 1 : -1);
1241
- }).deferred());
1242
- }
1243
- history(sessionID) {
1244
- return trackDecodeOperation("history", () => this.all(sessionID, -1));
1245
- }
1246
- all(sessionID, after, type, through) {
1247
- const rows = [];
1248
- for (;; ) {
1249
- const page = this.page(sessionID, { after, through, type });
1250
- rows.push(...page.rows);
1251
- if (page.rows.length < 100 || page.cursor === undefined)
1252
- return rows;
1253
- after = page.cursor;
1254
- }
1255
- }
1256
- }
1257
-
1258
- // src/features/magic-context/message-index.ts
1259
- import { createHash as createHash4 } from "node:crypto";
1260
-
1261
- // src/shared/search-text.ts
1262
- function removeTaskNotifications(text) {
1263
- return removeNestedBlocks(text, ["task", "task_cohort"]);
1264
- }
1265
- function cleanSearchText(text) {
1266
- return removeNestedBlocks(text, ["system-reminder", "task", "task_cohort"]).replace(/\s+/g, " ").trim();
1267
- }
1268
- var QUERY_STOP_WORDS = new Set("a an and are as at be been by can could did do does for from how i in is it me my of on or our please should that the their them there these they this to us was we were what when where which who why will with would you your".split(" "));
1269
- function terms(text) {
1270
- return [
1271
- ...new Set((text.toLowerCase().match(/[\p{L}\p{N}_]+/gu) ?? []).filter((term) => !QUERY_STOP_WORDS.has(term)))
1272
- ];
1273
- }
1274
- function keywordRelevance(query, content) {
1275
- const needles = terms(query);
1276
- if (needles.length === 0)
1277
- return 0;
1278
- const words = new Set(terms(cleanSearchText(content)));
1279
- return 0.8 * needles.filter((term) => words.has(term)).length / needles.length;
1280
- }
1281
- function matchedSearchSnippet(content, query, cap = 220) {
1282
- const text = cleanSearchText(content);
1283
- const needles = new Set(terms(query));
1284
- const matches = [...text.matchAll(/[\p{L}\p{N}_]+/gu)].filter((match) => needles.has(match[0].toLowerCase()));
1285
- if (matches.length === 0)
1286
- return "";
1287
- let bestStart = matches[0].index;
1288
- let bestCoverage = -1;
1289
- for (const match of matches) {
1290
- const start = Math.max(0, match.index - Math.floor(cap / 5));
1291
- const coverage = keywordRelevance(query, text.slice(start, start + cap));
1292
- if (coverage > bestCoverage) {
1293
- bestStart = start;
1294
- bestCoverage = coverage;
1295
- }
1296
- }
1297
- if (bestStart > 0) {
1298
- const boundary = text.indexOf(" ", bestStart);
1299
- if (boundary >= 0 && boundary < matches[matches.length - 1].index)
1300
- bestStart = boundary + 1;
1301
- }
1302
- const passage = text.slice(bestStart, bestStart + cap).trim();
1303
- return `${bestStart > 0 ? "…" : ""}${passage}${bestStart + cap < text.length ? "…" : ""}`;
1304
- }
1305
-
1306
- // src/features/magic-context/compression-depth-storage.ts
1307
- var incrementDepthStatements = new WeakMap;
1308
- var totalDepthStatements = new WeakMap;
1309
- var maxDepthStatements = new WeakMap;
1310
- var clearDepthStatements = new WeakMap;
1311
- function getClearDepthStatement(db) {
1312
- let stmt = clearDepthStatements.get(db);
1313
- if (!stmt) {
1314
- stmt = db.prepare("DELETE FROM compression_depth WHERE session_id = ?");
1315
- clearDepthStatements.set(db, stmt);
1316
- }
1317
- return stmt;
1318
- }
1319
- function clearCompressionDepth(db, sessionId) {
1320
- getClearDepthStatement(db).run(sessionId);
1321
- }
1322
- function clearCompressionDepthRange(db, sessionId, startOrdinal, endOrdinal) {
1323
- if (endOrdinal < startOrdinal) {
1324
- return;
1325
- }
1326
- db.prepare("DELETE FROM compression_depth WHERE session_id = ? AND message_ordinal BETWEEN ? AND ?").run(sessionId, startOrdinal, endOrdinal);
1327
- }
1328
-
1329
- // src/features/magic-context/session-activity.ts
1330
- var PREFIX = "retrospective_activity:";
1331
- var pending = new WeakMap;
1332
- function sessionActivityKey(sessionId) {
1333
- return `${PREFIX}${sessionId}`;
1334
- }
1335
- function deleteSessionActivity(db, sessionIds) {
1336
- if (sessionIds.length === 0)
1337
- return;
1338
- const sessions = pending.get(db);
1339
- for (const sessionId of sessionIds) {
1340
- const state = sessions?.get(sessionId);
1341
- if (state?.timer)
1342
- clearTimeout(state.timer);
1343
- sessions?.delete(sessionId);
1344
- }
1345
- const placeholders = sessionIds.map(() => "?").join(", ");
1346
- db.prepare(`DELETE FROM schema_migrations_meta WHERE key IN (${placeholders})`).run(...sessionIds.map(sessionActivityKey));
1347
- deleteTagIdentityRevisions(db, sessionIds);
1348
- }
1349
- function advanceSessionActivity(db, sessionId, time) {
1350
- if (!sessionId || !Number.isFinite(time) || time <= 0)
1351
- return;
1352
- db.prepare(`INSERT INTO schema_migrations_meta (key, value) VALUES (?, ?)
1353
- ON CONFLICT(key) DO UPDATE SET value = CAST(MAX(CAST(value AS INTEGER), CAST(excluded.value AS INTEGER)) AS TEXT)`).run(sessionActivityKey(sessionId), String(Math.floor(time)));
1354
- }
1355
- async function backfillSessionActivity(db, harness, latestTime) {
1356
- const completedKey = `retrospective_activity_backfill:${harness}:v1`;
1357
- if (db.prepare("SELECT 1 FROM schema_migrations_meta WHERE key = ?").get(completedKey))
1358
- return;
1359
- let cursor = "";
1360
- while (true) {
1361
- const rows = db.prepare(`SELECT session_id FROM session_projects
1362
- WHERE harness = ? AND session_id > ? ORDER BY session_id LIMIT 100`).all(harness, cursor);
1363
- if (rows.length === 0)
1364
- break;
1365
- for (const row of rows) {
1366
- const time = await latestTime(row.session_id);
1367
- if (time !== undefined)
1368
- advanceSessionActivity(db, row.session_id, time);
1369
- }
1370
- cursor = rows[rows.length - 1].session_id;
1371
- await new Promise((resolve) => setTimeout(resolve, 0));
1372
- }
1373
- db.prepare("INSERT OR IGNORE INTO schema_migrations_meta (key, value) VALUES (?, 'completed')").run(completedKey);
1374
- }
1375
-
1376
- // src/features/magic-context/storage-session-tables.ts
1377
- var SESSION_SCOPED_TABLES = [
1378
- { table: "pending_ops", harnessScoped: true },
1379
- { table: "source_contents", harnessScoped: true },
1380
- { table: "tool_owner_backfill_state" },
1381
- { table: "tags", harnessScoped: true },
1382
- { table: "session_meta", harnessScoped: true },
1383
- {
1384
- table: "temporal_decisions",
1385
- extraPredicate: "NOT EXISTS (SELECT 1 FROM session_meta AS remaining WHERE remaining.session_id = temporal_decisions.session_id)"
1386
- },
1387
- {
1388
- table: "session_replay_decisions",
1389
- extraPredicate: "NOT EXISTS (SELECT 1 FROM session_meta AS remaining WHERE remaining.session_id = session_replay_decisions.session_id)"
1390
- },
1391
- { table: "session_projects", harnessScoped: true },
1392
- { table: "compartment_chunk_embeddings", harnessScoped: true },
1393
- { table: "compartments", harnessScoped: true },
1394
- {
1395
- table: "compartment_history_versions",
1396
- extraPredicate: "NOT EXISTS (SELECT 1 FROM compartments AS remaining WHERE remaining.session_id = compartment_history_versions.session_id)"
1397
- },
1398
- { table: "compression_depth", harnessScoped: true },
1399
- { table: "session_facts", harnessScoped: true },
1400
- { table: "compartment_state_lease" },
1401
- { table: "notes", harnessScoped: true, extraPredicate: "type = 'session'" },
1402
- { table: "recomp_compartments", harnessScoped: true },
1403
- { table: "recomp_facts", harnessScoped: true },
1404
- { table: "user_memory_candidates" },
1405
- { table: "primer_candidates", harnessScoped: true },
1406
- { table: "m0_mutation_log" },
1407
- { table: "compartment_events", harnessScoped: true },
1408
- { table: "subagent_invocations", harnessScoped: true },
1409
- { table: "historian_runs", harnessScoped: true },
1410
- { table: "plugin_messages" },
1411
- { table: "transform_decisions", harnessScoped: true },
1412
- { table: "synapse_batch_ledger" },
1413
- { table: "embedding_measurement_corpus" },
1414
- { table: "pending_session_cleanup", harnessScoped: true },
1415
- { table: "message_history_fts" },
1416
- { table: "message_fts_rowid_map" },
1417
- { table: "message_history_source", harnessScoped: true },
1418
- { table: "message_history_index", harnessScoped: true },
1419
- { table: "lkg_slots" },
1420
- { table: "lkg_slot_chunks" }
1421
- ];
1422
- var cleanupStatements = new WeakMap;
1423
- function cleanupStatement(db, sql) {
1424
- let cache = cleanupStatements.get(db);
1425
- if (!cache) {
1426
- cache = new Map;
1427
- cleanupStatements.set(db, cache);
1428
- }
1429
- let statement = cache.get(sql);
1430
- if (!statement) {
1431
- if (cache.size >= 256)
1432
- cache.clear();
1433
- statement = prepareCachedStatement2(db, sql);
1434
- cache.set(sql, statement);
1435
- }
1436
- return statement;
1437
- }
1438
- function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
1439
- if (sessionIds.length === 0)
1440
- return 0;
1441
- let deletableSessionIds = [...sessionIds];
1442
- if (options.rustModuleCleanupAcknowledged !== true) {
1443
- const placeholders = sessionIds.map(() => "?").join(", ");
1444
- const harnessPredicate = harness === undefined ? "" : " AND harness = ?";
1445
- const protectedRows = cleanupStatement(db, `SELECT session_id
1446
- FROM pending_session_cleanup
1447
- WHERE session_id IN (${placeholders})
1448
- AND harness LIKE '%:rust'${harnessPredicate}`).all(...sessionIds, ...harness === undefined ? [] : [`${harness}:rust`]);
1449
- const protectedSessionIds = new Set(protectedRows.map((row) => row.session_id));
1450
- deletableSessionIds = sessionIds.filter((sessionId) => !protectedSessionIds.has(sessionId));
1451
- }
1452
- if (deletableSessionIds.length === 0)
1453
- return 0;
1454
- const placeholders = deletableSessionIds.map(() => "?").join(", ");
1455
- const harnessTables = SESSION_SCOPED_TABLES.filter((definition) => definition.harnessScoped);
1456
- if (options.preserveTagRevisionTombstones) {
1457
- const owners = cleanupStatement(db, harnessTables.map((definition) => `SELECT session_id, harness FROM ${definition.table} WHERE session_id IN (${placeholders})${definition.extraPredicate ? ` AND ${definition.extraPredicate}` : ""}`).join(" UNION ")).all(...harnessTables.flatMap(() => deletableSessionIds));
1458
- const record = cleanupStatement(db, "INSERT OR IGNORE INTO schema_migrations_meta (key, value) VALUES (?, ?)");
1459
- for (const owner of owners)
1460
- record.run(tagTombstoneOwnerKey(owner.session_id, owner.harness), owner.harness);
1461
- const fallback = cleanupStatement(db, `INSERT INTO schema_migrations_meta (key, value)
1462
- SELECT ?, ? WHERE NOT EXISTS (SELECT 1 FROM schema_migrations_meta WHERE key >= ? AND key < ?)`);
1463
- for (const id of deletableSessionIds)
1464
- fallback.run(tagTombstoneOwnerKey(id, getHarness2()), getHarness2(), ...tagTombstoneOwnerRange(id));
1465
- }
1466
- for (const sessionId of deletableSessionIds)
1467
- deleteChunkEmbedBackoffForSession2(db, sessionId);
1468
- for (const definition of SESSION_SCOPED_TABLES) {
1469
- if (definition.table === "message_history_fts") {
1470
- cleanupStatement(db, `DELETE FROM message_history_fts
1471
- WHERE rowid IN (
1472
- SELECT fts_rowid
1473
- FROM message_fts_rowid_map
1474
- WHERE session_id IN (${placeholders})
1475
- )`).run(...deletableSessionIds);
1476
- deleteUnmappedMessageFtsRows2(db, deletableSessionIds);
1477
- continue;
1478
- }
1479
- const predicates = [`session_id IN (${placeholders})`];
1480
- if (definition.extraPredicate)
1481
- predicates.push(definition.extraPredicate);
1482
- const bindHarness = harness !== undefined && definition.harnessScoped === true;
1483
- if (bindHarness)
1484
- predicates.push("harness = ?");
1485
- const statement = cleanupStatement(db, `DELETE FROM ${definition.table} WHERE ${predicates.join(" AND ")}`);
1486
- if (bindHarness)
1487
- statement.run(...deletableSessionIds, harness);
1488
- else
1489
- statement.run(...deletableSessionIds);
1490
- }
1491
- deleteSessionActivity(db, deletableSessionIds);
1492
- if (!options.preserveTagRevisionTombstones) {
1493
- const retireOwner = cleanupStatement(db, harness === undefined ? "DELETE FROM schema_migrations_meta WHERE key >= ? AND key < ?" : "DELETE FROM schema_migrations_meta WHERE key = ?");
1494
- const survivingRows = harnessTables.map((definition) => `EXISTS (SELECT 1 FROM ${definition.table} WHERE session_id = ?${definition.extraPredicate ? ` AND ${definition.extraPredicate}` : ""})`).join(" OR ");
1495
- const retire = cleanupStatement(db, `DELETE FROM schema_migrations_meta WHERE key IN (?, ?)
1496
- AND NOT (${survivingRows})
1497
- AND NOT EXISTS (SELECT 1 FROM schema_migrations_meta AS owners WHERE owners.key >= ? AND owners.key < ?)`);
1498
- for (const id of deletableSessionIds) {
1499
- if (harness === undefined)
1500
- retireOwner.run(...tagTombstoneOwnerRange(id));
1501
- else
1502
- retireOwner.run(tagTombstoneOwnerKey(id, harness));
1503
- retire.run(tagIdentityRevisionKey(id), tagRevisionKey(id), ...harnessTables.map(() => id), ...tagTombstoneOwnerRange(id));
1504
- }
1505
- }
1506
- if (!options.deferRecoveryFenceCleanup)
1507
- for (const sessionId of deletableSessionIds)
1508
- clearLkgRecoveryFence(sessionId);
1509
- return deletableSessionIds.length;
1510
- }
1511
- var SESSION_CLEANUP_SLICE_ROWS = 64;
1512
- function deleteSessionScopedRowsSlice(db, sessionId, harness) {
1513
- if (cleanupStatement(db, "SELECT 1 FROM pending_session_cleanup WHERE session_id = ? AND harness = ?").get(sessionId, `${harness}:rust`))
1514
- return { complete: true, deleted: 0 };
1515
- cleanupStatement(db, "INSERT OR IGNORE INTO schema_migrations_meta(key,value) VALUES (?,?)").run(tagTombstoneOwnerKey(sessionId, harness), harness);
1516
- let remaining = SESSION_CLEANUP_SLICE_ROWS;
1517
- const started = performance.now();
1518
- remaining -= deleteChunkEmbedBackoffForSession2(db, sessionId, remaining);
1519
- if (remaining <= 0)
1520
- return { complete: false, deleted: 0 };
1521
- for (const definition of SESSION_SCOPED_TABLES) {
1522
- if (definition.table === "message_history_fts") {
1523
- const rows = cleanupStatement(db, "SELECT fts_rowid AS id FROM message_fts_rowid_map WHERE session_id = ? LIMIT ?").all(sessionId, remaining);
1524
- const removeFts = cleanupStatement(db, "DELETE FROM message_history_fts WHERE rowid = ?");
1525
- const removeMap = cleanupStatement(db, "DELETE FROM message_fts_rowid_map WHERE session_id = ? AND fts_rowid = ?");
1526
- for (const row of rows) {
1527
- removeFts.run(row.id);
1528
- removeMap.run(sessionId, row.id);
1529
- }
1530
- remaining -= rows.length;
1531
- if (remaining > 0 && !getMessageFtsRowidMapBackfillProgress2(db).completed)
1532
- remaining -= cleanupStatement(db, `DELETE FROM message_history_fts WHERE rowid IN (
1533
- SELECT rowid FROM message_history_fts WHERE session_id = ? LIMIT ?
1534
- )`).run(sessionId, remaining).changes;
1535
- } else {
1536
- const predicates = ["session_id = ?"];
1537
- if (definition.extraPredicate)
1538
- predicates.push(definition.extraPredicate);
1539
- if (definition.harnessScoped)
1540
- predicates.push("harness = ?");
1541
- const args = [sessionId, ...definition.harnessScoped ? [harness] : [], remaining];
1542
- const withoutRowid = definition.table === "temporal_decisions" || definition.table === "session_replay_decisions";
1543
- const key = withoutRowid ? "message_id" : "rowid";
1544
- remaining -= cleanupStatement(db, `DELETE FROM ${definition.table}
1545
- WHERE ${withoutRowid ? "session_id = ? AND " : ""}${key} IN (
1546
- SELECT ${key} FROM ${definition.table} WHERE ${predicates.join(" AND ")} LIMIT ?
1547
- )`).run(...withoutRowid ? [sessionId] : [], ...args).changes;
1548
- }
1549
- if (remaining <= 0 || performance.now() - started >= 10)
1550
- return { complete: false, deleted: 0 };
1551
- }
1552
- return {
1553
- complete: true,
1554
- deleted: deleteSessionScopedRows(db, [sessionId], harness, {
1555
- deferRecoveryFenceCleanup: true
1556
- })
1557
- };
1558
- }
1559
-
1560
- // src/features/magic-context/message-index.ts
1561
- var MESSAGE_HISTORY_ORPHAN_SWEEP_BATCH_SIZE = 200;
1562
- var MESSAGE_HISTORY_ORPHAN_SAFETY_AGE_MS = 24 * 60 * 60 * 1000;
1563
- var MESSAGE_HISTORY_ORPHAN_SWEEP_COOLDOWN_MS = 10 * 60 * 1000;
1564
- var MESSAGE_HISTORY_ORPHAN_UNAVAILABLE_REPROBE_MS = 24 * 60 * 60 * 1000;
1565
- var lastIndexedStatements = new WeakMap;
1566
- var insertMessageStatements = new WeakMap;
1567
- var upsertProgressStatements = new WeakMap;
1568
- var upsertDirtyFloorStatements = new WeakMap;
1569
- var deleteFtsStatements = new WeakMap;
1570
- var deleteFtsRangeStatements = new WeakMap;
1571
- var deleteIndexStatements = new WeakMap;
1572
- var countIndexedMessageStatements = new WeakMap;
1573
- var getMessageSourceStatements = new WeakMap;
1574
- var upsertMessageSourceStatements = new WeakMap;
1575
- var deleteMessageSourceStatements = new WeakMap;
1576
- var deleteMessageSourceRangeStatements = new WeakMap;
1577
- var deleteMessageFtsStatements = new WeakMap;
1578
- var deleteFtsMapStatements = new WeakMap;
1579
- var deleteFtsMapRangeStatements = new WeakMap;
1580
- var deleteMessageFtsMapStatements = new WeakMap;
1581
- var MESSAGE_INDEX_MAX_FTS_BYTES2 = 64 * 1024;
1582
- var skipStatements = new WeakMap;
1583
- function skipStatement(db, sql) {
1584
- const statements = skipStatements.get(db) ?? new Map;
1585
- skipStatements.set(db, statements);
1586
- let statement = statements.get(sql);
1587
- if (!statement) {
1588
- statement = db.prepare(sql);
1589
- statements.set(sql, statement);
1590
- }
1591
- return statement;
1592
- }
1593
- var messageIndexSkipKey2 = (sessionId, messageId) => `message_index_skip:${JSON.stringify([sessionId, messageId])}`;
1594
- function deleteMessageIndexSkips(db, sessionId, messageId) {
1595
- if (messageId !== undefined) {
1596
- skipStatement(db, "DELETE FROM schema_migrations_meta WHERE key=?").run(messageIndexSkipKey2(sessionId, messageId));
1597
- } else {
1598
- const prefix = `message_index_skip:${JSON.stringify([sessionId]).slice(0, -1)},`;
1599
- skipStatement(db, "DELETE FROM schema_migrations_meta WHERE substr(key,1,length(?))=?").run(prefix, prefix);
1600
- }
1601
- }
1602
- function normalizeIndexText(text) {
1603
- return text.replace(/\s+/g, " ").trim();
1604
- }
1605
- function getLastIndexedStatement(db) {
1606
- let stmt = lastIndexedStatements.get(db);
1607
- if (!stmt) {
1608
- stmt = db.prepare("SELECT last_indexed_ordinal, dirty_floor_ordinal FROM message_history_index WHERE session_id = ?");
1609
- lastIndexedStatements.set(db, stmt);
1610
- }
1611
- return stmt;
1612
- }
1613
- function getInsertMessageStatement(db) {
1614
- let stmt = insertMessageStatements.get(db);
1615
- if (!stmt) {
1616
- stmt = db.prepare("INSERT INTO message_history_fts (session_id, message_ordinal, message_id, role, content) VALUES (?, ?, ?, ?, ?)");
1617
- insertMessageStatements.set(db, stmt);
1618
- }
1619
- return stmt;
1620
- }
1621
- function getUpsertProgressStatement(db) {
1622
- let stmt = upsertProgressStatements.get(db);
1623
- if (!stmt) {
1624
- stmt = db.prepare("INSERT INTO message_history_index (session_id, last_indexed_ordinal, dirty_floor_ordinal, updated_at, harness) VALUES (?, ?, ?, ?, ?) ON CONFLICT(session_id) DO UPDATE SET last_indexed_ordinal = excluded.last_indexed_ordinal, dirty_floor_ordinal = excluded.dirty_floor_ordinal, updated_at = excluded.updated_at");
1625
- upsertProgressStatements.set(db, stmt);
1626
- }
1627
- return stmt;
1628
- }
1629
- function getUpsertDirtyFloorStatement(db) {
1630
- let stmt = upsertDirtyFloorStatements.get(db);
1631
- if (!stmt) {
1632
- stmt = db.prepare("INSERT INTO message_history_index (session_id, last_indexed_ordinal, dirty_floor_ordinal, updated_at, harness) VALUES (?, ?, ?, ?, ?) ON CONFLICT(session_id) DO UPDATE SET last_indexed_ordinal = MAX(message_history_index.last_indexed_ordinal, excluded.last_indexed_ordinal), dirty_floor_ordinal = CASE WHEN message_history_index.dirty_floor_ordinal <= 0 THEN excluded.dirty_floor_ordinal WHEN excluded.dirty_floor_ordinal <= 0 THEN message_history_index.dirty_floor_ordinal ELSE MIN(message_history_index.dirty_floor_ordinal, excluded.dirty_floor_ordinal) END, updated_at = excluded.updated_at");
1633
- upsertDirtyFloorStatements.set(db, stmt);
1634
- }
1635
- return stmt;
1636
- }
1637
- function getDeleteFtsStatement(db) {
1638
- let stmt = deleteFtsStatements.get(db);
1639
- if (!stmt) {
1640
- stmt = db.prepare(`DELETE FROM message_history_fts
1641
- WHERE rowid IN (
1642
- SELECT fts_rowid FROM message_fts_rowid_map WHERE session_id = ?
1643
- )`);
1644
- deleteFtsStatements.set(db, stmt);
1645
- }
1646
- return stmt;
1647
- }
1648
- function getDeleteFtsRangeStatement(db) {
1649
- let stmt = deleteFtsRangeStatements.get(db);
1650
- if (!stmt) {
1651
- stmt = db.prepare(`DELETE FROM message_history_fts
1652
- WHERE rowid IN (
1653
- SELECT fts_rowid
1654
- FROM message_fts_rowid_map
1655
- WHERE session_id = ? AND message_ordinal BETWEEN ? AND ?
1656
- )`);
1657
- deleteFtsRangeStatements.set(db, stmt);
1658
- }
1659
- return stmt;
1660
- }
1661
- function getDeleteIndexStatement(db) {
1662
- let stmt = deleteIndexStatements.get(db);
1663
- if (!stmt) {
1664
- stmt = db.prepare("DELETE FROM message_history_index WHERE session_id = ?");
1665
- deleteIndexStatements.set(db, stmt);
1666
- }
1667
- return stmt;
1668
- }
1669
- function getCountIndexedMessageStatement(db) {
1670
- let stmt = countIndexedMessageStatements.get(db);
1671
- if (!stmt) {
1672
- stmt = db.prepare(`SELECT COUNT(*) AS count
1673
- FROM message_history_source AS source
1674
- JOIN message_fts_rowid_map AS map
1675
- ON map.session_id = source.session_id
1676
- AND map.message_ordinal = source.message_ordinal
1677
- WHERE source.session_id = ? AND source.message_id = ?`);
1678
- countIndexedMessageStatements.set(db, stmt);
1679
- }
1680
- return stmt;
1681
- }
1682
- function getMessageSourceStatement(db) {
1683
- let stmt = getMessageSourceStatements.get(db);
1684
- if (!stmt) {
1685
- stmt = db.prepare("SELECT message_ordinal, source_version, normalized_content_hash, role FROM message_history_source WHERE session_id = ? AND message_id = ?");
1686
- getMessageSourceStatements.set(db, stmt);
1687
- }
1688
- return stmt;
1689
- }
1690
- function getUpsertMessageSourceStatement(db) {
1691
- let stmt = upsertMessageSourceStatements.get(db);
1692
- if (!stmt) {
1693
- stmt = db.prepare(`INSERT INTO message_history_source (
1694
- session_id, message_id, message_ordinal, source_version,
1695
- normalized_content_hash, role, harness, updated_at
1696
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1697
- ON CONFLICT(session_id, message_id) DO UPDATE SET
1698
- message_ordinal = excluded.message_ordinal,
1699
- source_version = excluded.source_version,
1700
- normalized_content_hash = excluded.normalized_content_hash,
1701
- role = excluded.role,
1702
- harness = excluded.harness,
1703
- updated_at = excluded.updated_at`);
1704
- upsertMessageSourceStatements.set(db, stmt);
1705
- }
1706
- return stmt;
1707
- }
1708
- function getDeleteMessageSourceStatement(db) {
1709
- let stmt = deleteMessageSourceStatements.get(db);
1710
- if (!stmt) {
1711
- stmt = db.prepare("DELETE FROM message_history_source WHERE session_id = ?");
1712
- deleteMessageSourceStatements.set(db, stmt);
1713
- }
1714
- return stmt;
1715
- }
1716
- function getDeleteMessageSourceRangeStatement(db) {
1717
- let stmt = deleteMessageSourceRangeStatements.get(db);
1718
- if (!stmt) {
1719
- stmt = db.prepare("DELETE FROM message_history_source WHERE session_id = ? AND message_ordinal BETWEEN ? AND ?");
1720
- deleteMessageSourceRangeStatements.set(db, stmt);
1721
- }
1722
- return stmt;
1723
- }
1724
- function getDeleteMessageFtsStatement(db) {
1725
- let stmt = deleteMessageFtsStatements.get(db);
1726
- if (!stmt) {
1727
- stmt = db.prepare(`DELETE FROM message_history_fts
1728
- WHERE rowid IN (
1729
- SELECT map.fts_rowid
1730
- FROM message_history_source AS source
1731
- JOIN message_fts_rowid_map AS map
1732
- ON map.session_id = source.session_id
1733
- AND map.message_ordinal = source.message_ordinal
1734
- WHERE source.session_id = ? AND source.message_id = ?
1735
- )`);
1736
- deleteMessageFtsStatements.set(db, stmt);
1737
- }
1738
- return stmt;
1739
- }
1740
- function getDeleteFtsMapStatement(db) {
1741
- let stmt = deleteFtsMapStatements.get(db);
1742
- if (!stmt) {
1743
- stmt = db.prepare("DELETE FROM message_fts_rowid_map WHERE session_id = ?");
1744
- deleteFtsMapStatements.set(db, stmt);
1745
- }
1746
- return stmt;
1747
- }
1748
- function getDeleteFtsMapRangeStatement(db) {
1749
- let stmt = deleteFtsMapRangeStatements.get(db);
1750
- if (!stmt) {
1751
- stmt = db.prepare("DELETE FROM message_fts_rowid_map WHERE session_id = ? AND message_ordinal BETWEEN ? AND ?");
1752
- deleteFtsMapRangeStatements.set(db, stmt);
1753
- }
1754
- return stmt;
1755
- }
1756
- function getDeleteMessageFtsMapStatement(db) {
1757
- let stmt = deleteMessageFtsMapStatements.get(db);
1758
- if (!stmt) {
1759
- stmt = db.prepare(`DELETE FROM message_fts_rowid_map
1760
- WHERE session_id = ?
1761
- AND message_ordinal = (
1762
- SELECT message_ordinal
1763
- FROM message_history_source
1764
- WHERE session_id = ? AND message_id = ?
1765
- )`);
1766
- deleteMessageFtsMapStatements.set(db, stmt);
1767
- }
1768
- return stmt;
1769
- }
1770
- function insertMessageFtsRow(db, sessionId, messageOrdinal, messageId, role, content, messageTimeMs) {
1771
- const result = getInsertMessageStatement(db).run(sessionId, messageOrdinal, messageId, role, content);
1772
- recordMessageFtsRowid2(db, sessionId, messageOrdinal, result.lastInsertRowid, messageTimeMs ?? null);
1773
- }
1774
- function normalizeSourceVersion(version) {
1775
- if (typeof version === "number")
1776
- return `number:${version}`;
1777
- if (typeof version === "string")
1778
- return `string:${version}`;
1779
- return "null";
1780
- }
1781
- function toMessageIndexSource2(message) {
1782
- const content = getIndexableContent2(message.role, message.parts);
1783
- return {
1784
- id: message.id,
1785
- ordinal: message.ordinal,
1786
- role: message.role,
1787
- createdAt: message.createdAt,
1788
- sourceVersion: normalizeSourceVersion(message.version),
1789
- contentHash: createHash4("sha256").update(content).digest("hex"),
1790
- content
1791
- };
1792
- }
1793
- function getMessageSourceSnapshot(message) {
1794
- return toMessageIndexSource2(message);
1795
- }
1796
- function getMessageIndexSourceIdentity2(message) {
1797
- const source = getMessageSourceSnapshot(message);
1798
- return JSON.stringify([
1799
- source.ordinal,
1800
- source.sourceVersion,
1801
- source.contentHash,
1802
- source.role,
1803
- message.createdAt ?? null
1804
- ]);
1805
- }
1806
- function isMessageIndexSourceCurrent2(db, sessionId, message) {
1807
- const source = getMessageSourceSnapshot(message);
1808
- return isIndexSourceCurrent(db, sessionId, source);
1809
- }
1810
- function isIndexSourceCurrent(db, sessionId, source) {
1811
- const row = getMessageSourceStatement(db).get(sessionId, source.id);
1812
- return row?.message_ordinal === source.ordinal && row.source_version === source.sourceVersion && row.normalized_content_hash === source.contentHash && row.role === source.role;
1813
- }
1814
- function writeMessageSource(db, sessionId, source, now) {
1815
- getUpsertMessageSourceStatement(db).run(sessionId, source.id, source.ordinal, source.sourceVersion, source.contentHash, source.role, getHarness2(), now);
1816
- const bytes = Buffer.byteLength(source.content);
1817
- if (source.ftsSkippedBytes !== undefined || bytes > MESSAGE_INDEX_MAX_FTS_BYTES2) {
1818
- skipStatement(db, "INSERT INTO schema_migrations_meta(key,value) VALUES (?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value").run(messageIndexSkipKey2(sessionId, source.id), JSON.stringify({
1819
- reason: "oversized",
1820
- bytes: source.ftsSkippedBytes ?? bytes,
1821
- contentHash: source.contentHash,
1822
- ordinal: source.ordinal,
1823
- complete: true
1824
- }));
1825
- return "";
1826
- }
1827
- skipStatement(db, "DELETE FROM schema_migrations_meta WHERE key=?").run(messageIndexSkipKey2(sessionId, source.id));
1828
- return source.content;
1829
- }
1830
- function getLastIndexedOrdinal2(db, sessionId) {
1831
- const row = getLastIndexedStatement(db).get(sessionId);
1832
- return typeof row?.last_indexed_ordinal === "number" ? row.last_indexed_ordinal : 0;
1833
- }
1834
- function getIndexedMessageCorpusSize2(db, sessionId, maxOrdinal) {
1835
- const watermark = getLastIndexedOrdinal2(db, sessionId);
1836
- return maxOrdinal === null ? watermark : Math.min(watermark, Math.max(0, maxOrdinal));
1837
- }
1838
- function getDirtyIndexFloor(db, sessionId) {
1839
- const row = getLastIndexedStatement(db).get(sessionId);
1840
- return typeof row?.dirty_floor_ordinal === "number" && row.dirty_floor_ordinal > 0 ? row.dirty_floor_ordinal : null;
1841
- }
1842
- function markMessageIndexDirty2(db, sessionId, floorOrdinal) {
1843
- const dirtyFloor = Math.max(1, Math.floor(floorOrdinal));
1844
- getUpsertDirtyFloorStatement(db).run(sessionId, getLastIndexedOrdinal2(db, sessionId), dirtyFloor, Date.now(), getHarness2());
1845
- }
1846
- function isMessageAlreadyIndexed(db, sessionId, messageId) {
1847
- const row = getCountIndexedMessageStatement(db).get(sessionId, messageId);
1848
- return (typeof row?.count === "number" ? row.count : 0) > 0;
1849
- }
1850
- function setIndexProgress(db, sessionId, watermark, dirtyFloor, now) {
1851
- getUpsertProgressStatement(db).run(sessionId, Math.max(0, Math.floor(watermark)), dirtyFloor ?? 0, now, getHarness2());
1852
- }
1853
- function getMessageIndexReconciliationStartOrdinal2(db, sessionId) {
1854
- const watermark = getLastIndexedOrdinal2(db, sessionId);
1855
- const dirtyFloor = getDirtyIndexFloor(db, sessionId);
1856
- return dirtyFloor === null ? watermark : Math.min(watermark, dirtyFloor - 1);
1857
- }
1858
- function isMessageIndexReconciledThrough2(db, sessionId, finalWatermark) {
1859
- const dirtyFloor = getDirtyIndexFloor(db, sessionId);
1860
- return getLastIndexedOrdinal2(db, sessionId) >= finalWatermark && dirtyFloor === null;
1861
- }
1862
- function deleteIndexedMessage2(db, sessionId, messageId) {
1863
- deleteMessageIndexSkips(db, sessionId, messageId);
1864
- const row = getCountIndexedMessageStatement(db).get(sessionId, messageId);
1865
- const count = typeof row?.count === "number" ? row.count : 0;
1866
- clearIndexedMessages2(db, sessionId);
1867
- return count;
1868
- }
1869
- function clearIndexedMessagesInTransaction2(db, sessionId) {
1870
- deleteMessageIndexSkips(db, sessionId);
1871
- getDeleteFtsStatement(db).run(sessionId);
1872
- deleteUnmappedMessageFtsRows2(db, [sessionId]);
1873
- getDeleteFtsMapStatement(db).run(sessionId);
1874
- getDeleteMessageSourceStatement(db).run(sessionId);
1875
- getDeleteIndexStatement(db).run(sessionId);
1876
- clearCompressionDepth(db, sessionId);
1877
- }
1878
- function clearIndexedMessages2(db, sessionId) {
1879
- let transactionStartedAt = 0;
1880
- db.transaction(() => {
1881
- transactionStartedAt = performance.now();
1882
- clearIndexedMessagesInTransaction2(db, sessionId);
1883
- }).immediate();
1884
- logSlowWriteTransaction("message_index_clear", transactionStartedAt);
1885
- }
1886
- function getIndexableContent2(role, parts) {
1887
- if (role === "user") {
1888
- if (!hasMeaningfulUserText2(parts)) {
1889
- return "";
1890
- }
1891
- return extractTexts2(parts).map(removeTaskNotifications).map(cleanUserText2).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
1892
- }
1893
- if (role === "assistant") {
1894
- return extractTexts2(parts).map(removeTaskNotifications).map(removeSystemReminders).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
1895
- }
1896
- return "";
1897
- }
1898
- function indexSingleMessageInTransaction(db, sessionId, message, now, dirtyFloorBeforeAttempt, source) {
1899
- const currentWatermark = getLastIndexedOrdinal2(db, sessionId);
1900
- const dirtyFloor = getDirtyIndexFloor(db, sessionId);
1901
- if (message.ordinal <= currentWatermark) {
1902
- if (isIndexSourceCurrent(db, sessionId, source)) {
1903
- recordIndexedMessageTime2(db, sessionId, message.ordinal, message.createdAt);
1904
- return false;
1905
- }
1906
- if (!messageFtsOrdinalRangeIsMapped2(db, sessionId, message.ordinal, message.ordinal)) {
1907
- return false;
1908
- }
1909
- getDeleteMessageFtsStatement(db).run(sessionId, message.id);
1910
- getDeleteMessageFtsMapStatement(db).run(sessionId, sessionId, message.id);
1911
- const content = writeMessageSource(db, sessionId, source, now);
1912
- if (content.length > 0 && (message.role === "user" || message.role === "assistant")) {
1913
- insertMessageFtsRow(db, sessionId, message.ordinal, message.id, message.role, content, message.createdAt);
1914
- }
1915
- setIndexProgress(db, sessionId, currentWatermark, dirtyFloorBeforeAttempt === message.ordinal ? null : dirtyFloorBeforeAttempt, now);
1916
- return true;
1917
- }
1918
- if (message.ordinal !== currentWatermark + 1 || dirtyFloor !== null && dirtyFloor !== message.ordinal) {
1919
- return false;
1920
- }
1921
- const content = writeMessageSource(db, sessionId, source, now);
1922
- let inserted = false;
1923
- if (content.length > 0 && (message.role === "user" || message.role === "assistant") && !isMessageAlreadyIndexed(db, sessionId, message.id)) {
1924
- insertMessageFtsRow(db, sessionId, message.ordinal, message.id, message.role, content, message.createdAt);
1925
- inserted = true;
1926
- }
1927
- setIndexProgress(db, sessionId, message.ordinal, dirtyFloorBeforeAttempt === message.ordinal ? null : dirtyFloorBeforeAttempt, now);
1928
- return inserted;
1929
- }
1930
- function indexSingleMessage2(db, sessionId, message, shouldCommit = () => true, preparedSource) {
1931
- if (!shouldCommit())
1932
- return false;
1933
- const source = preparedSource ?? getMessageSourceSnapshot(message);
1934
- const currentWatermark = getLastIndexedOrdinal2(db, sessionId);
1935
- if (message.ordinal <= currentWatermark && isIndexSourceCurrent(db, sessionId, source)) {
1936
- recordIndexedMessageTime2(db, sessionId, message.ordinal, message.createdAt);
1937
- return false;
1938
- }
1939
- const dirtyFloorBeforeAttempt = getDirtyIndexFloor(db, sessionId);
1940
- markMessageIndexDirty2(db, sessionId, Math.min(message.ordinal, currentWatermark + 1));
1941
- db.exec("BEGIN IMMEDIATE");
1942
- const transactionStartedAt = performance.now();
1943
- let committed = false;
1944
- try {
1945
- const result = indexSingleMessageInTransaction(db, sessionId, message, Date.now(), dirtyFloorBeforeAttempt, source);
1946
- if (!shouldCommit())
1947
- return false;
1948
- db.exec("COMMIT");
1949
- committed = true;
1950
- logSlowWriteTransaction("message_index_incremental", transactionStartedAt);
1951
- return result;
1952
- } finally {
1953
- if (!committed) {
1954
- try {
1955
- db.exec("ROLLBACK");
1956
- } catch {}
1957
- }
1958
- }
1959
- }
1960
- function indexMessagesAfterOrdinal2(db, sessionId, messages, _lastIndexedOrdinal, finalWatermark = messages.length) {
1961
- return indexItemsAfterOrdinal(db, sessionId, messages, finalWatermark, toMessageIndexSource2);
1962
- }
1963
- function indexSourcesAfterOrdinal2(db, sessionId, sources, finalWatermark) {
1964
- return indexItemsAfterOrdinal(db, sessionId, sources, finalWatermark, (source) => source);
1965
- }
1966
- function indexItemsAfterOrdinal(db, sessionId, items, finalWatermark, toSource) {
1967
- const now = Date.now();
1968
- let inserted = 0;
1969
- const sources = new Map(items.map((item) => [item.ordinal, toSource(item)]));
1970
- db.exec("BEGIN IMMEDIATE");
1971
- const transactionStartedAt = performance.now();
1972
- let committed = false;
1973
- try {
1974
- const currentWatermark = getLastIndexedOrdinal2(db, sessionId);
1975
- const dirtyFloor = getDirtyIndexFloor(db, sessionId);
1976
- const effectiveWatermark = dirtyFloor === null ? currentWatermark : Math.min(currentWatermark, Math.max(0, dirtyFloor - 1));
1977
- if (dirtyFloor !== null && dirtyFloor <= currentWatermark && !messageFtsOrdinalRangeIsMapped2(db, sessionId, dirtyFloor, Math.min(currentWatermark, finalWatermark))) {
1978
- db.exec("COMMIT");
1979
- committed = true;
1980
- logSlowWriteTransaction("message_index_reconcile", transactionStartedAt);
1981
- return 0;
1982
- }
1983
- if (dirtyFloor !== null && dirtyFloor <= finalWatermark) {
1984
- getDeleteFtsRangeStatement(db).run(sessionId, dirtyFloor, finalWatermark);
1985
- getDeleteFtsMapRangeStatement(db).run(sessionId, dirtyFloor, finalWatermark);
1986
- getDeleteMessageSourceRangeStatement(db).run(sessionId, dirtyFloor, finalWatermark);
1987
- }
1988
- const itemsByOrdinal = new Map;
1989
- for (const item of items) {
1990
- if (item.ordinal > effectiveWatermark && item.ordinal <= finalWatermark) {
1991
- itemsByOrdinal.set(item.ordinal, item);
1992
- }
1993
- }
1994
- let coveredWatermark = effectiveWatermark;
1995
- while (coveredWatermark < finalWatermark && itemsByOrdinal.has(coveredWatermark + 1)) {
1996
- coveredWatermark += 1;
1997
- }
1998
- for (let ordinal = effectiveWatermark + 1;ordinal <= coveredWatermark; ordinal++) {
1999
- const item = itemsByOrdinal.get(ordinal);
2000
- if (!item)
2001
- continue;
2002
- const message = sources.get(ordinal);
2003
- if (!message)
2004
- continue;
2005
- const content = writeMessageSource(db, sessionId, message, now);
2006
- if (content.length === 0 || message.role !== "user" && message.role !== "assistant" || isMessageAlreadyIndexed(db, sessionId, message.id)) {
2007
- continue;
2008
- }
2009
- insertMessageFtsRow(db, sessionId, message.ordinal, message.id, message.role, content, message.createdAt);
2010
- inserted += 1;
2011
- }
2012
- const missingFloor = coveredWatermark < finalWatermark ? coveredWatermark + 1 : null;
2013
- const preservedFloor = dirtyFloor !== null && dirtyFloor > finalWatermark ? dirtyFloor : null;
2014
- const nextDirtyFloor = missingFloor === null ? preservedFloor : preservedFloor === null ? missingFloor : Math.min(missingFloor, preservedFloor);
2015
- setIndexProgress(db, sessionId, coveredWatermark, nextDirtyFloor, now);
2016
- db.exec("COMMIT");
2017
- committed = true;
2018
- logSlowWriteTransaction("message_index_reconcile", transactionStartedAt);
2019
- } finally {
2020
- if (!committed) {
2021
- try {
2022
- db.exec("ROLLBACK");
2023
- } catch {}
2024
- }
2025
- }
2026
- return inserted;
2027
- }
2028
- function harnessSupportsOpenCodeOrphanSweep(harness) {
2029
- return harness === "opencode" || harness === "opencode2";
2030
- }
2031
- function getMessageHistoryOrphanSweepState(db, harness) {
2032
- const state = db.prepare("SELECT cursor_session_id, last_swept_at FROM message_history_orphan_sweep WHERE harness = ?").get(harness) ?? {};
2033
- if (harness === "opencode2") {
2034
- if (!state.cursor_session_id?.startsWith("session_v2:"))
2035
- return {};
2036
- return { ...state, cursor_session_id: state.cursor_session_id.slice("session_v2:".length) };
2037
- }
2038
- return state;
2039
- }
2040
- function persistMessageHistoryOrphanSweepState(db, cursor, lastSweptAt, harness) {
2041
- db.prepare(`INSERT INTO message_history_orphan_sweep (harness, cursor_session_id, last_swept_at)
2042
- VALUES (?, ?, ?)
2043
- ON CONFLICT(harness) DO UPDATE SET
2044
- cursor_session_id = excluded.cursor_session_id,
2045
- last_swept_at = excluded.last_swept_at`).run(harness, harness === "opencode2" ? `session_v2:${cursor}` : cursor, lastSweptAt);
2046
- }
2047
- function getOpenCodeSessionScopedCandidateSourceSql(harness) {
2048
- const sources = SESSION_SCOPED_TABLES.filter((definition) => definition.harnessScoped === true).map((definition) => {
2049
- const predicates = ["session_id IS NOT NULL", `harness = '${harness}'`];
2050
- if (definition.extraPredicate)
2051
- predicates.push(definition.extraPredicate);
2052
- return `SELECT session_id FROM ${definition.table} WHERE ${predicates.join(" AND ")}`;
2053
- });
2054
- const jsonId = `SUBSTR(key, ${TAG_TOMBSTONE_OWNER_PREFIX.length + 1}, LENGTH(key) - ${TAG_TOMBSTONE_OWNER_PREFIX.length + harness.length + 1})`;
2055
- sources.push(`SELECT CASE WHEN json_valid(${jsonId}) THEN
2056
- CASE WHEN json_type(${jsonId}) = 'text' THEN json_extract(${jsonId}, '$') END END AS session_id
2057
- FROM schema_migrations_meta WHERE value = '${harness}'
2058
- AND key >= '${TAG_TOMBSTONE_OWNER_PREFIX}' AND key < '${TAG_TOMBSTONE_OWNER_PREFIX.slice(0, -1)};'
2059
- AND SUBSTR(key, -${harness.length + 1}) = ':${harness}'`);
2060
- return sources.join(`
2061
- UNION
2062
- `);
2063
- }
2064
- function* sweepOrphanedOpenCodeMessageIndexesInBackground(db, openReadableOpenCodeDb, options = {}) {
2065
- const now = options.now ?? Date.now();
2066
- const batchSize = Math.max(1, Math.floor(options.batchSize ?? MESSAGE_HISTORY_ORPHAN_SWEEP_BATCH_SIZE));
2067
- const safetyAgeMs = Math.max(0, options.safetyAgeMs ?? MESSAGE_HISTORY_ORPHAN_SAFETY_AGE_MS);
2068
- const cooldownMs = Math.max(0, options.cooldownMs ?? MESSAGE_HISTORY_ORPHAN_SWEEP_COOLDOWN_MS);
2069
- const unavailableReprobeMs = Math.max(cooldownMs, options.unavailableReprobeMs ?? MESSAGE_HISTORY_ORPHAN_UNAVAILABLE_REPROBE_MS);
2070
- const harness = getHarness2();
2071
- const state = getMessageHistoryOrphanSweepState(db, harness);
2072
- const cursor = typeof state.cursor_session_id === "string" ? state.cursor_session_id : "";
2073
- if (typeof state.last_swept_at === "number" && state.last_swept_at + cooldownMs > now) {
2074
- return { status: "cooldown", scanned: 0, deleted: 0, cursor };
2075
- }
2076
- if (!harnessSupportsOpenCodeOrphanSweep(harness)) {
2077
- persistMessageHistoryOrphanSweepState(db, cursor, now + unavailableReprobeMs - cooldownMs, harness);
2078
- return { status: "unavailable", scanned: 0, deleted: 0, cursor };
2079
- }
2080
- let openCodeDb = null;
2081
- try {
2082
- openCodeDb = openReadableOpenCodeDb();
2083
- } catch {
2084
- openCodeDb = null;
2085
- }
2086
- if (!openCodeDb) {
2087
- persistMessageHistoryOrphanSweepState(db, cursor, now + unavailableReprobeMs - cooldownMs, harness);
2088
- return { status: "source_unavailable", scanned: 0, deleted: 0, cursor };
2089
- }
2090
- let v2Reader;
2091
- try {
2092
- let sessionExists;
2093
- let liveIds;
2094
- if (harness === "opencode2") {
2095
- try {
2096
- const path = getSqliteDatabasePath2(openCodeDb);
2097
- if (!path)
2098
- throw new Error("OpenCode 2 source has no durable path");
2099
- v2Reader = new V2StoreReader(path);
2100
- liveIds = new Set(v2Reader.sessionIds());
2101
- if (liveIds.size === 0)
2102
- throw new Error("OpenCode 2 source has no sessions");
2103
- sessionExists = (id) => liveIds.has(id);
2104
- } catch {
2105
- persistMessageHistoryOrphanSweepState(db, cursor, now + unavailableReprobeMs - cooldownMs, harness);
2106
- return { status: "source_unavailable", scanned: 0, deleted: 0, cursor };
2107
- }
2108
- } else {
2109
- liveIds = new Set(openCodeDb.prepare("SELECT id FROM session").all().map((row) => row.id));
2110
- const query = openCodeDb.prepare("SELECT 1 FROM session WHERE id = ? LIMIT 1");
2111
- sessionExists = (id) => !!query.get(id);
2112
- }
2113
- if (options.fenceLockNames?.length) {
2114
- const retainedIds = new Set(liveIds);
2115
- const sources = SESSION_SCOPED_TABLES.map((def) => `SELECT session_id FROM ${def.table}${def.extraPredicate ? ` WHERE ${def.extraPredicate}` : ""}`);
2116
- for (const row of db.prepare(sources.join(" UNION ")).all()) {
2117
- if (typeof row.session_id === "string")
2118
- retainedIds.add(row.session_id);
2119
- }
2120
- for (const row of db.prepare("SELECT key FROM schema_migrations_meta WHERE key >= ? AND key < ?").all(TAG_TOMBSTONE_OWNER_PREFIX, "tag_revision_owner;")) {
2121
- try {
2122
- const id = JSON.parse(row.key.slice(TAG_TOMBSTONE_OWNER_PREFIX.length, row.key.lastIndexOf(":")));
2123
- if (typeof id === "string")
2124
- retainedIds.add(id);
2125
- } catch {}
2126
- }
2127
- sweepEmptyLkgRecoveryLockDirectories(options.fenceLockNames, retainedIds);
2128
- }
2129
- const cutoff = now - safetyAgeMs;
2130
- const candidateSourceSql = getOpenCodeSessionScopedCandidateSourceSql(harness);
2131
- const candidates = db.prepare(`SELECT session_id
2132
- FROM (${candidateSourceSql}) AS session_candidates
2133
- WHERE session_id > ?
2134
- AND NOT EXISTS (
2135
- SELECT 1
2136
- FROM message_history_index
2137
- WHERE message_history_index.session_id = session_candidates.session_id
2138
- AND message_history_index.harness = '${harness}'
2139
- AND message_history_index.updated_at > ?
2140
- )
2141
- ORDER BY session_id ASC
2142
- LIMIT ?`).all(cursor, cutoff, batchSize);
2143
- const missingSessionIds = candidates.filter((candidate) => !sessionExists(candidate.session_id)).map((candidate) => candidate.session_id);
2144
- const nextCursor = candidates.length < batchSize ? "" : candidates[candidates.length - 1]?.session_id ?? cursor;
2145
- const completedAt = candidates.length < batchSize ? now : null;
2146
- let deleted = 0;
2147
- let slices = 0;
2148
- const stillEligible = db.prepare(`SELECT 1 WHERE NOT EXISTS (
2149
- SELECT 1 FROM message_history_index
2150
- WHERE session_id = ? AND harness = '${harness}' AND updated_at > ?
2151
- )`);
2152
- for (const sessionId of missingSessionIds) {
2153
- let complete = false;
2154
- do {
2155
- let removed = 0;
2156
- db.exec("BEGIN IMMEDIATE");
2157
- const transactionStartedAt = performance.now();
2158
- let committed = false;
2159
- try {
2160
- const hostStillMissing = v2Reader ? !v2Reader.sessionExists(sessionId) : !sessionExists(sessionId);
2161
- if (hostStillMissing && stillEligible.get(sessionId, cutoff)) {
2162
- const slice = deleteSessionScopedRowsSlice(db, sessionId, harness);
2163
- removed = slice.deleted;
2164
- complete = slice.complete;
2165
- } else
2166
- complete = true;
2167
- db.exec("COMMIT");
2168
- committed = true;
2169
- logSlowWriteTransaction("message_index_orphan_sweep", transactionStartedAt);
2170
- } finally {
2171
- if (!committed) {
2172
- try {
2173
- db.exec("ROLLBACK");
2174
- } catch {}
2175
- }
2176
- }
2177
- deleted += removed;
2178
- if (removed)
2179
- clearLkgRecoveryFence(sessionId);
2180
- yield;
2181
- if (++slices >= Math.max(1, options.maxSlices ?? 512)) {
2182
- return {
2183
- status: "swept",
2184
- scanned: candidates.length,
2185
- deleted,
2186
- cursor,
2187
- partial: true
2188
- };
2189
- }
2190
- } while (!complete);
2191
- }
2192
- persistMessageHistoryOrphanSweepState(db, nextCursor, completedAt, harness);
2193
- return {
2194
- status: "swept",
2195
- scanned: candidates.length,
2196
- deleted,
2197
- cursor: nextCursor
2198
- };
2199
- } finally {
2200
- v2Reader?.close();
2201
- closeQuietly(openCodeDb);
2202
- }
2203
- }
2204
- async function sweepOrphanedOpenCodeMessageIndexesCooperatively2(db, openSource, options = {}) {
2205
- return withoutSqliteTransformPass2(() => withSqliteBackgroundWriter2(async () => {
2206
- const sweep = sweepOrphanedOpenCodeMessageIndexesInBackground(db, openSource, options);
2207
- try {
2208
- for (;; ) {
2209
- const step = sweep.next();
2210
- if (step.done)
2211
- return step.value;
2212
- await new Promise((resolve) => setTimeout(resolve, 0));
2213
- }
2214
- } finally {
2215
- sweep.return({ status: "source_unavailable", scanned: 0, deleted: 0, cursor: "" });
2216
- }
2217
- }));
2218
- }
2219
-
2220
- export { LkgRecoveryFenceError, clearLkgFenceProcessState, lkgServedArrayDigest, takeLkgRecoveryLockSweepPage, hasCleanLkgRecoveryFence, lkgFenceRequiresRebuild, lkgRecoveryFenceUnclean, dirtyLkgRecoveryRevision, ownsLkgDirtyFence, invalidateCleanLkgRecoveryFence, prepareLkgCaptureFence, certifyCommittedLkgCapture, markLkgRecoveryDirty, commitLkgWithRecoveryFence, clearLkgRecoveryFence, gaDatabasePath, RAW_MESSAGE_TYPES, V2StoreReaderPool, V2StoreReader, cleanSearchText, keywordRelevance, matchedSearchSnippet, clearCompressionDepth, clearCompressionDepthRange, advanceSessionActivity, backfillSessionActivity, deleteSessionScopedRows, MESSAGE_INDEX_MAX_FTS_BYTES2, messageIndexSkipKey2, toMessageIndexSource2, getMessageIndexSourceIdentity2, isMessageIndexSourceCurrent2, getLastIndexedOrdinal2, getIndexedMessageCorpusSize2, markMessageIndexDirty2, getMessageIndexReconciliationStartOrdinal2, isMessageIndexReconciledThrough2, deleteIndexedMessage2, clearIndexedMessagesInTransaction2, clearIndexedMessages2, getIndexableContent2, indexSingleMessage2, indexMessagesAfterOrdinal2, indexSourcesAfterOrdinal2, sweepOrphanedOpenCodeMessageIndexesCooperatively2 };