@cortexkit/opencode-magic-context 0.49.15 → 0.49.17

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 (235) hide show
  1. package/dist/auto-search-worker.js +76 -63
  2. package/dist/chunk-embedding-write-worker.js +12 -10
  3. package/dist/{compaction-marker-candidates-v5c6997n.js → compaction-marker-candidates-n4krhat4.js} +2 -2
  4. package/dist/compaction-marker-candidates-worker.js +2 -2
  5. package/dist/config/schema/magic-context.d.ts +343 -343
  6. package/dist/config/schema/magic-context.d.ts.map +1 -1
  7. package/dist/{conflict-warning-hook-mgq17yr3.js → conflict-warning-hook-4pmz879v.js} +6 -6
  8. package/dist/{context-0m46day0.js → context-yhetp0kn.js} +297 -141
  9. package/dist/embedding-backfill-fmz50tsy.js +24 -0
  10. package/dist/embedding-worker-process.js +1 -1
  11. package/dist/embedding-worker.js +5 -5
  12. package/dist/features/magic-context/compartment-chunk-embedding.d.ts +1 -1
  13. package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -1
  14. package/dist/features/magic-context/git-commits/indexer.d.ts.map +1 -1
  15. package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
  16. package/dist/features/magic-context/memory/project-identity-cache.d.ts +2 -0
  17. package/dist/features/magic-context/memory/project-identity-cache.d.ts.map +1 -1
  18. package/dist/features/magic-context/message-fts-rowid-map.d.ts +2 -2
  19. package/dist/features/magic-context/message-fts-rowid-map.d.ts.map +1 -1
  20. package/dist/features/magic-context/message-fts-session-filter.d.ts +2 -0
  21. package/dist/features/magic-context/message-fts-session-filter.d.ts.map +1 -1
  22. package/dist/features/magic-context/message-index-async.d.ts +1 -1
  23. package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
  24. package/dist/features/magic-context/message-index.d.ts +5 -0
  25. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  26. package/dist/features/magic-context/message-time-backfill.d.ts +2 -2
  27. package/dist/features/magic-context/message-time-backfill.d.ts.map +1 -1
  28. package/dist/features/magic-context/search.d.ts +6 -1
  29. package/dist/features/magic-context/search.d.ts.map +1 -1
  30. package/dist/features/magic-context/storage-db.d.ts +2 -1
  31. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  32. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  33. package/dist/features/magic-context/storage-meta-shared.d.ts +2 -2
  34. package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
  35. package/dist/features/magic-context/storage-session-tables.d.ts +9 -1
  36. package/dist/features/magic-context/storage-session-tables.d.ts.map +1 -1
  37. package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
  38. package/dist/hooks/magic-context/auto-search-deadline.d.ts +2 -1
  39. package/dist/hooks/magic-context/auto-search-deadline.d.ts.map +1 -1
  40. package/dist/hooks/magic-context/auto-search-hint.d.ts +3 -0
  41. package/dist/hooks/magic-context/auto-search-hint.d.ts.map +1 -1
  42. package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -1
  43. package/dist/hooks/magic-context/auto-search-worker-client.d.ts +10 -2
  44. package/dist/hooks/magic-context/auto-search-worker-client.d.ts.map +1 -1
  45. package/dist/hooks/magic-context/compartment-runner-historian.d.ts +4 -2
  46. package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
  47. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  48. package/dist/hooks/magic-context/compartment-runner-validation.d.ts.map +1 -1
  49. package/dist/hooks/magic-context/emergency-drop.d.ts.map +1 -1
  50. package/dist/hooks/magic-context/historian-host-runner.d.ts.map +1 -1
  51. package/dist/hooks/magic-context/historian-prompt-fit.d.ts +2 -1
  52. package/dist/hooks/magic-context/historian-prompt-fit.d.ts.map +1 -1
  53. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  54. package/dist/hooks/magic-context/lkg-recovery-fence.d.ts.map +1 -1
  55. package/dist/hooks/magic-context/raw-ordinal-warmup.d.ts +26 -0
  56. package/dist/hooks/magic-context/raw-ordinal-warmup.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
  58. package/dist/hooks/magic-context/recomp-orchestrator.d.ts +3 -3
  59. package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
  60. package/dist/hooks/magic-context/system-prompt-diagnostics.d.ts +8 -0
  61. package/dist/hooks/magic-context/system-prompt-diagnostics.d.ts.map +1 -0
  62. package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
  63. package/dist/hooks/magic-context/tag-messages.d.ts +6 -1
  64. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  65. package/dist/hooks/magic-context/todo-view.d.ts +2 -2
  66. package/dist/hooks/magic-context/tool-drop-target.d.ts +3 -2
  67. package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
  68. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  69. package/dist/hooks/magic-context/transform-stage-logger.d.ts +3 -0
  70. package/dist/hooks/magic-context/transform-stage-logger.d.ts.map +1 -1
  71. package/dist/hooks/magic-context/transform.d.ts +1 -0
  72. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  73. package/dist/{index-3kdm78zp.js → index-24fezq97.js} +4 -4
  74. package/dist/{index-a8bvhwqs.js → index-2vxtcb95.js} +32 -3
  75. package/dist/{index-f71thw8j.js → index-4s3t0nd0.js} +1 -1
  76. package/dist/{index-ts1hjk1h.js → index-4yt7dtf4.js} +453 -227
  77. package/dist/{index-mc1cqza6.js → index-5a1ex6x8.js} +5 -5
  78. package/dist/{index-fkx2qnvw.js → index-6x57ar45.js} +2 -2
  79. package/dist/{index-dyvbsvga.js → index-a6mc0v4d.js} +25 -11
  80. package/dist/{index-snp5wbge.js → index-a7c4r5re.js} +160 -749
  81. package/dist/{index-42vcjcwe.js → index-aabzay3h.js} +180 -103
  82. package/dist/{index-3kx0kg0f.js → index-b5vqckbw.js} +14 -8
  83. package/dist/{v2/server-wafzvyhg.js → index-beht37jy.js} +8 -0
  84. package/dist/{index-e20m7284.js → index-bjcnntmz.js} +3 -3
  85. package/dist/{index-hqkczed7.js → index-c5pytzne.js} +22 -2
  86. package/dist/{index-thvnpfsv.js → index-cwzfcbtq.js} +967 -88
  87. package/dist/{index-yxycg7fs.js → index-fb2qxs2y.js} +26 -18
  88. package/dist/{index-mht7bq0z.js → index-hqhm3wke.js} +32 -8
  89. package/dist/{index-e4dsjj1p.js → index-jn12fvvh.js} +1 -1
  90. package/dist/index-kaqs6h31.js +58 -0
  91. package/dist/{index-n6m03s3n.js → index-ke416mdc.js} +1 -1
  92. package/dist/index-kmdj7dsq.js +96 -0
  93. package/dist/{index-1d289pq2.js → index-kq82mtg0.js} +2 -2
  94. package/dist/{index-n78avt2p.js → index-n37sebck.js} +1 -1
  95. package/dist/{index-1vc7jrkg.js → index-nkpmm9q8.js} +3 -3
  96. package/dist/{index-944w16m0.js → index-p7m7j0cp.js} +25 -17
  97. package/dist/{index-wk0n1cgt.js → index-qn7xstt2.js} +2 -2
  98. package/dist/{index-saews2s3.js → index-rr2bb45z.js} +1 -1
  99. package/dist/{index-mmfa86mt.js → index-s734jcsr.js} +169 -149
  100. package/dist/{index-dgs16axy.js → index-s74zqahq.js} +1 -1
  101. package/dist/{index-bxdg2nbt.js → index-v8awdgvd.js} +4 -4
  102. package/dist/{index-xn6q9pg6.js → index-vrzjhftj.js} +36 -76
  103. package/dist/{index-1sarmj9z.js → index-wgypt3cs.js} +6 -6
  104. package/dist/{index-y1qysm3q.js → index-ze7mvk54.js} +11 -8
  105. package/dist/index.js +47 -45
  106. package/dist/message-fts-rowid-map-17s0dfn4.js +25 -0
  107. package/dist/{message-index-bounded-vtgpq6b4.js → message-index-bounded-1633vc7f.js} +17 -17
  108. package/dist/{message-index-a6gztaqx.js → message-index-dprebmtt.js} +15 -15
  109. package/dist/{message-time-backfill-qjge9044.js → message-time-backfill-73mmm76q.js} +5 -5
  110. package/dist/migration-worker.js +9 -7
  111. package/dist/plugin/dream-timer.d.ts +11 -0
  112. package/dist/plugin/dream-timer.d.ts.map +1 -1
  113. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  114. package/dist/{project-embedding-registry-x45bv3qr.js → project-embedding-registry-z74mhqkf.js} +12 -10
  115. package/dist/raw-ordinal-warmup-worker.js +40 -10
  116. package/dist/{read-session-chunk-3ae3nfdn.js → read-session-chunk-4zp09ejb.js} +9 -9
  117. package/dist/{read-session-raw-m56hc7v2.js → read-session-raw-rsc8c7qy.js} +2 -2
  118. package/dist/{recomp-orchestrator-nwzcfhx4.js → recomp-orchestrator-z3ecszx6.js} +26 -26
  119. package/dist/{search-deajrd90.js → search-6mee3vty.js} +23 -22
  120. package/dist/search-archive-znyy44ve.js +25 -0
  121. package/dist/{send-session-notification-24r4esbh.js → send-session-notification-6ve0e8p4.js} +5 -5
  122. package/dist/shared/historian-cache.d.ts +8 -0
  123. package/dist/shared/historian-cache.d.ts.map +1 -0
  124. package/dist/shared/model-suggestion-retry.d.ts +4 -0
  125. package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
  126. package/dist/shared/resolve-fallbacks.d.ts +3 -3
  127. package/dist/shared/rpc-discovery-cleanup.d.ts +3 -0
  128. package/dist/shared/rpc-discovery-cleanup.d.ts.map +1 -0
  129. package/dist/shared/rpc-server.d.ts +2 -0
  130. package/dist/shared/rpc-server.d.ts.map +1 -1
  131. package/dist/shared/search-text.d.ts +8 -0
  132. package/dist/shared/search-text.d.ts.map +1 -0
  133. package/dist/shared/sqlite.d.ts +4 -2
  134. package/dist/shared/sqlite.d.ts.map +1 -1
  135. package/dist/shared/storage-file-mutex.d.ts.map +1 -1
  136. package/dist/shared/subagent-runner.d.ts +2 -0
  137. package/dist/shared/subagent-runner.d.ts.map +1 -1
  138. package/dist/shared/system-directive.d.ts +2 -0
  139. package/dist/shared/system-directive.d.ts.map +1 -1
  140. package/dist/{sqlite-4pqyynrs.js → sqlite-zf72c52d.js} +1 -1
  141. package/dist/{storage-memory-j3v3hc83.js → storage-memory-y3qmy00r.js} +2 -2
  142. package/dist/{storage-tags-s5bg1ahx.js → storage-tags-8qns3012.js} +8 -4
  143. package/dist/tools/ctx-expand/search-archive.d.ts +5 -1
  144. package/dist/tools/ctx-expand/search-archive.d.ts.map +1 -1
  145. package/dist/{transform-postprocess-phase-ne3z1zmt.js → transform-postprocess-phase-3ydzxa62.js} +26 -26
  146. package/dist/v2/auto-search-worker.js +76 -63
  147. package/dist/v2/chunk-embedding-write-worker.js +12 -10
  148. package/dist/v2/{compaction-marker-candidates-1vqwr3s5.js → compaction-marker-candidates-ebx3388k.js} +2 -2
  149. package/dist/v2/compaction-marker-candidates-worker.js +2 -2
  150. package/dist/v2/{context-4qczzck7.js → context-jn3zpeqh.js} +909 -454
  151. package/dist/v2/embedding-backfill-7c7yr69y.js +23 -0
  152. package/dist/v2/embedding-worker-process.js +1 -1
  153. package/dist/v2/embedding-worker.js +5 -5
  154. package/dist/v2/fold/native-replay.d.ts +11 -2
  155. package/dist/v2/fold/native-replay.d.ts.map +1 -1
  156. package/dist/v2/fold/policy.d.ts.map +1 -1
  157. package/dist/v2/fold/restore.d.ts.map +1 -1
  158. package/dist/v2/hooks/context.d.ts.map +1 -1
  159. package/dist/v2/hooks/hidden-child.d.ts +8 -0
  160. package/dist/v2/hooks/hidden-child.d.ts.map +1 -1
  161. package/dist/v2/hooks/types.d.ts +17 -0
  162. package/dist/v2/hooks/types.d.ts.map +1 -1
  163. package/dist/v2/message-fts-rowid-map-wfx67gfd.js +24 -0
  164. package/dist/v2/{message-index-bounded-rxxznd34.js → message-index-bounded-hpejqyd8.js} +17 -17
  165. package/dist/v2/{message-index-f947qtbz.js → message-index-pxehjpzx.js} +15 -15
  166. package/dist/v2/{message-time-backfill-bsteyc4d.js → message-time-backfill-04t0km1z.js} +5 -5
  167. package/dist/v2/migration-worker.js +9 -7
  168. package/dist/v2/{project-embedding-registry-0msp56p8.js → project-embedding-registry-gnkj56xp.js} +12 -10
  169. package/dist/v2/raw-ordinal-warmup-worker.js +40 -10
  170. package/dist/v2/{read-session-chunk-rq9nx1nn.js → read-session-chunk-zdppn4bj.js} +9 -9
  171. package/dist/v2/{read-session-raw-b3tf6ra1.js → read-session-raw-yaegtjzh.js} +2 -2
  172. package/dist/v2/{recomp-orchestrator-5zhxtwha.js → recomp-orchestrator-cmqnfpp3.js} +26 -26
  173. package/dist/v2/{search-n8wqxb1j.js → search-66842dya.js} +23 -22
  174. package/dist/v2/search-archive-gfh11vs0.js +24 -0
  175. package/dist/v2/{send-session-notification-8ghn9tnv.js → send-session-notification-3pxn658x.js} +5 -5
  176. package/dist/v2/{server-yrvqaswb.js → server-1wy7b9de.js} +25 -11
  177. package/dist/v2/{server-th4y11y7.js → server-271qxsf5.js} +6 -6
  178. package/dist/v2/{server-e298hswg.js → server-3rhpvy84.js} +967 -88
  179. package/dist/v2/{server-qhfvvfxj.js → server-46neh5fa.js} +14 -8
  180. package/dist/v2/{server-rrqpvwqd.js → server-6tmj0s7j.js} +2 -2
  181. package/dist/v2/{server-hek6z3w6.js → server-8by8qfec.js} +11 -8
  182. package/dist/v2/{server-vbe4se57.js → server-a9xqj88f.js} +1 -1
  183. package/dist/{index-wafzvyhg.js → v2/server-beht37jy.js} +8 -0
  184. package/dist/v2/{server-05xjtq6c.js → server-d04vrx0j.js} +1 -1
  185. package/dist/v2/server-ec4ca92p.js +96 -0
  186. package/dist/v2/{server-dd0tz6cn.js → server-fw7wrk01.js} +160 -750
  187. package/dist/v2/{server-zs8d0s3b.js → server-g3qma5c0.js} +1 -1
  188. package/dist/v2/{server-k2bzedxk.js → server-gpvay0xz.js} +2 -2
  189. package/dist/v2/{server-kmbarfmn.js → server-gtfhttbr.js} +26 -18
  190. package/dist/v2/{server-g10mdym6.js → server-h1v0pyxb.js} +3 -3
  191. package/dist/v2/{server-4gn3z5an.js → server-hy3d3vsx.js} +1 -1
  192. package/dist/v2/server-kaqs6h31.js +58 -0
  193. package/dist/v2/{server-ehyava47.js → server-kh7dpa1a.js} +25 -17
  194. package/dist/v2/{server-6cdy4rc0.js → server-my7k4eds.js} +4 -4
  195. package/dist/v2/{server-d8tj10db.js → server-n1prf1g6.js} +1 -1
  196. package/dist/v2/{server-n78avt2p.js → server-n37sebck.js} +1 -1
  197. package/dist/v2/{server-zc3e54vy.js → server-nj659894.js} +6 -6
  198. package/dist/v2/{server-gvkm8xaj.js → server-rmygf5pn.js} +32 -3
  199. package/dist/v2/{server-0ppsweh6.js → server-tgnbmwah.js} +22 -2
  200. package/dist/v2/{server-cfbr1s2x.js → server-vsw8tmm6.js} +36 -76
  201. package/dist/v2/{server-prg55b7k.js → server-xj136kt8.js} +23 -3
  202. package/dist/v2/{server-atbc19ab.js → server-zyzawhdw.js} +32 -8
  203. package/dist/v2/{server-ef89gdsx.js → server-zzkj2fch.js} +4 -4
  204. package/dist/v2/server.js +5 -5
  205. package/dist/v2/{sqlite-4pqyynrs.js → sqlite-zf72c52d.js} +1 -1
  206. package/dist/v2/{storage-memory-j3v3hc83.js → storage-memory-y3qmy00r.js} +2 -2
  207. package/dist/v2/{storage-tags-hcc2adpf.js → storage-tags-1h8k6ra9.js} +8 -4
  208. package/dist/v2/store-reader.d.ts +2 -0
  209. package/dist/v2/store-reader.d.ts.map +1 -1
  210. package/dist/v2/{wrapup-orchestrator-65pwye0d.js → wrapup-orchestrator-xs8redz9.js} +26 -26
  211. package/dist/wrapup-orchestrator-j7d7ry5b.js +48 -0
  212. package/package.json +1 -1
  213. package/src/config/latch-permanence-guard.test.ts +4 -0
  214. package/src/config/schema/magic-context.ts +14 -6
  215. package/src/shared/historian-cache.test.ts +69 -0
  216. package/src/shared/historian-cache.ts +55 -0
  217. package/src/shared/model-resolution.test.ts +22 -0
  218. package/src/shared/model-suggestion-retry.ts +11 -3
  219. package/src/shared/resolve-fallbacks.test.ts +2 -3
  220. package/src/shared/resolve-fallbacks.ts +3 -3
  221. package/src/shared/rpc-discovery-cleanup.test.ts +57 -0
  222. package/src/shared/rpc-discovery-cleanup.ts +57 -0
  223. package/src/shared/rpc-server.ts +36 -13
  224. package/src/shared/search-text.test.ts +47 -0
  225. package/src/shared/search-text.ts +63 -0
  226. package/src/shared/sqlite.test.ts +56 -0
  227. package/src/shared/sqlite.ts +51 -2
  228. package/src/shared/storage-file-mutex-review.test.ts +22 -2
  229. package/src/shared/storage-file-mutex.ts +11 -1
  230. package/src/shared/subagent-runner.ts +2 -0
  231. package/src/shared/system-directive.ts +19 -1
  232. package/src/shared/tag-transcript.ts +12 -4
  233. package/dist/embedding-backfill-5e1t5wha.js +0 -22
  234. package/dist/v2/embedding-backfill-jmaf8zrr.js +0 -21
  235. package/dist/wrapup-orchestrator-28xzsj5q.js +0 -48
@@ -1,18 +1,26 @@
1
1
  import {
2
2
  getHarness2
3
3
  } from "./server-pavzsdc6.js";
4
+ import {
5
+ getMagicContextStorageDir
6
+ } from "./server-f2xf5gcb.js";
7
+ import {
8
+ isPidAlive,
9
+ readProcessStartTime
10
+ } from "./server-da07v4xp.js";
4
11
  import {
5
12
  removeSystemReminders,
13
+ removeNestedBlocks,
6
14
  hasMeaningfulUserText2,
7
15
  extractTexts2
8
- } from "./server-0ppsweh6.js";
9
- import {
10
- getMagicContextStorageDir
11
- } from "./server-f2xf5gcb.js";
16
+ } from "./server-tgnbmwah.js";
12
17
  import {
18
+ Database2,
19
+ getSqliteDatabasePath2,
20
+ prepareCachedStatement2,
13
21
  withSqliteBackgroundWriter2,
14
22
  withoutSqliteTransformPass2
15
- } from "./server-wafzvyhg.js";
23
+ } from "./server-beht37jy.js";
16
24
  import {
17
25
  TAG_TOMBSTONE_OWNER_PREFIX,
18
26
  tagTombstoneOwnerRange,
@@ -21,22 +29,26 @@ import {
21
29
  tagIdentityRevisionKey,
22
30
  deleteTagIdentityRevisions
23
31
  } from "./server-pwv4ezb0.js";
32
+ import {
33
+ resolveOpenCodeDbPath,
34
+ assertOpenCodeStoreGeneration
35
+ } from "./server-2ra6pwf3.js";
24
36
  import {
25
37
  cleanUserText2
26
- } from "./server-ehyava47.js";
38
+ } from "./server-kh7dpa1a.js";
27
39
  import {
28
- recordMessageFtsRowid,
29
- recordIndexedMessageTime,
30
- messageFtsOrdinalRangeIsMapped,
31
- deleteUnmappedMessageFtsRows
32
- } from "./server-yrvqaswb.js";
33
- import {
34
- isPidAlive,
35
- readProcessStartTime
36
- } from "./server-da07v4xp.js";
40
+ recordMessageFtsRowid2,
41
+ recordIndexedMessageTime2,
42
+ messageFtsOrdinalRangeIsMapped2,
43
+ getMessageFtsRowidMapBackfillProgress2,
44
+ deleteUnmappedMessageFtsRows2
45
+ } from "./server-1wy7b9de.js";
37
46
  import {
38
47
  deleteChunkEmbedBackoffForSession
39
- } from "./server-qhfvvfxj.js";
48
+ } from "./server-46neh5fa.js";
49
+ import {
50
+ sameProjectDirectory
51
+ } from "./server-knvkbfvh.js";
40
52
  import {
41
53
  closeQuietly
42
54
  } from "./server-8ajstryz.js";
@@ -120,11 +132,11 @@ function clearCompressionDepthRange(db, sessionId, startOrdinal, endOrdinal) {
120
132
  }
121
133
 
122
134
  // src/features/magic-context/message-index.ts
123
- import { createHash as createHash3 } from "node:crypto";
135
+ import { createHash as createHash4 } from "node:crypto";
124
136
 
125
137
  // src/hooks/magic-context/lkg-recovery-fence.ts
126
138
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
127
- import { lstatSync, readdirSync as readdirSync2, readFileSync as readFileSync2, rmSync as rmSync2 } from "node:fs";
139
+ import { lstatSync, readdirSync as readdirSync2, readFileSync as readFileSync2, rmdirSync, rmSync as rmSync2 } from "node:fs";
128
140
  import { basename, dirname, join as join2 } from "node:path";
129
141
 
130
142
  // src/shared/storage-file-mutex.ts
@@ -169,7 +181,16 @@ function withStorageFileMutex(directory, work, timeoutMs = 1000) {
169
181
  const birth = processBirth(process.pid);
170
182
  const ticket = { choosing: true, number: 0, birth, checksum: "" };
171
183
  const encodedTicket = () => JSON.stringify({ ...ticket, checksum: ticketChecksum(name, ticket) });
172
- writeStorageFileSync(ownPath, encodedTicket(), { flag: "wx", forcePrivate: true });
184
+ for (let attempt = 0;; attempt++) {
185
+ try {
186
+ writeStorageFileSync(ownPath, encodedTicket(), { flag: "wx", forcePrivate: true });
187
+ break;
188
+ } catch (error) {
189
+ if (error.code !== "ENOENT" || attempt >= 2)
190
+ throw error;
191
+ ensurePrivateStorageDirectorySync(directory);
192
+ }
193
+ }
173
194
  const deadline = performance.now() + timeoutMs;
174
195
  const peers = () => {
175
196
  const found = [];
@@ -507,15 +528,767 @@ function commitLkgWithRecoveryFence(args) {
507
528
  function clearLkgRecoveryFence(sessionId, remove, shouldClear) {
508
529
  if (!durableFencesSupported())
509
530
  return remove?.();
510
- return locked(sessionId, () => {
511
- const result = remove?.();
512
- if (shouldClear && !shouldClear(result))
513
- return result;
514
- rmSync2(lkgRecoveryFencePath(sessionId), { force: true });
515
- syncStorageDirectorySync(dirname(lkgRecoveryFencePath(sessionId)));
531
+ const path = lkgRecoveryFencePath(sessionId);
532
+ const missing = (file) => {
533
+ try {
534
+ lstatSync(file);
535
+ return false;
536
+ } catch (error) {
537
+ return error.code === "ENOENT";
538
+ }
539
+ };
540
+ if (!remove && !shouldClear && missing(path) && missing(`${path}.locks`)) {
516
541
  clearLkgFenceProcessState(sessionId);
542
+ return;
543
+ }
544
+ try {
545
+ return locked(sessionId, () => {
546
+ const result = remove?.();
547
+ if (shouldClear && !shouldClear(result))
548
+ return result;
549
+ rmSync2(lkgRecoveryFencePath(sessionId), { force: true });
550
+ syncStorageDirectorySync(dirname(lkgRecoveryFencePath(sessionId)));
551
+ clearLkgFenceProcessState(sessionId);
552
+ return result;
553
+ });
554
+ } finally {
555
+ try {
556
+ rmdirSync(`${path}.locks`);
557
+ } catch {}
558
+ }
559
+ }
560
+
561
+ // src/shared/search-text.ts
562
+ function removeTaskNotifications(text) {
563
+ return removeNestedBlocks(text, ["task", "task_cohort"]);
564
+ }
565
+ function cleanSearchText(text) {
566
+ return removeNestedBlocks(text, ["system-reminder", "task", "task_cohort"]).replace(/\s+/g, " ").trim();
567
+ }
568
+ 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(" "));
569
+ function terms(text) {
570
+ return [
571
+ ...new Set((text.toLowerCase().match(/[\p{L}\p{N}_]+/gu) ?? []).filter((term) => !QUERY_STOP_WORDS.has(term)))
572
+ ];
573
+ }
574
+ function keywordRelevance(query, content) {
575
+ const needles = terms(query);
576
+ if (needles.length === 0)
577
+ return 0;
578
+ const words = new Set(terms(cleanSearchText(content)));
579
+ return 0.8 * needles.filter((term) => words.has(term)).length / needles.length;
580
+ }
581
+ function matchedSearchSnippet(content, query, cap = 220) {
582
+ const text = cleanSearchText(content);
583
+ const needles = new Set(terms(query));
584
+ const matches = [...text.matchAll(/[\p{L}\p{N}_]+/gu)].filter((match) => needles.has(match[0].toLowerCase()));
585
+ if (matches.length === 0)
586
+ return "";
587
+ let bestStart = matches[0].index;
588
+ let bestCoverage = -1;
589
+ for (const match of matches) {
590
+ const start = Math.max(0, match.index - Math.floor(cap / 5));
591
+ const coverage = keywordRelevance(query, text.slice(start, start + cap));
592
+ if (coverage > bestCoverage) {
593
+ bestStart = start;
594
+ bestCoverage = coverage;
595
+ }
596
+ }
597
+ if (bestStart > 0) {
598
+ const boundary = text.indexOf(" ", bestStart);
599
+ if (boundary >= 0 && boundary < matches[matches.length - 1].index)
600
+ bestStart = boundary + 1;
601
+ }
602
+ const passage = text.slice(bestStart, bestStart + cap).trim();
603
+ return `${bestStart > 0 ? "…" : ""}${passage}${bestStart + cap < text.length ? "…" : ""}`;
604
+ }
605
+
606
+ // src/v2/store-reader.ts
607
+ import { createHash as createHash3 } from "node:crypto";
608
+ import { statSync } from "node:fs";
609
+ import { hostname } from "node:os";
610
+ import { resolve } from "node:path";
611
+ function gaDatabasePath(dataHome, channel = "latest", env = process.env) {
612
+ return resolveOpenCodeDbPath("v2", { dataHome, channel, env }).path;
613
+ }
614
+ var RAW_MESSAGE_TYPES = [
615
+ "user",
616
+ "synthetic",
617
+ "assistant",
618
+ "skill",
619
+ "shell",
620
+ "system"
621
+ ];
622
+ var RAW_MESSAGE_TYPE_PARAMETERS = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
623
+ var V2_MESSAGE_PAGE_SQL = `WITH bounds AS (
624
+ SELECT
625
+ CASE WHEN ? = 0 THEN -1 ELSE COALESCE((
626
+ SELECT seq FROM session_message
627
+ WHERE session_id = ? AND type IN (${RAW_MESSAGE_TYPE_PARAMETERS})
628
+ ORDER BY seq ASC LIMIT 1 OFFSET ?
629
+ ), (SELECT MAX(seq) FROM session_message WHERE session_id = ?), -1) END AS after_seq,
630
+ COALESCE((
631
+ SELECT seq FROM session_message
632
+ WHERE session_id = ? AND type IN (${RAW_MESSAGE_TYPE_PARAMETERS})
633
+ ORDER BY seq ASC LIMIT 1 OFFSET ?
634
+ ), ?) AS watermark_seq
635
+ )
636
+ SELECT id, session_id, type, seq, time_created, data FROM session_message, bounds
637
+ WHERE session_id = ?
638
+ AND type IN (${RAW_MESSAGE_TYPE_PARAMETERS})
639
+ AND seq > bounds.after_seq
640
+ AND seq <= bounds.watermark_seq
641
+ ORDER BY seq ASC LIMIT ?`;
642
+ var V2_STORE_READER_DEBUG_COUNTER_KEY = "magic-context.v2.store-reader-debug";
643
+ var debugSymbol = Symbol.for(V2_STORE_READER_DEBUG_COUNTER_KEY);
644
+ var debugGlobal = globalThis;
645
+ var QUERY_STATEMENTS = {
646
+ messagePage: V2_MESSAGE_PAGE_SQL,
647
+ messageCount: "SELECT COUNT(*) AS count FROM session_message WHERE session_id = ? AND type IN (?)",
648
+ storedMessageCount: "SELECT COUNT(*) AS count FROM session_message WHERE session_id = ?",
649
+ messageById: "SELECT id, session_id, type, seq, time_created, data FROM session_message WHERE session_id = ? AND id = ? AND type IN (?) LIMIT 1",
650
+ messageExistsById: "SELECT 1 FROM session_message WHERE id = ? AND session_id = ? AND type IN (?) LIMIT 1",
651
+ messageOrdinalById: `WITH target AS (
652
+ SELECT seq FROM session_message WHERE session_id = ? AND id = ? AND type IN (?) LIMIT 1
653
+ )
654
+ SELECT (SELECT COUNT(*) FROM session_message WHERE session_id = ? AND type IN (?) AND seq <= target.seq) AS ordinal FROM target`,
655
+ messageIdOrdinals: "SELECT id FROM session_message WHERE session_id = ? AND type IN (?) ORDER BY seq ASC LIMIT ? OFFSET ?",
656
+ 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 ?",
657
+ 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 ?"
658
+ };
659
+ function debugCounters() {
660
+ const counters = debugGlobal[debugSymbol] ??= {
661
+ decodedRows: 0,
662
+ operations: {},
663
+ openReaders: 0,
664
+ maxOpenReaders: 0,
665
+ readersOpened: 0,
666
+ readersClosed: 0
667
+ };
668
+ counters.openReaders ??= 0;
669
+ counters.maxOpenReaders ??= 0;
670
+ counters.readersOpened ??= 0;
671
+ counters.readersClosed ??= 0;
672
+ return counters;
673
+ }
674
+ function returnedRowCount(value) {
675
+ if (Array.isArray(value))
676
+ return value.length;
677
+ if (value === null || value === undefined || value === false)
678
+ return 0;
679
+ return 1;
680
+ }
681
+ function trackDecodeOperation(name, operation) {
682
+ const counters = debugCounters();
683
+ const before = counters.decodedRows;
684
+ const started = performance.now();
685
+ let value;
686
+ try {
687
+ value = operation();
688
+ return value;
689
+ } finally {
690
+ const decodedRows = counters.decodedRows - before;
691
+ const current = counters.operations[name] ?? {
692
+ calls: 0,
693
+ decodedRows: 0,
694
+ maxDecodedRows: 0
695
+ };
696
+ current.calls += 1;
697
+ current.decodedRows += decodedRows;
698
+ current.maxDecodedRows = Math.max(current.maxDecodedRows, decodedRows);
699
+ counters.operations[name] = current;
700
+ const statement = QUERY_STATEMENTS[name];
701
+ if (counters.captureQueries && statement) {
702
+ (counters.queries ??= []).push({
703
+ operation: name,
704
+ statement,
705
+ rows: returnedRowCount(value),
706
+ elapsedMs: performance.now() - started
707
+ });
708
+ }
709
+ }
710
+ }
711
+ function decode(row) {
712
+ debugCounters().decodedRows += 1;
713
+ const data = JSON.parse(row.data);
714
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
715
+ throw new Error(`Invalid session_message data at seq ${row.seq}`);
716
+ }
717
+ if (row.type === "idle" && !["succeeded", "failed", "interrupted"].includes(String(data.outcome))) {
718
+ throw new Error(`Invalid idle outcome at seq ${row.seq}`);
719
+ }
720
+ return { ...row, data };
721
+ }
722
+ function connect(path, identity = "") {
723
+ const stat = statSync(path);
724
+ const acquiredIdentity = identity || `${stat.dev}:${stat.ino}:${stat.birthtimeMs}`;
725
+ const db = new Database2(path, { readonly: true, fileMustExist: true });
726
+ try {
727
+ assertOpenCodeStoreGeneration(db, "v2", path);
728
+ } catch (error) {
729
+ db.close();
730
+ throw error;
731
+ }
732
+ const counters = debugCounters();
733
+ counters.openReaders += 1;
734
+ counters.readersOpened += 1;
735
+ counters.maxOpenReaders = Math.max(counters.maxOpenReaders, counters.openReaders);
736
+ return { db, statements: new Map, identity: acquiredIdentity, leases: 0, retired: false };
737
+ }
738
+ function closeConnection(connection) {
739
+ connection.statements.clear();
740
+ try {
741
+ connection.db.close();
742
+ } finally {
743
+ const counters = debugCounters();
744
+ counters.openReaders = Math.max(0, counters.openReaders - 1);
745
+ counters.readersClosed += 1;
746
+ }
747
+ }
748
+
749
+ class V2StoreReaderPool {
750
+ capacity;
751
+ connections = new Map;
752
+ closed = false;
753
+ closing = false;
754
+ holds = 0;
755
+ idleWaiters = new Set;
756
+ constructor(capacity = 4) {
757
+ this.capacity = capacity;
758
+ if (!Number.isSafeInteger(capacity) || capacity < 1)
759
+ throw new Error("Invalid reader capacity");
760
+ }
761
+ open(path) {
762
+ return new V2StoreReader(path, this);
763
+ }
764
+ acquire(path) {
765
+ if (this.closed)
766
+ throw new Error("Store reader pool is closed");
767
+ const key = resolve(path);
768
+ const stat = statSync(key);
769
+ const identity = `${stat.dev}:${stat.ino}:${stat.birthtimeMs}`;
770
+ let connection = this.connections.get(key);
771
+ if (connection && connection.identity !== identity) {
772
+ this.connections.delete(key);
773
+ this.retire(connection);
774
+ connection = undefined;
775
+ }
776
+ if (!connection)
777
+ connection = connect(key, identity);
778
+ connection.leases++;
779
+ this.connections.delete(key);
780
+ this.connections.set(key, connection);
781
+ while (this.connections.size > this.capacity) {
782
+ const oldest = this.connections.entries().next().value;
783
+ if (!oldest)
784
+ break;
785
+ this.connections.delete(oldest[0]);
786
+ this.retire(oldest[1]);
787
+ }
788
+ return connection;
789
+ }
790
+ release(connection) {
791
+ connection.leases--;
792
+ if (connection.retired && connection.leases === 0)
793
+ closeConnection(connection);
794
+ }
795
+ retire(connection) {
796
+ connection.retired = true;
797
+ if (connection.leases === 0)
798
+ closeConnection(connection);
799
+ }
800
+ hold() {
801
+ if (this.closed || this.closing)
802
+ throw new Error("Store reader pool is closed");
803
+ this.holds++;
804
+ let released = false;
805
+ return () => {
806
+ if (released)
807
+ return;
808
+ released = true;
809
+ this.holds--;
810
+ if (this.holds === 0 && this.closing)
811
+ this.finishClose();
812
+ };
813
+ }
814
+ isClosed() {
815
+ return this.closed;
816
+ }
817
+ holdCount() {
818
+ return this.holds;
819
+ }
820
+ close() {
821
+ if (this.closed || this.closing)
822
+ return;
823
+ this.closing = true;
824
+ if (this.holds === 0)
825
+ this.finishClose();
826
+ }
827
+ async closeWhenIdle(timeoutMs) {
828
+ this.close();
829
+ if (this.closed)
830
+ return true;
831
+ let timer;
832
+ let waiter;
833
+ try {
834
+ await new Promise((resolve) => {
835
+ waiter = resolve;
836
+ this.idleWaiters.add(resolve);
837
+ timer = setTimeout(resolve, Math.max(0, timeoutMs));
838
+ });
839
+ } finally {
840
+ if (timer)
841
+ clearTimeout(timer);
842
+ if (waiter)
843
+ this.idleWaiters.delete(waiter);
844
+ }
845
+ return this.closed;
846
+ }
847
+ finishClose() {
848
+ if (this.closed)
849
+ return;
850
+ this.closed = true;
851
+ for (const connection of this.connections.values())
852
+ this.retire(connection);
853
+ this.connections.clear();
854
+ for (const resolve of this.idleWaiters)
855
+ resolve();
856
+ this.idleWaiters.clear();
857
+ }
858
+ }
859
+
860
+ class V2StoreReader {
861
+ path;
862
+ pool;
863
+ db;
864
+ connection;
865
+ closed = false;
866
+ constructor(path, pool) {
867
+ this.path = path;
868
+ this.pool = pool;
869
+ this.connection = pool ? pool.acquire(path) : connect(path);
870
+ this.db = this.connection.db;
871
+ }
872
+ hostIdentity() {
873
+ return createHash3("sha256").update(JSON.stringify([hostname(), resolve(this.path), this.connection.identity])).digest("hex");
874
+ }
875
+ prepare(sql) {
876
+ if (this.closed)
877
+ throw new Error("Store reader is closed");
878
+ const statements = this.connection.statements;
879
+ let statement = statements.get(sql);
880
+ if (!statement) {
881
+ statement = this.db.prepare(sql);
882
+ if (statements.size >= 128)
883
+ statements.clear();
884
+ statements.set(sql, statement);
885
+ }
886
+ return statement;
887
+ }
888
+ close() {
889
+ if (this.closed)
890
+ return;
891
+ this.closed = true;
892
+ if (this.pool)
893
+ this.pool.release(this.connection);
894
+ else
895
+ closeConnection(this.connection);
896
+ }
897
+ page(sessionID, options = {}) {
898
+ return trackDecodeOperation("page", () => {
899
+ const limit = options.limit ?? 100;
900
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e4)
901
+ throw new Error("Invalid page limit");
902
+ const after = options.after ?? -1;
903
+ if (!Number.isSafeInteger(after))
904
+ throw new Error("Invalid seq cursor");
905
+ if (options.through !== undefined && !Number.isSafeInteger(options.through))
906
+ throw new Error("Invalid seq upper bound");
907
+ const predicates = ["session_id = ?", "seq > ?"];
908
+ const parameters = [sessionID, after];
909
+ if (options.through !== undefined) {
910
+ predicates.push("seq <= ?");
911
+ parameters.push(options.through);
912
+ }
913
+ if (options.type) {
914
+ predicates.push("type = ?");
915
+ parameters.push(options.type);
916
+ }
917
+ const rows = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
918
+ WHERE ${predicates.join(" AND ")}
919
+ ORDER BY seq ASC LIMIT ?`).all(...parameters, limit).map(decode);
920
+ return { rows, cursor: rows.at(-1)?.seq };
921
+ });
922
+ }
923
+ messagePage(sessionID, afterOrdinal, limit, finalWatermark, after) {
924
+ return trackDecodeOperation("messagePage", () => {
925
+ if (!Number.isSafeInteger(afterOrdinal) || afterOrdinal < 0)
926
+ throw new Error("Invalid raw-message ordinal cursor");
927
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e4)
928
+ throw new Error("Invalid page limit");
929
+ if (!Number.isSafeInteger(finalWatermark) || finalWatermark < 0)
930
+ throw new Error("Invalid raw-message watermark");
931
+ const pageSize = Math.min(limit, finalWatermark - afterOrdinal);
932
+ if (pageSize <= 0)
933
+ return [];
934
+ if (after) {
935
+ const anchor = this.prepare("SELECT seq FROM session_message WHERE session_id = ? AND id = ?").get(sessionID, after.id);
936
+ if (anchor) {
937
+ return this.prepare(`SELECT id, session_id, type, seq, time_created, data
938
+ FROM session_message WHERE session_id = ? AND seq > ?
939
+ AND type IN (${RAW_MESSAGE_TYPE_PARAMETERS}) ORDER BY seq ASC LIMIT ?`).all(sessionID, anchor.seq, ...RAW_MESSAGE_TYPES, pageSize).map(decode);
940
+ }
941
+ }
942
+ const maximumSeq = Number.MAX_SAFE_INTEGER;
943
+ 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);
944
+ return rows.map(decode);
945
+ });
946
+ }
947
+ messageCount(sessionID) {
948
+ return trackDecodeOperation("messageCount", () => {
949
+ const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
950
+ const row = this.prepare(`SELECT COUNT(*) AS count FROM session_message
951
+ WHERE session_id = ? AND type IN (${rawTypes})`).get(sessionID, ...RAW_MESSAGE_TYPES);
952
+ return typeof row?.count === "number" ? row.count : 0;
953
+ });
954
+ }
955
+ sessionDirectoryPage(afterSessionID, limit) {
956
+ const rows = this.prepare(`SELECT id, COALESCE(directory, '') AS directory FROM session_v2
957
+ WHERE id > ? ORDER BY id ASC LIMIT ?`).all(afterSessionID ?? "", limit);
958
+ return rows.map((row) => ({ sessionId: row.id, directory: row.directory }));
959
+ }
960
+ latestMessageTime(sessionID) {
961
+ const row = this.prepare("SELECT MAX(time_created) AS time FROM session_message WHERE session_id = ?").get(sessionID);
962
+ return row?.time ?? undefined;
963
+ }
964
+ isSubagent(sessionID) {
965
+ const row = this.prepare("SELECT parent_id FROM session_v2 WHERE id = ?").get(sessionID);
966
+ if (!row)
967
+ return null;
968
+ return typeof row.parent_id === "string" && row.parent_id.length > 0;
969
+ }
970
+ sessionIds() {
971
+ return this.prepare("SELECT id FROM session_v2").all().map((row) => row.id);
972
+ }
973
+ latestRootSessionInDirectory(directory) {
974
+ const columns = new Set(this.prepare("PRAGMA table_info(session_v2)").all().map((column) => column.name));
975
+ const archived = columns.has("time_archived") ? " AND time_archived IS NULL" : "";
976
+ const order = columns.has("time_updated") ? "time_updated DESC, id DESC" : "id DESC";
977
+ const page = this.prepare(`SELECT id, COALESCE(directory, '') AS directory FROM session_v2
978
+ WHERE parent_id IS NULL${archived}
979
+ AND COALESCE(json_extract(metadata, '$.magic_context'), '') <> 'hidden-run'
980
+ ORDER BY ${order} LIMIT ? OFFSET ?`);
981
+ const pageSize = 500;
982
+ for (let offset = 0;; offset += pageSize) {
983
+ const rows = page.all(pageSize, offset);
984
+ const match = rows.find((row) => sameProjectDirectory(row.directory, directory));
985
+ if (match)
986
+ return match.id;
987
+ if (rows.length < pageSize)
988
+ return;
989
+ }
990
+ }
991
+ rootSessionActivity(sessionIDs) {
992
+ const roots = new Map;
993
+ for (let offset = 0;offset < sessionIDs.length; offset += 500) {
994
+ const chunk = sessionIDs.slice(offset, offset + 500);
995
+ const rows = this.prepare(`SELECT id, (
996
+ SELECT MAX(time_created) FROM session_message
997
+ WHERE session_id = session_v2.id AND type = 'user'
998
+ ) AS time FROM session_v2
999
+ WHERE parent_id IS NULL
1000
+ AND COALESCE(json_extract(metadata, '$.magic_context'), '') <> 'hidden-run'
1001
+ AND id IN (${chunk.map(() => "?").join(",")})`).all(...chunk);
1002
+ for (const row of rows)
1003
+ if (row.time !== null)
1004
+ roots.set(row.id, row.time);
1005
+ }
1006
+ return roots;
1007
+ }
1008
+ oldestUserMessageTimesSince(sessionIDs, sinceMs) {
1009
+ const result = new Map;
1010
+ for (let offset = 0;offset < sessionIDs.length; offset += 500) {
1011
+ const chunk = sessionIDs.slice(offset, offset + 500);
1012
+ const rows = this.prepare(`SELECT session_id, MIN(time_created) AS time FROM session_message
1013
+ WHERE type = 'user' AND time_created > ? AND session_id IN (${chunk.map(() => "?").join(",")})
1014
+ GROUP BY session_id`).all(sinceMs, ...chunk);
1015
+ for (const row of rows)
1016
+ result.set(row.session_id, row.time);
1017
+ }
517
1018
  return result;
518
- });
1019
+ }
1020
+ retrospectiveUserPage(sessionID, options) {
1021
+ const { boundaryMs, before, limit, maxChars, truncationMarker } = options;
1022
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e4)
1023
+ throw new Error("Invalid page limit");
1024
+ if (!Number.isSafeInteger(maxChars) || maxChars <= truncationMarker.length)
1025
+ throw new Error("Invalid text limit");
1026
+ const direction = before ? "DESC" : "ASC";
1027
+ const head = Math.floor((maxChars - truncationMarker.length) / 2);
1028
+ const tail = maxChars - truncationMarker.length - head;
1029
+ return this.prepare(`WITH users AS (
1030
+ SELECT session_id, seq, time_created, data FROM session_message
1031
+ WHERE session_id = ? AND type = 'user' AND time_created ${before ? "<=" : ">"}
1032
+ ?
1033
+ ORDER BY time_created ${direction}, seq ${direction} LIMIT ?
1034
+ ), texts AS (
1035
+ SELECT session_id, seq, time_created, CASE
1036
+ WHEN json_extract(data, '$.synthetic') = 1 OR json_extract(data, '$.ignored') = 1 THEN ''
1037
+ WHEN json_type(data, '$.text') = 'text' THEN json_extract(data, '$.text')
1038
+ ELSE COALESCE((SELECT group_concat(json_extract(value, '$.text'), char(10))
1039
+ FROM json_each(users.data, '$.content')
1040
+ WHERE json_extract(value, '$.type') IN ('text', 'input_text')
1041
+ AND COALESCE(json_extract(value, '$.synthetic'), 0) <> 1
1042
+ AND COALESCE(json_extract(value, '$.ignored'), 0) <> 1), '') END AS text
1043
+ FROM users WHERE json_valid(data)
1044
+ ) SELECT seq, time_created, CASE WHEN length(text) > ?
1045
+ THEN substr(text, 1, ?) || ? || substr(text, -?) ELSE text END AS text,
1046
+ EXISTS (
1047
+ SELECT 1 FROM session_message assistant_reply
1048
+ WHERE assistant_reply.session_id = texts.session_id
1049
+ AND assistant_reply.type = 'assistant'
1050
+ AND assistant_reply.seq < texts.seq
1051
+ AND EXISTS (
1052
+ SELECT 1 FROM session_message prior_user
1053
+ WHERE prior_user.session_id = texts.session_id
1054
+ AND prior_user.type = 'user'
1055
+ AND prior_user.seq < assistant_reply.seq
1056
+ )
1057
+ ) AS follows_assistant_reply
1058
+ FROM texts ORDER BY time_created ${direction}, seq ${direction}`).all(sessionID, boundaryMs, limit, maxChars, head, truncationMarker, tail);
1059
+ }
1060
+ storedRowsAfter(sessionID, after) {
1061
+ const row = this.prepare("SELECT COUNT(*) AS count FROM session_message WHERE session_id = ? AND seq > ?").get(sessionID, after);
1062
+ return row.count;
1063
+ }
1064
+ storedMessageCount(sessionID) {
1065
+ return trackDecodeOperation("storedMessageCount", () => {
1066
+ const row = this.prepare("SELECT COUNT(*) AS count FROM session_message WHERE session_id = ?").get(sessionID);
1067
+ return typeof row?.count === "number" ? row.count : 0;
1068
+ });
1069
+ }
1070
+ messageById(sessionID, id) {
1071
+ return trackDecodeOperation("messageById", () => {
1072
+ const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
1073
+ const row = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
1074
+ WHERE session_id = ? AND id = ? AND type IN (${rawTypes}) LIMIT 1`).get(sessionID, id, ...RAW_MESSAGE_TYPES);
1075
+ return row ? decode(row) : null;
1076
+ });
1077
+ }
1078
+ messageTimes(sessionID, ids) {
1079
+ const times = new Map;
1080
+ for (let offset = 0;offset < ids.length; offset += 500) {
1081
+ const batch = ids.slice(offset, offset + 500);
1082
+ const rows = this.prepare(`SELECT id, COALESCE(time_created, json_extract(data, '$.time.created')) AS created,
1083
+ json_extract(data, '$.time.completed') AS completed
1084
+ FROM session_message WHERE session_id = ? AND id IN (${batch.map(() => "?").join(",")})`).all(sessionID, ...batch);
1085
+ for (const row of rows)
1086
+ times.set(row.id, {
1087
+ created: typeof row.created === "number" ? row.created : undefined,
1088
+ completed: typeof row.completed === "number" ? row.completed : undefined
1089
+ });
1090
+ }
1091
+ return times;
1092
+ }
1093
+ messageExistsById(sessionID, id) {
1094
+ return trackDecodeOperation("messageExistsById", () => {
1095
+ const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
1096
+ const row = this.prepare(`SELECT 1 FROM session_message
1097
+ WHERE id = ? AND session_id = ? AND type IN (${rawTypes}) LIMIT 1`).get(id, sessionID, ...RAW_MESSAGE_TYPES);
1098
+ return row != null;
1099
+ });
1100
+ }
1101
+ messageOrdinalById(sessionID, id) {
1102
+ return trackDecodeOperation("messageOrdinalById", () => {
1103
+ const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
1104
+ const row = this.prepare(`WITH target AS (
1105
+ SELECT seq FROM session_message
1106
+ WHERE session_id = ? AND id = ? AND type IN (${rawTypes})
1107
+ LIMIT 1
1108
+ )
1109
+ SELECT (
1110
+ SELECT COUNT(*) FROM session_message
1111
+ WHERE session_id = ?
1112
+ AND type IN (${rawTypes})
1113
+ AND seq <= target.seq
1114
+ ) AS ordinal
1115
+ FROM target`).get(sessionID, id, ...RAW_MESSAGE_TYPES, sessionID, ...RAW_MESSAGE_TYPES);
1116
+ return typeof row?.ordinal === "number" ? row.ordinal : null;
1117
+ });
1118
+ }
1119
+ messageIdOrdinals(sessionID, fromOrdinal, toOrdinal) {
1120
+ return trackDecodeOperation("messageIdOrdinals", () => {
1121
+ if (!Number.isSafeInteger(fromOrdinal) || fromOrdinal < 1)
1122
+ throw new Error("Invalid raw-message range start");
1123
+ if (!Number.isSafeInteger(toOrdinal) || toOrdinal < fromOrdinal)
1124
+ throw new Error("Invalid raw-message range end");
1125
+ const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
1126
+ const rows = this.prepare(`SELECT id FROM session_message
1127
+ WHERE session_id = ? AND type IN (${rawTypes})
1128
+ ORDER BY seq ASC LIMIT ? OFFSET ?`).all(sessionID, ...RAW_MESSAGE_TYPES, toOrdinal - fromOrdinal + 1, fromOrdinal - 1);
1129
+ return new Map(rows.map((row, index) => [row.id, fromOrdinal + index]));
1130
+ });
1131
+ }
1132
+ messageOrdinalPage(sessionID, after, limit) {
1133
+ return trackDecodeOperation("messageOrdinalPage", () => {
1134
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e4)
1135
+ throw new Error("Invalid page limit");
1136
+ const rows = after ? this.prepare(`SELECT id, type, seq, json_valid(data) AS valid
1137
+ FROM session_message
1138
+ WHERE session_id = ? AND (seq > ? OR (seq = ? AND id > ?))
1139
+ 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
1140
+ FROM session_message
1141
+ WHERE session_id = ?
1142
+ ORDER BY seq ASC, id ASC LIMIT ?`).all(sessionID, limit);
1143
+ const rawTypes = new Set(RAW_MESSAGE_TYPES);
1144
+ return rows.map((row) => ({
1145
+ id: row.id,
1146
+ timeCreated: row.seq,
1147
+ contributesOrdinal: rawTypes.has(row.type),
1148
+ hasValidInfo: row.valid === 1
1149
+ }));
1150
+ });
1151
+ }
1152
+ range(sessionID, after, through) {
1153
+ return trackDecodeOperation("range", () => through <= after ? [] : this.all(sessionID, after, undefined, through));
1154
+ }
1155
+ spanRowStamps(sessionID, after, through) {
1156
+ if (!Number.isSafeInteger(after) || !Number.isSafeInteger(through))
1157
+ throw new Error("Invalid seq span");
1158
+ const rows = this.prepare(`SELECT seq, id, time_updated AS updated, length(CAST(data AS BLOB)) AS bytes
1159
+ FROM session_message WHERE session_id = ? AND seq > ? AND seq <= ?
1160
+ ORDER BY seq ASC`).all(sessionID, after, through);
1161
+ const stamps = new Map;
1162
+ for (const row of rows)
1163
+ stamps.set(row.seq, `${row.id}\x00${row.updated ?? ""}\x00${row.bytes ?? ""}`);
1164
+ return stamps;
1165
+ }
1166
+ rawRowsThrough(sessionID, throughSeq, limit) {
1167
+ return trackDecodeOperation("rawRowsThrough", () => {
1168
+ if (!Number.isSafeInteger(throughSeq))
1169
+ throw new Error("Invalid seq upper bound");
1170
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e4)
1171
+ throw new Error("Invalid page limit");
1172
+ const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
1173
+ return this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
1174
+ WHERE session_id = ? AND type IN (${rawTypes}) AND seq <= ?
1175
+ ORDER BY seq DESC LIMIT ?`).all(sessionID, ...RAW_MESSAGE_TYPES, throughSeq, limit).map(decode);
1176
+ });
1177
+ }
1178
+ sequenceForId(sessionID, id) {
1179
+ if (!id)
1180
+ return;
1181
+ const row = this.prepare("SELECT seq FROM session_message WHERE session_id = ? AND id = ? LIMIT 1").get(sessionID, id);
1182
+ return typeof row?.seq === "number" ? row.seq : undefined;
1183
+ }
1184
+ syntheticMessageIDs(sessionID) {
1185
+ const rows = this.prepare("SELECT id FROM session_message WHERE session_id = ? AND type = 'synthetic'").all(sessionID);
1186
+ return new Set(rows.map((row) => row.id));
1187
+ }
1188
+ forkOrigin(sessionID) {
1189
+ const columns = new Set(this.prepare("PRAGMA table_info(session_v2)").all().map((column) => column.name));
1190
+ if (!columns.has("fork_session_id") || !columns.has("fork_boundary"))
1191
+ return null;
1192
+ const row = this.prepare("SELECT fork_session_id, fork_boundary FROM session_v2 WHERE id = ?").get(sessionID);
1193
+ if (!row?.fork_session_id || !row.fork_boundary)
1194
+ return null;
1195
+ try {
1196
+ const boundary = JSON.parse(row.fork_boundary);
1197
+ if (boundary.type !== "before" && boundary.type !== "through" || typeof boundary.messageID !== "string" || boundary.messageID.length === 0)
1198
+ return null;
1199
+ return {
1200
+ parentSessionID: row.fork_session_id,
1201
+ boundary: { type: boundary.type, messageID: boundary.messageID }
1202
+ };
1203
+ } catch {
1204
+ return null;
1205
+ }
1206
+ }
1207
+ sessionExists(sessionID) {
1208
+ return this.prepare("SELECT 1 FROM session_v2 WHERE id = ?").get(sessionID) != null;
1209
+ }
1210
+ rowStampsThrough(sessionID, throughSeq) {
1211
+ return this.prepare(`SELECT id, type, seq, time_created FROM session_message
1212
+ WHERE session_id = ? AND seq <= ? ORDER BY seq ASC`).all(sessionID, throughSeq);
1213
+ }
1214
+ latestSequenceForIds(sessionID, ids) {
1215
+ let latest = -1;
1216
+ for (let offset = 0;offset < ids.length; offset += 500) {
1217
+ const chunk = ids.slice(offset, offset + 500);
1218
+ const row = this.prepare(`SELECT MAX(seq) AS seq FROM session_message
1219
+ WHERE session_id = ? AND id IN (${chunk.map(() => "?").join(", ")})`).get(sessionID, ...chunk);
1220
+ if (typeof row?.seq === "number")
1221
+ latest = Math.max(latest, row.seq);
1222
+ }
1223
+ return latest;
1224
+ }
1225
+ compactionByStatus(sessionID, status) {
1226
+ const row = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
1227
+ WHERE session_id = ? AND type = 'compaction'
1228
+ AND json_extract(data, '$.status') = ?
1229
+ ORDER BY seq DESC LIMIT 1`).get(sessionID, status);
1230
+ return row ? decode(row) : undefined;
1231
+ }
1232
+ latestCompaction(sessionID) {
1233
+ return trackDecodeOperation("latestCompaction", () => this.compactionByStatus(sessionID, "completed"));
1234
+ }
1235
+ latestRunningCompaction(sessionID) {
1236
+ return trackDecodeOperation("latestRunningCompaction", () => this.compactionByStatus(sessionID, "running"));
1237
+ }
1238
+ idleRows(sessionID, after = -1) {
1239
+ return trackDecodeOperation("idleRows", () => this.all(sessionID, after, "idle"));
1240
+ }
1241
+ earliestSequence(sessionID) {
1242
+ const row = this.prepare("SELECT MIN(seq) AS seq FROM session_message WHERE session_id = ?").get(sessionID);
1243
+ return typeof row?.seq === "number" ? row.seq : undefined;
1244
+ }
1245
+ latestSequence(sessionID) {
1246
+ const row = this.prepare("SELECT MAX(seq) AS seq FROM session_message WHERE session_id = ?").get(sessionID);
1247
+ return typeof row?.seq === "number" ? row.seq : -1;
1248
+ }
1249
+ assistantSince(sessionID, afterSeq) {
1250
+ return trackDecodeOperation("assistantSince", () => {
1251
+ const rows = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
1252
+ WHERE session_id = ? AND type = 'assistant' AND seq > ?
1253
+ ORDER BY seq ASC`).all(sessionID, afterSeq);
1254
+ return rows.map((row) => decode(row));
1255
+ });
1256
+ }
1257
+ latestAssistant(sessionID) {
1258
+ return trackDecodeOperation("latestAssistant", () => {
1259
+ const row = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
1260
+ WHERE session_id = ? AND type = 'assistant'
1261
+ ORDER BY seq DESC LIMIT 1`).get(sessionID);
1262
+ return row ? decode(row) : undefined;
1263
+ });
1264
+ }
1265
+ latestIdle(sessionID) {
1266
+ return trackDecodeOperation("latestIdle", () => {
1267
+ const row = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
1268
+ WHERE session_id = ? AND type = 'idle'
1269
+ ORDER BY seq DESC LIMIT 1`).get(sessionID);
1270
+ return row ? decode(row) : undefined;
1271
+ });
1272
+ }
1273
+ window(sessionID) {
1274
+ return trackDecodeOperation("window", () => this.db.transaction(() => {
1275
+ const cut = this.latestCompaction(sessionID);
1276
+ return this.all(sessionID, cut ? cut.seq - 1 : -1);
1277
+ }).deferred());
1278
+ }
1279
+ history(sessionID) {
1280
+ return trackDecodeOperation("history", () => this.all(sessionID, -1));
1281
+ }
1282
+ all(sessionID, after, type, through) {
1283
+ const rows = [];
1284
+ for (;; ) {
1285
+ const page = this.page(sessionID, { after, through, type });
1286
+ rows.push(...page.rows);
1287
+ if (page.rows.length < 100 || page.cursor === undefined)
1288
+ return rows;
1289
+ after = page.cursor;
1290
+ }
1291
+ }
519
1292
  }
520
1293
 
521
1294
  // src/features/magic-context/session-activity.ts
@@ -611,6 +1384,22 @@ var SESSION_SCOPED_TABLES = [
611
1384
  { table: "lkg_slots" },
612
1385
  { table: "lkg_slot_chunks" }
613
1386
  ];
1387
+ var cleanupStatements = new WeakMap;
1388
+ function cleanupStatement(db, sql) {
1389
+ let cache = cleanupStatements.get(db);
1390
+ if (!cache) {
1391
+ cache = new Map;
1392
+ cleanupStatements.set(db, cache);
1393
+ }
1394
+ let statement = cache.get(sql);
1395
+ if (!statement) {
1396
+ if (cache.size >= 256)
1397
+ cache.clear();
1398
+ statement = prepareCachedStatement2(db, sql);
1399
+ cache.set(sql, statement);
1400
+ }
1401
+ return statement;
1402
+ }
614
1403
  function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
615
1404
  if (sessionIds.length === 0)
616
1405
  return 0;
@@ -618,7 +1407,7 @@ function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
618
1407
  if (options.rustModuleCleanupAcknowledged !== true) {
619
1408
  const placeholders = sessionIds.map(() => "?").join(", ");
620
1409
  const harnessPredicate = harness === undefined ? "" : " AND harness = ?";
621
- const protectedRows = db.prepare(`SELECT session_id
1410
+ const protectedRows = cleanupStatement(db, `SELECT session_id
622
1411
  FROM pending_session_cleanup
623
1412
  WHERE session_id IN (${placeholders})
624
1413
  AND harness LIKE '%:rust'${harnessPredicate}`).all(...sessionIds, ...harness === undefined ? [] : [`${harness}:rust`]);
@@ -630,11 +1419,11 @@ function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
630
1419
  const placeholders = deletableSessionIds.map(() => "?").join(", ");
631
1420
  const harnessTables = SESSION_SCOPED_TABLES.filter((definition) => definition.harnessScoped);
632
1421
  if (options.preserveTagRevisionTombstones) {
633
- const owners = db.prepare(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));
634
- const record = db.prepare("INSERT OR IGNORE INTO schema_migrations_meta (key, value) VALUES (?, ?)");
1422
+ 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));
1423
+ const record = cleanupStatement(db, "INSERT OR IGNORE INTO schema_migrations_meta (key, value) VALUES (?, ?)");
635
1424
  for (const owner of owners)
636
1425
  record.run(tagTombstoneOwnerKey(owner.session_id, owner.harness), owner.harness);
637
- const fallback = db.prepare(`INSERT INTO schema_migrations_meta (key, value)
1426
+ const fallback = cleanupStatement(db, `INSERT INTO schema_migrations_meta (key, value)
638
1427
  SELECT ?, ? WHERE NOT EXISTS (SELECT 1 FROM schema_migrations_meta WHERE key >= ? AND key < ?)`);
639
1428
  for (const id of deletableSessionIds)
640
1429
  fallback.run(tagTombstoneOwnerKey(id, getHarness2()), getHarness2(), ...tagTombstoneOwnerRange(id));
@@ -643,13 +1432,13 @@ function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
643
1432
  deleteChunkEmbedBackoffForSession(db, sessionId);
644
1433
  for (const definition of SESSION_SCOPED_TABLES) {
645
1434
  if (definition.table === "message_history_fts") {
646
- db.prepare(`DELETE FROM message_history_fts
1435
+ cleanupStatement(db, `DELETE FROM message_history_fts
647
1436
  WHERE rowid IN (
648
1437
  SELECT fts_rowid
649
1438
  FROM message_fts_rowid_map
650
1439
  WHERE session_id IN (${placeholders})
651
1440
  )`).run(...deletableSessionIds);
652
- deleteUnmappedMessageFtsRows(db, deletableSessionIds);
1441
+ deleteUnmappedMessageFtsRows2(db, deletableSessionIds);
653
1442
  continue;
654
1443
  }
655
1444
  const predicates = [`session_id IN (${placeholders})`];
@@ -658,7 +1447,7 @@ function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
658
1447
  const bindHarness = harness !== undefined && definition.harnessScoped === true;
659
1448
  if (bindHarness)
660
1449
  predicates.push("harness = ?");
661
- const statement = db.prepare(`DELETE FROM ${definition.table} WHERE ${predicates.join(" AND ")}`);
1450
+ const statement = cleanupStatement(db, `DELETE FROM ${definition.table} WHERE ${predicates.join(" AND ")}`);
662
1451
  if (bindHarness)
663
1452
  statement.run(...deletableSessionIds, harness);
664
1453
  else
@@ -666,9 +1455,9 @@ function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
666
1455
  }
667
1456
  deleteSessionActivity(db, deletableSessionIds);
668
1457
  if (!options.preserveTagRevisionTombstones) {
669
- const retireOwner = db.prepare(harness === undefined ? "DELETE FROM schema_migrations_meta WHERE key >= ? AND key < ?" : "DELETE FROM schema_migrations_meta WHERE key = ?");
1458
+ const retireOwner = cleanupStatement(db, harness === undefined ? "DELETE FROM schema_migrations_meta WHERE key >= ? AND key < ?" : "DELETE FROM schema_migrations_meta WHERE key = ?");
670
1459
  const survivingRows = harnessTables.map((definition) => `EXISTS (SELECT 1 FROM ${definition.table} WHERE session_id = ?${definition.extraPredicate ? ` AND ${definition.extraPredicate}` : ""})`).join(" OR ");
671
- const retire = db.prepare(`DELETE FROM schema_migrations_meta WHERE key IN (?, ?)
1460
+ const retire = cleanupStatement(db, `DELETE FROM schema_migrations_meta WHERE key IN (?, ?)
672
1461
  AND NOT (${survivingRows})
673
1462
  AND NOT EXISTS (SELECT 1 FROM schema_migrations_meta AS owners WHERE owners.key >= ? AND owners.key < ?)`);
674
1463
  for (const id of deletableSessionIds) {
@@ -679,10 +1468,59 @@ function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
679
1468
  retire.run(tagIdentityRevisionKey(id), tagRevisionKey(id), ...harnessTables.map(() => id), ...tagTombstoneOwnerRange(id));
680
1469
  }
681
1470
  }
682
- for (const sessionId of deletableSessionIds)
683
- clearLkgRecoveryFence(sessionId);
1471
+ if (!options.deferRecoveryFenceCleanup)
1472
+ for (const sessionId of deletableSessionIds)
1473
+ clearLkgRecoveryFence(sessionId);
684
1474
  return deletableSessionIds.length;
685
1475
  }
1476
+ var SESSION_CLEANUP_SLICE_ROWS = 64;
1477
+ function deleteSessionScopedRowsSlice(db, sessionId, harness) {
1478
+ if (cleanupStatement(db, "SELECT 1 FROM pending_session_cleanup WHERE session_id = ? AND harness = ?").get(sessionId, `${harness}:rust`))
1479
+ return { complete: true, deleted: 0 };
1480
+ cleanupStatement(db, "INSERT OR IGNORE INTO schema_migrations_meta(key,value) VALUES (?,?)").run(tagTombstoneOwnerKey(sessionId, harness), harness);
1481
+ let remaining = SESSION_CLEANUP_SLICE_ROWS;
1482
+ const started = performance.now();
1483
+ remaining -= deleteChunkEmbedBackoffForSession(db, sessionId, remaining);
1484
+ if (remaining <= 0)
1485
+ return { complete: false, deleted: 0 };
1486
+ for (const definition of SESSION_SCOPED_TABLES) {
1487
+ if (definition.table === "message_history_fts") {
1488
+ const rows = cleanupStatement(db, "SELECT fts_rowid AS id FROM message_fts_rowid_map WHERE session_id = ? LIMIT ?").all(sessionId, remaining);
1489
+ const removeFts = cleanupStatement(db, "DELETE FROM message_history_fts WHERE rowid = ?");
1490
+ const removeMap = cleanupStatement(db, "DELETE FROM message_fts_rowid_map WHERE session_id = ? AND fts_rowid = ?");
1491
+ for (const row of rows) {
1492
+ removeFts.run(row.id);
1493
+ removeMap.run(sessionId, row.id);
1494
+ }
1495
+ remaining -= rows.length;
1496
+ if (remaining > 0 && !getMessageFtsRowidMapBackfillProgress2(db).completed)
1497
+ remaining -= cleanupStatement(db, `DELETE FROM message_history_fts WHERE rowid IN (
1498
+ SELECT rowid FROM message_history_fts WHERE session_id = ? LIMIT ?
1499
+ )`).run(sessionId, remaining).changes;
1500
+ } else {
1501
+ const predicates = ["session_id = ?"];
1502
+ if (definition.extraPredicate)
1503
+ predicates.push(definition.extraPredicate);
1504
+ if (definition.harnessScoped)
1505
+ predicates.push("harness = ?");
1506
+ const args = [sessionId, ...definition.harnessScoped ? [harness] : [], remaining];
1507
+ const withoutRowid = definition.table === "temporal_decisions" || definition.table === "session_replay_decisions";
1508
+ const key = withoutRowid ? "message_id" : "rowid";
1509
+ remaining -= cleanupStatement(db, `DELETE FROM ${definition.table}
1510
+ WHERE ${withoutRowid ? "session_id = ? AND " : ""}${key} IN (
1511
+ SELECT ${key} FROM ${definition.table} WHERE ${predicates.join(" AND ")} LIMIT ?
1512
+ )`).run(...withoutRowid ? [sessionId] : [], ...args).changes;
1513
+ }
1514
+ if (remaining <= 0 || performance.now() - started >= 10)
1515
+ return { complete: false, deleted: 0 };
1516
+ }
1517
+ return {
1518
+ complete: true,
1519
+ deleted: deleteSessionScopedRows(db, [sessionId], harness, {
1520
+ deferRecoveryFenceCleanup: true
1521
+ })
1522
+ };
1523
+ }
686
1524
 
687
1525
  // src/features/magic-context/message-index.ts
688
1526
  var MESSAGE_HISTORY_ORPHAN_SWEEP_BATCH_SIZE = 200;
@@ -896,7 +1734,7 @@ function getDeleteMessageFtsMapStatement(db) {
896
1734
  }
897
1735
  function insertMessageFtsRow(db, sessionId, messageOrdinal, messageId, role, content, messageTimeMs) {
898
1736
  const result = getInsertMessageStatement(db).run(sessionId, messageOrdinal, messageId, role, content);
899
- recordMessageFtsRowid(db, sessionId, messageOrdinal, result.lastInsertRowid, messageTimeMs ?? null);
1737
+ recordMessageFtsRowid2(db, sessionId, messageOrdinal, result.lastInsertRowid, messageTimeMs ?? null);
900
1738
  }
901
1739
  function normalizeSourceVersion(version) {
902
1740
  if (typeof version === "number")
@@ -913,7 +1751,7 @@ function toMessageIndexSource2(message) {
913
1751
  role: message.role,
914
1752
  createdAt: message.createdAt,
915
1753
  sourceVersion: normalizeSourceVersion(message.version),
916
- contentHash: createHash3("sha256").update(content).digest("hex"),
1754
+ contentHash: createHash4("sha256").update(content).digest("hex"),
917
1755
  content
918
1756
  };
919
1757
  }
@@ -996,7 +1834,7 @@ function deleteIndexedMessage2(db, sessionId, messageId) {
996
1834
  function clearIndexedMessagesInTransaction2(db, sessionId) {
997
1835
  deleteMessageIndexSkips(db, sessionId);
998
1836
  getDeleteFtsStatement(db).run(sessionId);
999
- deleteUnmappedMessageFtsRows(db, [sessionId]);
1837
+ deleteUnmappedMessageFtsRows2(db, [sessionId]);
1000
1838
  getDeleteFtsMapStatement(db).run(sessionId);
1001
1839
  getDeleteMessageSourceStatement(db).run(sessionId);
1002
1840
  getDeleteIndexStatement(db).run(sessionId);
@@ -1015,10 +1853,10 @@ function getIndexableContent2(role, parts) {
1015
1853
  if (!hasMeaningfulUserText2(parts)) {
1016
1854
  return "";
1017
1855
  }
1018
- return extractTexts2(parts).map(cleanUserText2).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
1856
+ return extractTexts2(parts).map(removeTaskNotifications).map(cleanUserText2).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
1019
1857
  }
1020
1858
  if (role === "assistant") {
1021
- return extractTexts2(parts).map(removeSystemReminders).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
1859
+ return extractTexts2(parts).map(removeTaskNotifications).map(removeSystemReminders).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
1022
1860
  }
1023
1861
  return "";
1024
1862
  }
@@ -1027,10 +1865,10 @@ function indexSingleMessageInTransaction(db, sessionId, message, now, dirtyFloor
1027
1865
  const dirtyFloor = getDirtyIndexFloor(db, sessionId);
1028
1866
  if (message.ordinal <= currentWatermark) {
1029
1867
  if (isIndexSourceCurrent(db, sessionId, source)) {
1030
- recordIndexedMessageTime(db, sessionId, message.ordinal, message.createdAt);
1868
+ recordIndexedMessageTime2(db, sessionId, message.ordinal, message.createdAt);
1031
1869
  return false;
1032
1870
  }
1033
- if (!messageFtsOrdinalRangeIsMapped(db, sessionId, message.ordinal, message.ordinal)) {
1871
+ if (!messageFtsOrdinalRangeIsMapped2(db, sessionId, message.ordinal, message.ordinal)) {
1034
1872
  return false;
1035
1873
  }
1036
1874
  getDeleteMessageFtsStatement(db).run(sessionId, message.id);
@@ -1060,7 +1898,7 @@ function indexSingleMessage2(db, sessionId, message, shouldCommit = () => true,
1060
1898
  const source = preparedSource ?? getMessageSourceSnapshot(message);
1061
1899
  const currentWatermark = getLastIndexedOrdinal2(db, sessionId);
1062
1900
  if (message.ordinal <= currentWatermark && isIndexSourceCurrent(db, sessionId, source)) {
1063
- recordIndexedMessageTime(db, sessionId, message.ordinal, message.createdAt);
1901
+ recordIndexedMessageTime2(db, sessionId, message.ordinal, message.createdAt);
1064
1902
  return false;
1065
1903
  }
1066
1904
  const dirtyFloorBeforeAttempt = getDirtyIndexFloor(db, sessionId);
@@ -1101,7 +1939,7 @@ function indexItemsAfterOrdinal(db, sessionId, items, finalWatermark, toSource)
1101
1939
  const currentWatermark = getLastIndexedOrdinal2(db, sessionId);
1102
1940
  const dirtyFloor = getDirtyIndexFloor(db, sessionId);
1103
1941
  const effectiveWatermark = dirtyFloor === null ? currentWatermark : Math.min(currentWatermark, Math.max(0, dirtyFloor - 1));
1104
- if (dirtyFloor !== null && dirtyFloor <= currentWatermark && !messageFtsOrdinalRangeIsMapped(db, sessionId, dirtyFloor, Math.min(currentWatermark, finalWatermark))) {
1942
+ if (dirtyFloor !== null && dirtyFloor <= currentWatermark && !messageFtsOrdinalRangeIsMapped2(db, sessionId, dirtyFloor, Math.min(currentWatermark, finalWatermark))) {
1105
1943
  db.exec("COMMIT");
1106
1944
  committed = true;
1107
1945
  logSlowWriteTransaction("message_index_reconcile", transactionStartedAt);
@@ -1153,7 +1991,7 @@ function indexItemsAfterOrdinal(db, sessionId, items, finalWatermark, toSource)
1153
1991
  return inserted;
1154
1992
  }
1155
1993
  function harnessSupportsOpenCodeOrphanSweep(harness) {
1156
- return harness === "opencode";
1994
+ return harness === "opencode" || harness === "opencode2";
1157
1995
  }
1158
1996
  function getMessageHistoryOrphanSweepState(db, harness) {
1159
1997
  return db.prepare("SELECT cursor_session_id, last_swept_at FROM message_history_orphan_sweep WHERE harness = ?").get(harness) ?? {};
@@ -1182,7 +2020,7 @@ function getOpenCodeSessionScopedCandidateSourceSql(harness) {
1182
2020
  UNION
1183
2021
  `);
1184
2022
  }
1185
- function sweepOrphanedOpenCodeMessageIndexesInBackground(db, openReadableOpenCodeDb, options = {}) {
2023
+ function* sweepOrphanedOpenCodeMessageIndexesInBackground(db, openReadableOpenCodeDb, options = {}) {
1186
2024
  const now = options.now ?? Date.now();
1187
2025
  const batchSize = Math.max(1, Math.floor(options.batchSize ?? MESSAGE_HISTORY_ORPHAN_SWEEP_BATCH_SIZE));
1188
2026
  const safetyAgeMs = Math.max(0, options.safetyAgeMs ?? MESSAGE_HISTORY_ORPHAN_SAFETY_AGE_MS);
@@ -1196,15 +2034,6 @@ function sweepOrphanedOpenCodeMessageIndexesInBackground(db, openReadableOpenCod
1196
2034
  }
1197
2035
  if (!harnessSupportsOpenCodeOrphanSweep(harness)) {
1198
2036
  persistMessageHistoryOrphanSweepState(db, cursor, now + unavailableReprobeMs - cooldownMs, harness);
1199
- if (harness === "opencode2") {
1200
- return {
1201
- status: "unavailable",
1202
- scanned: 0,
1203
- deleted: 0,
1204
- cursor,
1205
- reason: "OpenCode 2 keeps its sessions in session_v2, which this sweep does not read"
1206
- };
1207
- }
1208
2037
  return { status: "unavailable", scanned: 0, deleted: 0, cursor };
1209
2038
  }
1210
2039
  let openCodeDb = null;
@@ -1217,7 +2046,27 @@ function sweepOrphanedOpenCodeMessageIndexesInBackground(db, openReadableOpenCod
1217
2046
  persistMessageHistoryOrphanSweepState(db, cursor, now + unavailableReprobeMs - cooldownMs, harness);
1218
2047
  return { status: "source_unavailable", scanned: 0, deleted: 0, cursor };
1219
2048
  }
2049
+ let v2Reader;
1220
2050
  try {
2051
+ let sessionExists;
2052
+ if (harness === "opencode2") {
2053
+ try {
2054
+ const path = getSqliteDatabasePath2(openCodeDb);
2055
+ if (!path)
2056
+ throw new Error("OpenCode 2 source has no durable path");
2057
+ v2Reader = new V2StoreReader(path);
2058
+ const liveIds = new Set(v2Reader.sessionIds());
2059
+ if (liveIds.size === 0)
2060
+ throw new Error("OpenCode 2 source has no sessions");
2061
+ sessionExists = (id) => liveIds.has(id);
2062
+ } catch {
2063
+ persistMessageHistoryOrphanSweepState(db, cursor, now + unavailableReprobeMs - cooldownMs, harness);
2064
+ return { status: "source_unavailable", scanned: 0, deleted: 0, cursor };
2065
+ }
2066
+ } else {
2067
+ const query = openCodeDb.prepare("SELECT 1 FROM session WHERE id = ? LIMIT 1");
2068
+ sessionExists = (id) => !!query.get(id);
2069
+ }
1221
2070
  const cutoff = now - safetyAgeMs;
1222
2071
  const candidateSourceSql = getOpenCodeSessionScopedCandidateSourceSql(harness);
1223
2072
  const candidates = db.prepare(`SELECT session_id
@@ -1232,39 +2081,56 @@ function sweepOrphanedOpenCodeMessageIndexesInBackground(db, openReadableOpenCod
1232
2081
  )
1233
2082
  ORDER BY session_id ASC
1234
2083
  LIMIT ?`).all(cursor, cutoff, batchSize);
1235
- const sessionExists = openCodeDb.prepare("SELECT 1 FROM session WHERE id = ? LIMIT 1");
1236
- const missingSessionIds = candidates.filter((candidate) => !sessionExists.get(candidate.session_id)).map((candidate) => candidate.session_id);
2084
+ const missingSessionIds = candidates.filter((candidate) => !sessionExists(candidate.session_id)).map((candidate) => candidate.session_id);
1237
2085
  const nextCursor = candidates.length < batchSize ? "" : candidates[candidates.length - 1]?.session_id ?? cursor;
1238
2086
  const completedAt = candidates.length < batchSize ? now : null;
1239
- db.exec("BEGIN IMMEDIATE");
1240
- const transactionStartedAt = performance.now();
1241
- let committed = false;
1242
2087
  let deleted = 0;
1243
- try {
1244
- const stillEligible = db.prepare(`SELECT 1
1245
- FROM (${candidateSourceSql}) AS session_candidates
1246
- WHERE session_id = ?
1247
- AND NOT EXISTS (
1248
- SELECT 1
1249
- FROM message_history_index
1250
- WHERE message_history_index.session_id = session_candidates.session_id
1251
- AND message_history_index.harness = '${harness}'
1252
- AND message_history_index.updated_at > ?
1253
- )
1254
- LIMIT 1`);
1255
- const eligibleSessionIds = missingSessionIds.filter((sessionId) => !sessionExists.get(sessionId) && stillEligible.get(sessionId, cutoff));
1256
- deleted = deleteSessionScopedRows(db, eligibleSessionIds, harness);
1257
- persistMessageHistoryOrphanSweepState(db, nextCursor, completedAt, harness);
1258
- db.exec("COMMIT");
1259
- committed = true;
1260
- logSlowWriteTransaction("message_index_orphan_sweep", transactionStartedAt);
1261
- } finally {
1262
- if (!committed) {
2088
+ let slices = 0;
2089
+ const stillEligible = db.prepare(`SELECT 1 WHERE NOT EXISTS (
2090
+ SELECT 1 FROM message_history_index
2091
+ WHERE session_id = ? AND harness = '${harness}' AND updated_at > ?
2092
+ )`);
2093
+ for (const sessionId of missingSessionIds) {
2094
+ let complete = false;
2095
+ do {
2096
+ let removed = 0;
2097
+ db.exec("BEGIN IMMEDIATE");
2098
+ const transactionStartedAt = performance.now();
2099
+ let committed = false;
1263
2100
  try {
1264
- db.exec("ROLLBACK");
1265
- } catch {}
1266
- }
2101
+ const hostStillMissing = v2Reader ? !v2Reader.sessionExists(sessionId) : !sessionExists(sessionId);
2102
+ if (hostStillMissing && stillEligible.get(sessionId, cutoff)) {
2103
+ const slice = deleteSessionScopedRowsSlice(db, sessionId, harness);
2104
+ removed = slice.deleted;
2105
+ complete = slice.complete;
2106
+ } else
2107
+ complete = true;
2108
+ db.exec("COMMIT");
2109
+ committed = true;
2110
+ logSlowWriteTransaction("message_index_orphan_sweep", transactionStartedAt);
2111
+ } finally {
2112
+ if (!committed) {
2113
+ try {
2114
+ db.exec("ROLLBACK");
2115
+ } catch {}
2116
+ }
2117
+ }
2118
+ deleted += removed;
2119
+ if (removed)
2120
+ clearLkgRecoveryFence(sessionId);
2121
+ yield;
2122
+ if (++slices >= Math.max(1, options.maxSlices ?? 512)) {
2123
+ return {
2124
+ status: "swept",
2125
+ scanned: candidates.length,
2126
+ deleted,
2127
+ cursor,
2128
+ partial: true
2129
+ };
2130
+ }
2131
+ } while (!complete);
1267
2132
  }
2133
+ persistMessageHistoryOrphanSweepState(db, nextCursor, completedAt, harness);
1268
2134
  return {
1269
2135
  status: "swept",
1270
2136
  scanned: candidates.length,
@@ -1272,11 +2138,24 @@ function sweepOrphanedOpenCodeMessageIndexesInBackground(db, openReadableOpenCod
1272
2138
  cursor: nextCursor
1273
2139
  };
1274
2140
  } finally {
2141
+ v2Reader?.close();
1275
2142
  closeQuietly(openCodeDb);
1276
2143
  }
1277
2144
  }
1278
- function sweepOrphanedOpenCodeMessageIndexes2(db, openReadableOpenCodeDb, options = {}) {
1279
- return withoutSqliteTransformPass2(() => withSqliteBackgroundWriter2(() => sweepOrphanedOpenCodeMessageIndexesInBackground(db, openReadableOpenCodeDb, options)));
2145
+ async function sweepOrphanedOpenCodeMessageIndexesCooperatively2(db, openSource, options = {}) {
2146
+ return withoutSqliteTransformPass2(() => withSqliteBackgroundWriter2(async () => {
2147
+ const sweep = sweepOrphanedOpenCodeMessageIndexesInBackground(db, openSource, options);
2148
+ try {
2149
+ for (;; ) {
2150
+ const step = sweep.next();
2151
+ if (step.done)
2152
+ return step.value;
2153
+ await new Promise((resolve) => setTimeout(resolve, 0));
2154
+ }
2155
+ } finally {
2156
+ sweep.return({ status: "source_unavailable", scanned: 0, deleted: 0, cursor: "" });
2157
+ }
2158
+ }));
1280
2159
  }
1281
2160
 
1282
- export { BoundedSessionMap, clearCompressionDepth, clearCompressionDepthRange, LkgRecoveryFenceError, clearLkgFenceProcessState, lkgServedArrayDigest, hasCleanLkgRecoveryFence, lkgFenceRequiresRebuild, lkgRecoveryFenceUnclean, dirtyLkgRecoveryRevision, ownsLkgDirtyFence, invalidateCleanLkgRecoveryFence, prepareLkgCaptureFence, certifyCommittedLkgCapture, markLkgRecoveryDirty, commitLkgWithRecoveryFence, clearLkgRecoveryFence, advanceSessionActivity, backfillSessionActivity, deleteSessionScopedRows, MESSAGE_INDEX_MAX_FTS_BYTES2, messageIndexSkipKey2, toMessageIndexSource2, getMessageIndexSourceIdentity2, isMessageIndexSourceCurrent2, getLastIndexedOrdinal2, getIndexedMessageCorpusSize2, markMessageIndexDirty2, getMessageIndexReconciliationStartOrdinal2, isMessageIndexReconciledThrough2, deleteIndexedMessage2, clearIndexedMessagesInTransaction2, clearIndexedMessages2, getIndexableContent2, indexSingleMessage2, indexMessagesAfterOrdinal2, indexSourcesAfterOrdinal2, sweepOrphanedOpenCodeMessageIndexes2 };
2161
+ export { BoundedSessionMap, clearCompressionDepth, clearCompressionDepthRange, LkgRecoveryFenceError, clearLkgFenceProcessState, lkgServedArrayDigest, hasCleanLkgRecoveryFence, lkgFenceRequiresRebuild, lkgRecoveryFenceUnclean, dirtyLkgRecoveryRevision, ownsLkgDirtyFence, invalidateCleanLkgRecoveryFence, prepareLkgCaptureFence, certifyCommittedLkgCapture, markLkgRecoveryDirty, commitLkgWithRecoveryFence, clearLkgRecoveryFence, cleanSearchText, keywordRelevance, matchedSearchSnippet, gaDatabasePath, RAW_MESSAGE_TYPES, V2StoreReaderPool, V2StoreReader, 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 };