@cortexkit/opencode-magic-context 0.49.16 → 0.49.18

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 +11 -11
  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 +394 -394
  6. package/dist/{conflict-warning-hook-znry4j76.js → conflict-warning-hook-4pmz879v.js} +5 -5
  7. package/dist/{context-3znk23va.js → context-m897gfp9.js} +304 -145
  8. package/dist/{embedding-backfill-q4n8dend.js → embedding-backfill-fmz50tsy.js} +11 -11
  9. package/dist/embedding-worker-process.js +1 -1
  10. package/dist/embedding-worker.js +4 -4
  11. package/dist/features/magic-context/compartment-chunk-embedding.d.ts +1 -1
  12. package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -1
  13. package/dist/features/magic-context/git-commits/indexer.d.ts.map +1 -1
  14. package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
  15. package/dist/features/magic-context/memory/project-identity-cache.d.ts +2 -0
  16. package/dist/features/magic-context/memory/project-identity-cache.d.ts.map +1 -1
  17. package/dist/features/magic-context/message-index-async.d.ts +1 -1
  18. package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
  19. package/dist/features/magic-context/message-index.d.ts +5 -0
  20. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  21. package/dist/features/magic-context/search.d.ts +6 -1
  22. package/dist/features/magic-context/search.d.ts.map +1 -1
  23. package/dist/features/magic-context/storage-host-instruction-retention.d.ts +11 -0
  24. package/dist/features/magic-context/storage-host-instruction-retention.d.ts.map +1 -0
  25. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  26. package/dist/features/magic-context/storage-meta-shared.d.ts +2 -2
  27. package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
  28. package/dist/features/magic-context/storage-session-tables.d.ts +9 -1
  29. package/dist/features/magic-context/storage-session-tables.d.ts.map +1 -1
  30. package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
  31. package/dist/hooks/magic-context/auto-search-deadline.d.ts +2 -1
  32. package/dist/hooks/magic-context/auto-search-deadline.d.ts.map +1 -1
  33. package/dist/hooks/magic-context/auto-search-hint.d.ts +3 -0
  34. package/dist/hooks/magic-context/auto-search-hint.d.ts.map +1 -1
  35. package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -1
  36. package/dist/hooks/magic-context/auto-search-worker-client.d.ts +10 -2
  37. package/dist/hooks/magic-context/auto-search-worker-client.d.ts.map +1 -1
  38. package/dist/hooks/magic-context/cache-busting-signals.d.ts +22 -1
  39. package/dist/hooks/magic-context/cache-busting-signals.d.ts.map +1 -1
  40. package/dist/hooks/magic-context/compartment-runner-historian.d.ts +2 -0
  41. package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
  42. package/dist/hooks/magic-context/compartment-trigger.d.ts +4 -2
  43. package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
  44. package/dist/hooks/magic-context/file-token-coverage.d.ts +11 -0
  45. package/dist/hooks/magic-context/file-token-coverage.d.ts.map +1 -0
  46. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +9 -0
  47. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
  48. package/dist/hooks/magic-context/historian-host-runner.d.ts.map +1 -1
  49. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  50. package/dist/hooks/magic-context/host-served-rows.d.ts +25 -0
  51. package/dist/hooks/magic-context/host-served-rows.d.ts.map +1 -1
  52. package/dist/hooks/magic-context/inject-compartments.d.ts +7 -1
  53. package/dist/hooks/magic-context/inject-compartments.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/lkg-replay.d.ts.map +1 -1
  56. package/dist/hooks/magic-context/lkg-slot.d.ts +1 -1
  57. package/dist/hooks/magic-context/lkg-slot.d.ts.map +1 -1
  58. package/dist/hooks/magic-context/pass-outcome.d.ts +2 -0
  59. package/dist/hooks/magic-context/pass-outcome.d.ts.map +1 -1
  60. package/dist/hooks/magic-context/protected-tail-boundary.d.ts +2 -1
  61. package/dist/hooks/magic-context/protected-tail-boundary.d.ts.map +1 -1
  62. package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
  63. package/dist/hooks/magic-context/signed-thinking-hold.d.ts +59 -0
  64. package/dist/hooks/magic-context/signed-thinking-hold.d.ts.map +1 -0
  65. package/dist/hooks/magic-context/system-prompt-diagnostics.d.ts +8 -0
  66. package/dist/hooks/magic-context/system-prompt-diagnostics.d.ts.map +1 -0
  67. package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
  68. package/dist/hooks/magic-context/tag-content-primitives.d.ts.map +1 -1
  69. package/dist/hooks/magic-context/tag-messages.d.ts +6 -1
  70. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  71. package/dist/hooks/magic-context/todo-view.d.ts +2 -2
  72. package/dist/hooks/magic-context/tool-drop-target.d.ts +3 -2
  73. package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
  74. package/dist/hooks/magic-context/transform-compartment-phase.d.ts +6 -0
  75. package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
  76. package/dist/hooks/magic-context/transform-context-state.d.ts +19 -0
  77. package/dist/hooks/magic-context/transform-context-state.d.ts.map +1 -1
  78. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +19 -0
  79. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  80. package/dist/hooks/magic-context/transform-stage-logger.d.ts +3 -0
  81. package/dist/hooks/magic-context/transform-stage-logger.d.ts.map +1 -1
  82. package/dist/hooks/magic-context/transform.d.ts +1 -0
  83. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  84. package/dist/{index-9nkg2dzk.js → index-24fezq97.js} +3 -3
  85. package/dist/{index-v9asb16b.js → index-24jbw52d.js} +5 -5
  86. package/dist/{index-a8bvhwqs.js → index-2vxtcb95.js} +32 -3
  87. package/dist/{index-f71thw8j.js → index-4s3t0nd0.js} +1 -1
  88. package/dist/{index-6a6dq53h.js → index-6s5941tm.js} +5 -5
  89. package/dist/{index-fkx2qnvw.js → index-6x57ar45.js} +2 -2
  90. package/dist/{index-ek1yxvvz.js → index-936py2wm.js} +361 -70
  91. package/dist/{index-dqe8pq4x.js → index-a6mc0v4d.js} +6 -3
  92. package/dist/{index-hehkf6zm.js → index-b5vqckbw.js} +12 -6
  93. package/dist/{v2/server-wafzvyhg.js → index-beht37jy.js} +8 -0
  94. package/dist/{index-7b2cfmaz.js → index-bjcnntmz.js} +2 -2
  95. package/dist/{index-hqkczed7.js → index-c5pytzne.js} +22 -2
  96. package/dist/{index-nd7m2a5a.js → index-cf74k5ak.js} +27 -5
  97. package/dist/{index-kn4t1haw.js → index-e4xv8890.js} +531 -253
  98. package/dist/{index-cj9g0j7a.js → index-fgncb0ng.js} +7 -7
  99. package/dist/{index-5mwk1nek.js → index-jn12fvvh.js} +1 -1
  100. package/dist/{index-n6m03s3n.js → index-ke416mdc.js} +1 -1
  101. package/dist/{index-mtw9e71a.js → index-kq82mtg0.js} +2 -2
  102. package/dist/{index-n78avt2p.js → index-n37sebck.js} +1 -1
  103. package/dist/{index-wk0n1cgt.js → index-qn7xstt2.js} +2 -2
  104. package/dist/{index-v63beg1z.js → index-r4nkrs05.js} +3 -3
  105. package/dist/{index-j6zw70pf.js → index-rmwykbbb.js} +60 -21
  106. package/dist/{index-saews2s3.js → index-rr2bb45z.js} +1 -1
  107. package/dist/{index-ayym1x7f.js → index-s1ex9y6p.js} +195 -748
  108. package/dist/{index-7pa32z07.js → index-s734jcsr.js} +1 -1
  109. package/dist/{index-b17m64g9.js → index-s74zqahq.js} +1 -1
  110. package/dist/{index-4qfy7v57.js → index-s7j8tvhv.js} +31 -20
  111. package/dist/{index-wv9n2zcq.js → index-v8awdgvd.js} +4 -4
  112. package/dist/{index-p4vde22c.js → index-x4njr8df.js} +965 -77
  113. package/dist/{index-qrahzcqv.js → index-ze7mvk54.js} +10 -7
  114. package/dist/index.js +39 -39
  115. package/dist/{message-fts-rowid-map-yvx5a9jw.js → message-fts-rowid-map-17s0dfn4.js} +5 -3
  116. package/dist/{message-index-t4pz7akd.js → message-index-66jv6fh4.js} +13 -13
  117. package/dist/{message-index-bounded-6th8djy9.js → message-index-bounded-n1frdn85.js} +17 -17
  118. package/dist/{message-time-backfill-8egzkd3z.js → message-time-backfill-73mmm76q.js} +1 -1
  119. package/dist/migration-worker.js +8 -8
  120. package/dist/plugin/dream-timer.d.ts +11 -0
  121. package/dist/plugin/dream-timer.d.ts.map +1 -1
  122. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  123. package/dist/{project-embedding-registry-2dea0a77.js → project-embedding-registry-z74mhqkf.js} +10 -10
  124. package/dist/raw-ordinal-warmup-worker.js +10 -10
  125. package/dist/{read-session-chunk-4pwqxm5a.js → read-session-chunk-v06g3vka.js} +7 -7
  126. package/dist/{read-session-raw-5ar8se5v.js → read-session-raw-rsc8c7qy.js} +2 -2
  127. package/dist/{recomp-orchestrator-tjcaj5x7.js → recomp-orchestrator-ghpk2dtq.js} +24 -24
  128. package/dist/{search-archive-02jws7hw.js → search-archive-4d53y23e.js} +8 -8
  129. package/dist/{search-rb01n29x.js → search-nkf271d4.js} +20 -20
  130. package/dist/{send-session-notification-2x9edkrp.js → send-session-notification-6ve0e8p4.js} +4 -4
  131. package/dist/shared/historian-cache.d.ts +8 -0
  132. package/dist/shared/historian-cache.d.ts.map +1 -0
  133. package/dist/shared/model-suggestion-retry.d.ts +2 -0
  134. package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
  135. package/dist/shared/rpc-discovery-cleanup.d.ts +3 -0
  136. package/dist/shared/rpc-discovery-cleanup.d.ts.map +1 -0
  137. package/dist/shared/rpc-server.d.ts +2 -0
  138. package/dist/shared/rpc-server.d.ts.map +1 -1
  139. package/dist/shared/search-text.d.ts +8 -0
  140. package/dist/shared/search-text.d.ts.map +1 -0
  141. package/dist/shared/sqlite.d.ts +4 -2
  142. package/dist/shared/sqlite.d.ts.map +1 -1
  143. package/dist/shared/storage-file-mutex.d.ts.map +1 -1
  144. package/dist/shared/subagent-runner.d.ts +2 -0
  145. package/dist/shared/subagent-runner.d.ts.map +1 -1
  146. package/dist/shared/system-directive.d.ts +2 -0
  147. package/dist/shared/system-directive.d.ts.map +1 -1
  148. package/dist/{sqlite-4pqyynrs.js → sqlite-zf72c52d.js} +1 -1
  149. package/dist/{storage-memory-j3v3hc83.js → storage-memory-y3qmy00r.js} +2 -2
  150. package/dist/{storage-tags-4z1w27xa.js → storage-tags-r7k9tjcv.js} +8 -4
  151. package/dist/{transform-postprocess-phase-xaw5c3xp.js → transform-postprocess-phase-zpgav0h7.js} +24 -24
  152. package/dist/v2/auto-search-worker.js +76 -63
  153. package/dist/v2/chunk-embedding-write-worker.js +11 -11
  154. package/dist/v2/{compaction-marker-candidates-1vqwr3s5.js → compaction-marker-candidates-ebx3388k.js} +2 -2
  155. package/dist/v2/compaction-marker-candidates-worker.js +2 -2
  156. package/dist/v2/{context-acw1nhv8.js → context-f2xh7ba4.js} +1171 -448
  157. package/dist/v2/embedding-backfill-7c7yr69y.js +23 -0
  158. package/dist/v2/embedding-worker-process.js +1 -1
  159. package/dist/v2/embedding-worker.js +4 -4
  160. package/dist/v2/fold/native-replay.d.ts +11 -2
  161. package/dist/v2/fold/native-replay.d.ts.map +1 -1
  162. package/dist/v2/fold/policy.d.ts.map +1 -1
  163. package/dist/v2/fold/restore.d.ts.map +1 -1
  164. package/dist/v2/hooks/context.d.ts.map +1 -1
  165. package/dist/v2/hooks/hidden-child.d.ts +8 -0
  166. package/dist/v2/hooks/hidden-child.d.ts.map +1 -1
  167. package/dist/v2/hooks/payload.d.ts.map +1 -1
  168. package/dist/v2/hooks/types.d.ts +17 -0
  169. package/dist/v2/hooks/types.d.ts.map +1 -1
  170. package/dist/v2/{message-fts-rowid-map-8vf0v7cr.js → message-fts-rowid-map-wfx67gfd.js} +5 -3
  171. package/dist/v2/{message-index-e6qzw477.js → message-index-48f7hw7a.js} +13 -13
  172. package/dist/v2/{message-index-bounded-dsrf49y7.js → message-index-bounded-w3hfm29z.js} +17 -17
  173. package/dist/v2/{message-time-backfill-q5pgxf0d.js → message-time-backfill-04t0km1z.js} +1 -1
  174. package/dist/v2/migration-worker.js +8 -8
  175. package/dist/v2/{project-embedding-registry-rek75aqw.js → project-embedding-registry-gnkj56xp.js} +10 -10
  176. package/dist/v2/raw-ordinal-warmup-worker.js +10 -10
  177. package/dist/v2/{read-session-chunk-7bpygfv2.js → read-session-chunk-zpgmxwte.js} +7 -7
  178. package/dist/v2/{read-session-raw-pw2n1qgc.js → read-session-raw-yaegtjzh.js} +2 -2
  179. package/dist/v2/{recomp-orchestrator-hzpgg06e.js → recomp-orchestrator-4vfxej9t.js} +24 -24
  180. package/dist/v2/{search-archive-fn4wy8sx.js → search-archive-vv48s2aa.js} +8 -8
  181. package/dist/v2/{search-sf2vg5pk.js → search-kza414f0.js} +20 -20
  182. package/dist/v2/{send-session-notification-hmr7q0nt.js → send-session-notification-3pxn658x.js} +4 -4
  183. package/dist/v2/{server-n5q841qn.js → server-1nyybqan.js} +31 -20
  184. package/dist/v2/{server-785mb90s.js → server-1wy7b9de.js} +6 -3
  185. package/dist/v2/{server-0mzjjw3s.js → server-27gq4mfz.js} +27 -5
  186. package/dist/v2/{server-g59aatsz.js → server-2ehj0bw7.js} +965 -77
  187. package/dist/v2/{server-kqpawe1a.js → server-46neh5fa.js} +12 -6
  188. package/dist/v2/{server-rrqpvwqd.js → server-6tmj0s7j.js} +2 -2
  189. package/dist/v2/{server-kr2kc9dz.js → server-8by8qfec.js} +10 -7
  190. package/dist/v2/{server-r9gjxcte.js → server-a2eqhkxr.js} +7 -7
  191. package/dist/v2/{server-vbe4se57.js → server-a9xqj88f.js} +1 -1
  192. package/dist/{index-wafzvyhg.js → v2/server-beht37jy.js} +8 -0
  193. package/dist/v2/{server-6fyp0v67.js → server-bfpda1tb.js} +195 -749
  194. package/dist/v2/{server-05xjtq6c.js → server-d04vrx0j.js} +1 -1
  195. package/dist/v2/{server-zs8d0s3b.js → server-g3qma5c0.js} +1 -1
  196. package/dist/v2/{server-00hx04yn.js → server-gpvay0xz.js} +2 -2
  197. package/dist/v2/{server-gha9x4qq.js → server-h1v0pyxb.js} +2 -2
  198. package/dist/v2/{server-hgg55hde.js → server-hy3d3vsx.js} +1 -1
  199. package/dist/v2/{server-vgpab6rd.js → server-my7k4eds.js} +4 -4
  200. package/dist/v2/{server-d8tj10db.js → server-n1prf1g6.js} +1 -1
  201. package/dist/v2/{server-n78avt2p.js → server-n37sebck.js} +1 -1
  202. package/dist/v2/{server-y5pznjt9.js → server-nj659894.js} +5 -5
  203. package/dist/v2/{server-zyw55ygv.js → server-qv7f0hhe.js} +5 -5
  204. package/dist/v2/{server-e3ftpk7v.js → server-rmygf5pn.js} +32 -3
  205. package/dist/v2/{server-0ppsweh6.js → server-tgnbmwah.js} +22 -2
  206. package/dist/v2/{server-1hcbw661.js → server-xhab8jx8.js} +60 -21
  207. package/dist/v2/{server-6714jebr.js → server-xj136kt8.js} +1 -1
  208. package/dist/v2/{server-aavz8pm6.js → server-zzkj2fch.js} +3 -3
  209. package/dist/v2/server.js +4 -4
  210. package/dist/v2/{sqlite-4pqyynrs.js → sqlite-zf72c52d.js} +1 -1
  211. package/dist/v2/{storage-memory-j3v3hc83.js → storage-memory-y3qmy00r.js} +2 -2
  212. package/dist/v2/{storage-tags-hkwtbp3g.js → storage-tags-3wdb8zph.js} +8 -4
  213. package/dist/v2/store-reader.d.ts +8 -0
  214. package/dist/v2/store-reader.d.ts.map +1 -1
  215. package/dist/v2/{wrapup-orchestrator-x4xj1782.js → wrapup-orchestrator-psqz7yh0.js} +24 -24
  216. package/dist/wrapup-orchestrator-s9zc5b00.js +48 -0
  217. package/package.json +1 -1
  218. package/src/config/latch-permanence-guard.test.ts +4 -0
  219. package/src/shared/historian-cache.test.ts +69 -0
  220. package/src/shared/historian-cache.ts +55 -0
  221. package/src/shared/model-suggestion-retry.ts +2 -0
  222. package/src/shared/rpc-discovery-cleanup.test.ts +57 -0
  223. package/src/shared/rpc-discovery-cleanup.ts +57 -0
  224. package/src/shared/rpc-server.ts +36 -13
  225. package/src/shared/search-text.test.ts +47 -0
  226. package/src/shared/search-text.ts +63 -0
  227. package/src/shared/sqlite.test.ts +56 -0
  228. package/src/shared/sqlite.ts +51 -2
  229. package/src/shared/storage-file-mutex-review.test.ts +22 -2
  230. package/src/shared/storage-file-mutex.ts +11 -1
  231. package/src/shared/subagent-runner.ts +2 -0
  232. package/src/shared/system-directive.ts +19 -1
  233. package/src/shared/tag-transcript.ts +12 -4
  234. package/dist/v2/embedding-backfill-jyd6492b.js +0 -23
  235. package/dist/wrapup-orchestrator-vj2tbp22.js +0 -48
@@ -9,34 +9,46 @@ import {
9
9
  import {
10
10
  getHarness2
11
11
  } from "./index-pavzsdc6.js";
12
+ import {
13
+ getMagicContextStorageDir
14
+ } from "./index-g2q4htwp.js";
15
+ import {
16
+ isPidAlive,
17
+ readProcessStartTime
18
+ } from "./index-nj7yykkx.js";
12
19
  import {
13
20
  removeSystemReminders,
21
+ removeNestedBlocks,
14
22
  hasMeaningfulUserText2,
15
23
  extractTexts2
16
- } from "./index-hqkczed7.js";
17
- import {
18
- getMagicContextStorageDir
19
- } from "./index-g2q4htwp.js";
24
+ } from "./index-c5pytzne.js";
20
25
  import {
26
+ Database2,
27
+ getSqliteDatabasePath2,
28
+ prepareCachedStatement2,
21
29
  withSqliteBackgroundWriter2,
22
30
  withoutSqliteTransformPass2
23
- } from "./index-wafzvyhg.js";
31
+ } from "./index-beht37jy.js";
32
+ import {
33
+ resolveOpenCodeDbPath,
34
+ assertOpenCodeStoreGeneration
35
+ } from "./index-58e3y90n.js";
24
36
  import {
25
37
  cleanUserText2
26
- } from "./index-j6zw70pf.js";
38
+ } from "./index-rmwykbbb.js";
27
39
  import {
28
40
  recordMessageFtsRowid2,
29
41
  recordIndexedMessageTime2,
30
42
  messageFtsOrdinalRangeIsMapped2,
43
+ getMessageFtsRowidMapBackfillProgress2,
31
44
  deleteUnmappedMessageFtsRows2
32
- } from "./index-dqe8pq4x.js";
33
- import {
34
- isPidAlive,
35
- readProcessStartTime
36
- } from "./index-nj7yykkx.js";
45
+ } from "./index-a6mc0v4d.js";
37
46
  import {
38
47
  deleteChunkEmbedBackoffForSession
39
- } from "./index-hehkf6zm.js";
48
+ } from "./index-b5vqckbw.js";
49
+ import {
50
+ sameProjectDirectory
51
+ } from "./index-knvkbfvh.js";
40
52
  import {
41
53
  closeQuietly
42
54
  } from "./index-8ajstryz.js";
@@ -198,11 +210,11 @@ function clearCompressionDepthRange(db, sessionId, startOrdinal, endOrdinal) {
198
210
  }
199
211
 
200
212
  // src/features/magic-context/message-index.ts
201
- import { createHash as createHash3 } from "node:crypto";
213
+ import { createHash as createHash4 } from "node:crypto";
202
214
 
203
215
  // src/hooks/magic-context/lkg-recovery-fence.ts
204
216
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
205
- import { lstatSync, readdirSync as readdirSync2, readFileSync as readFileSync2, rmSync as rmSync2 } from "node:fs";
217
+ import { lstatSync, readdirSync as readdirSync2, readFileSync as readFileSync2, rmdirSync, rmSync as rmSync2 } from "node:fs";
206
218
  import { basename, dirname, join as join2 } from "node:path";
207
219
 
208
220
  // src/shared/storage-file-mutex.ts
@@ -247,7 +259,16 @@ function withStorageFileMutex(directory, work, timeoutMs = 1000) {
247
259
  const birth = processBirth(process.pid);
248
260
  const ticket = { choosing: true, number: 0, birth, checksum: "" };
249
261
  const encodedTicket = () => JSON.stringify({ ...ticket, checksum: ticketChecksum(name, ticket) });
250
- writeStorageFileSync(ownPath, encodedTicket(), { flag: "wx", forcePrivate: true });
262
+ for (let attempt = 0;; attempt++) {
263
+ try {
264
+ writeStorageFileSync(ownPath, encodedTicket(), { flag: "wx", forcePrivate: true });
265
+ break;
266
+ } catch (error) {
267
+ if (error.code !== "ENOENT" || attempt >= 2)
268
+ throw error;
269
+ ensurePrivateStorageDirectorySync(directory);
270
+ }
271
+ }
251
272
  const deadline = performance.now() + timeoutMs;
252
273
  const peers = () => {
253
274
  const found = [];
@@ -585,15 +606,776 @@ function commitLkgWithRecoveryFence(args) {
585
606
  function clearLkgRecoveryFence(sessionId, remove, shouldClear) {
586
607
  if (!durableFencesSupported())
587
608
  return remove?.();
588
- return locked(sessionId, () => {
589
- const result = remove?.();
590
- if (shouldClear && !shouldClear(result))
591
- return result;
592
- rmSync2(lkgRecoveryFencePath(sessionId), { force: true });
593
- syncStorageDirectorySync(dirname(lkgRecoveryFencePath(sessionId)));
609
+ const path = lkgRecoveryFencePath(sessionId);
610
+ const missing = (file) => {
611
+ try {
612
+ lstatSync(file);
613
+ return false;
614
+ } catch (error) {
615
+ return error.code === "ENOENT";
616
+ }
617
+ };
618
+ if (!remove && !shouldClear && missing(path) && missing(`${path}.locks`)) {
594
619
  clearLkgFenceProcessState(sessionId);
620
+ return;
621
+ }
622
+ try {
623
+ return locked(sessionId, () => {
624
+ const result = remove?.();
625
+ if (shouldClear && !shouldClear(result))
626
+ return result;
627
+ rmSync2(lkgRecoveryFencePath(sessionId), { force: true });
628
+ syncStorageDirectorySync(dirname(lkgRecoveryFencePath(sessionId)));
629
+ clearLkgFenceProcessState(sessionId);
630
+ return result;
631
+ });
632
+ } finally {
633
+ try {
634
+ rmdirSync(`${path}.locks`);
635
+ } catch {}
636
+ }
637
+ }
638
+
639
+ // src/shared/search-text.ts
640
+ function removeTaskNotifications(text) {
641
+ return removeNestedBlocks(text, ["task", "task_cohort"]);
642
+ }
643
+ function cleanSearchText(text) {
644
+ return removeNestedBlocks(text, ["system-reminder", "task", "task_cohort"]).replace(/\s+/g, " ").trim();
645
+ }
646
+ 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(" "));
647
+ function terms(text) {
648
+ return [
649
+ ...new Set((text.toLowerCase().match(/[\p{L}\p{N}_]+/gu) ?? []).filter((term) => !QUERY_STOP_WORDS.has(term)))
650
+ ];
651
+ }
652
+ function keywordRelevance(query, content) {
653
+ const needles = terms(query);
654
+ if (needles.length === 0)
655
+ return 0;
656
+ const words = new Set(terms(cleanSearchText(content)));
657
+ return 0.8 * needles.filter((term) => words.has(term)).length / needles.length;
658
+ }
659
+ function matchedSearchSnippet(content, query, cap = 220) {
660
+ const text = cleanSearchText(content);
661
+ const needles = new Set(terms(query));
662
+ const matches = [...text.matchAll(/[\p{L}\p{N}_]+/gu)].filter((match) => needles.has(match[0].toLowerCase()));
663
+ if (matches.length === 0)
664
+ return "";
665
+ let bestStart = matches[0].index;
666
+ let bestCoverage = -1;
667
+ for (const match of matches) {
668
+ const start = Math.max(0, match.index - Math.floor(cap / 5));
669
+ const coverage = keywordRelevance(query, text.slice(start, start + cap));
670
+ if (coverage > bestCoverage) {
671
+ bestStart = start;
672
+ bestCoverage = coverage;
673
+ }
674
+ }
675
+ if (bestStart > 0) {
676
+ const boundary = text.indexOf(" ", bestStart);
677
+ if (boundary >= 0 && boundary < matches[matches.length - 1].index)
678
+ bestStart = boundary + 1;
679
+ }
680
+ const passage = text.slice(bestStart, bestStart + cap).trim();
681
+ return `${bestStart > 0 ? "…" : ""}${passage}${bestStart + cap < text.length ? "…" : ""}`;
682
+ }
683
+
684
+ // src/v2/store-reader.ts
685
+ import { createHash as createHash3 } from "node:crypto";
686
+ import { statSync } from "node:fs";
687
+ import { hostname } from "node:os";
688
+ import { resolve } from "node:path";
689
+ function gaDatabasePath(dataHome, channel = "latest", env = process.env) {
690
+ return resolveOpenCodeDbPath("v2", { dataHome, channel, env }).path;
691
+ }
692
+ var RAW_MESSAGE_TYPES = [
693
+ "user",
694
+ "synthetic",
695
+ "assistant",
696
+ "skill",
697
+ "shell",
698
+ "system"
699
+ ];
700
+ var RAW_MESSAGE_TYPE_PARAMETERS = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
701
+ var V2_MESSAGE_PAGE_SQL = `WITH bounds AS (
702
+ SELECT
703
+ CASE WHEN ? = 0 THEN -1 ELSE COALESCE((
704
+ SELECT seq FROM session_message
705
+ WHERE session_id = ? AND type IN (${RAW_MESSAGE_TYPE_PARAMETERS})
706
+ ORDER BY seq ASC LIMIT 1 OFFSET ?
707
+ ), (SELECT MAX(seq) FROM session_message WHERE session_id = ?), -1) END AS after_seq,
708
+ COALESCE((
709
+ SELECT seq FROM session_message
710
+ WHERE session_id = ? AND type IN (${RAW_MESSAGE_TYPE_PARAMETERS})
711
+ ORDER BY seq ASC LIMIT 1 OFFSET ?
712
+ ), ?) AS watermark_seq
713
+ )
714
+ SELECT id, session_id, type, seq, time_created, data FROM session_message, bounds
715
+ WHERE session_id = ?
716
+ AND type IN (${RAW_MESSAGE_TYPE_PARAMETERS})
717
+ AND seq > bounds.after_seq
718
+ AND seq <= bounds.watermark_seq
719
+ ORDER BY seq ASC LIMIT ?`;
720
+ var V2_STORE_READER_DEBUG_COUNTER_KEY = "magic-context.v2.store-reader-debug";
721
+ var debugSymbol = Symbol.for(V2_STORE_READER_DEBUG_COUNTER_KEY);
722
+ var debugGlobal = globalThis;
723
+ var QUERY_STATEMENTS = {
724
+ messagePage: V2_MESSAGE_PAGE_SQL,
725
+ messageCount: "SELECT COUNT(*) AS count FROM session_message WHERE session_id = ? AND type IN (?)",
726
+ storedMessageCount: "SELECT COUNT(*) AS count FROM session_message WHERE session_id = ?",
727
+ messageById: "SELECT id, session_id, type, seq, time_created, data FROM session_message WHERE session_id = ? AND id = ? AND type IN (?) LIMIT 1",
728
+ messageExistsById: "SELECT 1 FROM session_message WHERE id = ? AND session_id = ? AND type IN (?) LIMIT 1",
729
+ messageOrdinalById: `WITH target AS (
730
+ SELECT seq FROM session_message WHERE session_id = ? AND id = ? AND type IN (?) LIMIT 1
731
+ )
732
+ SELECT (SELECT COUNT(*) FROM session_message WHERE session_id = ? AND type IN (?) AND seq <= target.seq) AS ordinal FROM target`,
733
+ messageIdOrdinals: "SELECT id FROM session_message WHERE session_id = ? AND type IN (?) ORDER BY seq ASC LIMIT ? OFFSET ?",
734
+ 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 ?",
735
+ 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 ?"
736
+ };
737
+ function debugCounters() {
738
+ const counters = debugGlobal[debugSymbol] ??= {
739
+ decodedRows: 0,
740
+ operations: {},
741
+ openReaders: 0,
742
+ maxOpenReaders: 0,
743
+ readersOpened: 0,
744
+ readersClosed: 0
745
+ };
746
+ counters.openReaders ??= 0;
747
+ counters.maxOpenReaders ??= 0;
748
+ counters.readersOpened ??= 0;
749
+ counters.readersClosed ??= 0;
750
+ return counters;
751
+ }
752
+ function returnedRowCount(value) {
753
+ if (Array.isArray(value))
754
+ return value.length;
755
+ if (value === null || value === undefined || value === false)
756
+ return 0;
757
+ return 1;
758
+ }
759
+ function trackDecodeOperation(name, operation) {
760
+ const counters = debugCounters();
761
+ const before = counters.decodedRows;
762
+ const started = performance.now();
763
+ let value;
764
+ try {
765
+ value = operation();
766
+ return value;
767
+ } finally {
768
+ const decodedRows = counters.decodedRows - before;
769
+ const current = counters.operations[name] ?? {
770
+ calls: 0,
771
+ decodedRows: 0,
772
+ maxDecodedRows: 0
773
+ };
774
+ current.calls += 1;
775
+ current.decodedRows += decodedRows;
776
+ current.maxDecodedRows = Math.max(current.maxDecodedRows, decodedRows);
777
+ counters.operations[name] = current;
778
+ const statement = QUERY_STATEMENTS[name];
779
+ if (counters.captureQueries && statement) {
780
+ (counters.queries ??= []).push({
781
+ operation: name,
782
+ statement,
783
+ rows: returnedRowCount(value),
784
+ elapsedMs: performance.now() - started
785
+ });
786
+ }
787
+ }
788
+ }
789
+ function decode(row) {
790
+ debugCounters().decodedRows += 1;
791
+ const data = JSON.parse(row.data);
792
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
793
+ throw new Error(`Invalid session_message data at seq ${row.seq}`);
794
+ }
795
+ if (row.type === "idle" && !["succeeded", "failed", "interrupted"].includes(String(data.outcome))) {
796
+ throw new Error(`Invalid idle outcome at seq ${row.seq}`);
797
+ }
798
+ return { ...row, data };
799
+ }
800
+ function connect(path, identity = "") {
801
+ const stat = statSync(path);
802
+ const acquiredIdentity = identity || `${stat.dev}:${stat.ino}:${stat.birthtimeMs}`;
803
+ const db = new Database2(path, { readonly: true, fileMustExist: true });
804
+ try {
805
+ assertOpenCodeStoreGeneration(db, "v2", path);
806
+ } catch (error) {
807
+ db.close();
808
+ throw error;
809
+ }
810
+ const counters = debugCounters();
811
+ counters.openReaders += 1;
812
+ counters.readersOpened += 1;
813
+ counters.maxOpenReaders = Math.max(counters.maxOpenReaders, counters.openReaders);
814
+ return { db, statements: new Map, identity: acquiredIdentity, leases: 0, retired: false };
815
+ }
816
+ function closeConnection(connection) {
817
+ connection.statements.clear();
818
+ try {
819
+ connection.db.close();
820
+ } finally {
821
+ const counters = debugCounters();
822
+ counters.openReaders = Math.max(0, counters.openReaders - 1);
823
+ counters.readersClosed += 1;
824
+ }
825
+ }
826
+
827
+ class V2StoreReaderPool {
828
+ capacity;
829
+ connections = new Map;
830
+ closed = false;
831
+ closing = false;
832
+ holds = 0;
833
+ idleWaiters = new Set;
834
+ constructor(capacity = 4) {
835
+ this.capacity = capacity;
836
+ if (!Number.isSafeInteger(capacity) || capacity < 1)
837
+ throw new Error("Invalid reader capacity");
838
+ }
839
+ open(path) {
840
+ return new V2StoreReader(path, this);
841
+ }
842
+ acquire(path) {
843
+ if (this.closed)
844
+ throw new Error("Store reader pool is closed");
845
+ const key = resolve(path);
846
+ const stat = statSync(key);
847
+ const identity = `${stat.dev}:${stat.ino}:${stat.birthtimeMs}`;
848
+ let connection = this.connections.get(key);
849
+ if (connection && connection.identity !== identity) {
850
+ this.connections.delete(key);
851
+ this.retire(connection);
852
+ connection = undefined;
853
+ }
854
+ if (!connection)
855
+ connection = connect(key, identity);
856
+ connection.leases++;
857
+ this.connections.delete(key);
858
+ this.connections.set(key, connection);
859
+ while (this.connections.size > this.capacity) {
860
+ const oldest = this.connections.entries().next().value;
861
+ if (!oldest)
862
+ break;
863
+ this.connections.delete(oldest[0]);
864
+ this.retire(oldest[1]);
865
+ }
866
+ return connection;
867
+ }
868
+ release(connection) {
869
+ connection.leases--;
870
+ if (connection.retired && connection.leases === 0)
871
+ closeConnection(connection);
872
+ }
873
+ retire(connection) {
874
+ connection.retired = true;
875
+ if (connection.leases === 0)
876
+ closeConnection(connection);
877
+ }
878
+ hold() {
879
+ if (this.closed || this.closing)
880
+ throw new Error("Store reader pool is closed");
881
+ this.holds++;
882
+ let released = false;
883
+ return () => {
884
+ if (released)
885
+ return;
886
+ released = true;
887
+ this.holds--;
888
+ if (this.holds === 0 && this.closing)
889
+ this.finishClose();
890
+ };
891
+ }
892
+ isClosed() {
893
+ return this.closed;
894
+ }
895
+ holdCount() {
896
+ return this.holds;
897
+ }
898
+ close() {
899
+ if (this.closed || this.closing)
900
+ return;
901
+ this.closing = true;
902
+ if (this.holds === 0)
903
+ this.finishClose();
904
+ }
905
+ async closeWhenIdle(timeoutMs) {
906
+ this.close();
907
+ if (this.closed)
908
+ return true;
909
+ let timer;
910
+ let waiter;
911
+ try {
912
+ await new Promise((resolve) => {
913
+ waiter = resolve;
914
+ this.idleWaiters.add(resolve);
915
+ timer = setTimeout(resolve, Math.max(0, timeoutMs));
916
+ });
917
+ } finally {
918
+ if (timer)
919
+ clearTimeout(timer);
920
+ if (waiter)
921
+ this.idleWaiters.delete(waiter);
922
+ }
923
+ return this.closed;
924
+ }
925
+ finishClose() {
926
+ if (this.closed)
927
+ return;
928
+ this.closed = true;
929
+ for (const connection of this.connections.values())
930
+ this.retire(connection);
931
+ this.connections.clear();
932
+ for (const resolve of this.idleWaiters)
933
+ resolve();
934
+ this.idleWaiters.clear();
935
+ }
936
+ }
937
+
938
+ class V2StoreReader {
939
+ path;
940
+ pool;
941
+ db;
942
+ connection;
943
+ closed = false;
944
+ constructor(path, pool) {
945
+ this.path = path;
946
+ this.pool = pool;
947
+ this.connection = pool ? pool.acquire(path) : connect(path);
948
+ this.db = this.connection.db;
949
+ }
950
+ hostIdentity() {
951
+ return createHash3("sha256").update(JSON.stringify([hostname(), resolve(this.path), this.connection.identity])).digest("hex");
952
+ }
953
+ prepare(sql) {
954
+ if (this.closed)
955
+ throw new Error("Store reader is closed");
956
+ const statements = this.connection.statements;
957
+ let statement = statements.get(sql);
958
+ if (!statement) {
959
+ statement = this.db.prepare(sql);
960
+ if (statements.size >= 128)
961
+ statements.clear();
962
+ statements.set(sql, statement);
963
+ }
964
+ return statement;
965
+ }
966
+ close() {
967
+ if (this.closed)
968
+ return;
969
+ this.closed = true;
970
+ if (this.pool)
971
+ this.pool.release(this.connection);
972
+ else
973
+ closeConnection(this.connection);
974
+ }
975
+ page(sessionID, options = {}) {
976
+ return trackDecodeOperation("page", () => {
977
+ const limit = options.limit ?? 100;
978
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e4)
979
+ throw new Error("Invalid page limit");
980
+ const after = options.after ?? -1;
981
+ if (!Number.isSafeInteger(after))
982
+ throw new Error("Invalid seq cursor");
983
+ if (options.through !== undefined && !Number.isSafeInteger(options.through))
984
+ throw new Error("Invalid seq upper bound");
985
+ const predicates = ["session_id = ?", "seq > ?"];
986
+ const parameters = [sessionID, after];
987
+ if (options.through !== undefined) {
988
+ predicates.push("seq <= ?");
989
+ parameters.push(options.through);
990
+ }
991
+ if (options.type) {
992
+ predicates.push("type = ?");
993
+ parameters.push(options.type);
994
+ }
995
+ const rows = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
996
+ WHERE ${predicates.join(" AND ")}
997
+ ORDER BY seq ASC LIMIT ?`).all(...parameters, limit).map(decode);
998
+ return { rows, cursor: rows.at(-1)?.seq };
999
+ });
1000
+ }
1001
+ messagePage(sessionID, afterOrdinal, limit, finalWatermark, after) {
1002
+ return trackDecodeOperation("messagePage", () => {
1003
+ if (!Number.isSafeInteger(afterOrdinal) || afterOrdinal < 0)
1004
+ throw new Error("Invalid raw-message ordinal cursor");
1005
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e4)
1006
+ throw new Error("Invalid page limit");
1007
+ if (!Number.isSafeInteger(finalWatermark) || finalWatermark < 0)
1008
+ throw new Error("Invalid raw-message watermark");
1009
+ const pageSize = Math.min(limit, finalWatermark - afterOrdinal);
1010
+ if (pageSize <= 0)
1011
+ return [];
1012
+ if (after) {
1013
+ const anchor = this.prepare("SELECT seq FROM session_message WHERE session_id = ? AND id = ?").get(sessionID, after.id);
1014
+ if (anchor) {
1015
+ return this.prepare(`SELECT id, session_id, type, seq, time_created, data
1016
+ FROM session_message WHERE session_id = ? AND seq > ?
1017
+ AND type IN (${RAW_MESSAGE_TYPE_PARAMETERS}) ORDER BY seq ASC LIMIT ?`).all(sessionID, anchor.seq, ...RAW_MESSAGE_TYPES, pageSize).map(decode);
1018
+ }
1019
+ }
1020
+ const maximumSeq = Number.MAX_SAFE_INTEGER;
1021
+ 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);
1022
+ return rows.map(decode);
1023
+ });
1024
+ }
1025
+ messageCount(sessionID) {
1026
+ return trackDecodeOperation("messageCount", () => {
1027
+ const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
1028
+ const row = this.prepare(`SELECT COUNT(*) AS count FROM session_message
1029
+ WHERE session_id = ? AND type IN (${rawTypes})`).get(sessionID, ...RAW_MESSAGE_TYPES);
1030
+ return typeof row?.count === "number" ? row.count : 0;
1031
+ });
1032
+ }
1033
+ sessionDirectoryPage(afterSessionID, limit) {
1034
+ const rows = this.prepare(`SELECT id, COALESCE(directory, '') AS directory FROM session_v2
1035
+ WHERE id > ? ORDER BY id ASC LIMIT ?`).all(afterSessionID ?? "", limit);
1036
+ return rows.map((row) => ({ sessionId: row.id, directory: row.directory }));
1037
+ }
1038
+ latestMessageTime(sessionID) {
1039
+ const row = this.prepare("SELECT MAX(time_created) AS time FROM session_message WHERE session_id = ?").get(sessionID);
1040
+ return row?.time ?? undefined;
1041
+ }
1042
+ isSubagent(sessionID) {
1043
+ const row = this.prepare("SELECT parent_id FROM session_v2 WHERE id = ?").get(sessionID);
1044
+ if (!row)
1045
+ return null;
1046
+ return typeof row.parent_id === "string" && row.parent_id.length > 0;
1047
+ }
1048
+ sessionIds() {
1049
+ return this.prepare("SELECT id FROM session_v2").all().map((row) => row.id);
1050
+ }
1051
+ latestRootSessionInDirectory(directory) {
1052
+ const columns = new Set(this.prepare("PRAGMA table_info(session_v2)").all().map((column) => column.name));
1053
+ const archived = columns.has("time_archived") ? " AND time_archived IS NULL" : "";
1054
+ const order = columns.has("time_updated") ? "time_updated DESC, id DESC" : "id DESC";
1055
+ const page = this.prepare(`SELECT id, COALESCE(directory, '') AS directory FROM session_v2
1056
+ WHERE parent_id IS NULL${archived}
1057
+ AND COALESCE(json_extract(metadata, '$.magic_context'), '') <> 'hidden-run'
1058
+ ORDER BY ${order} LIMIT ? OFFSET ?`);
1059
+ const pageSize = 500;
1060
+ for (let offset = 0;; offset += pageSize) {
1061
+ const rows = page.all(pageSize, offset);
1062
+ const match = rows.find((row) => sameProjectDirectory(row.directory, directory));
1063
+ if (match)
1064
+ return match.id;
1065
+ if (rows.length < pageSize)
1066
+ return;
1067
+ }
1068
+ }
1069
+ rootSessionActivity(sessionIDs) {
1070
+ const roots = new Map;
1071
+ for (let offset = 0;offset < sessionIDs.length; offset += 500) {
1072
+ const chunk = sessionIDs.slice(offset, offset + 500);
1073
+ const rows = this.prepare(`SELECT id, (
1074
+ SELECT MAX(time_created) FROM session_message
1075
+ WHERE session_id = session_v2.id AND type = 'user'
1076
+ ) AS time FROM session_v2
1077
+ WHERE parent_id IS NULL
1078
+ AND COALESCE(json_extract(metadata, '$.magic_context'), '') <> 'hidden-run'
1079
+ AND id IN (${chunk.map(() => "?").join(",")})`).all(...chunk);
1080
+ for (const row of rows)
1081
+ if (row.time !== null)
1082
+ roots.set(row.id, row.time);
1083
+ }
1084
+ return roots;
1085
+ }
1086
+ oldestUserMessageTimesSince(sessionIDs, sinceMs) {
1087
+ const result = new Map;
1088
+ for (let offset = 0;offset < sessionIDs.length; offset += 500) {
1089
+ const chunk = sessionIDs.slice(offset, offset + 500);
1090
+ const rows = this.prepare(`SELECT session_id, MIN(time_created) AS time FROM session_message
1091
+ WHERE type = 'user' AND time_created > ? AND session_id IN (${chunk.map(() => "?").join(",")})
1092
+ GROUP BY session_id`).all(sinceMs, ...chunk);
1093
+ for (const row of rows)
1094
+ result.set(row.session_id, row.time);
1095
+ }
595
1096
  return result;
596
- });
1097
+ }
1098
+ retrospectiveUserPage(sessionID, options) {
1099
+ const { boundaryMs, before, limit, maxChars, truncationMarker } = options;
1100
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e4)
1101
+ throw new Error("Invalid page limit");
1102
+ if (!Number.isSafeInteger(maxChars) || maxChars <= truncationMarker.length)
1103
+ throw new Error("Invalid text limit");
1104
+ const direction = before ? "DESC" : "ASC";
1105
+ const head = Math.floor((maxChars - truncationMarker.length) / 2);
1106
+ const tail = maxChars - truncationMarker.length - head;
1107
+ return this.prepare(`WITH users AS (
1108
+ SELECT session_id, seq, time_created, data FROM session_message
1109
+ WHERE session_id = ? AND type = 'user' AND time_created ${before ? "<=" : ">"}
1110
+ ?
1111
+ ORDER BY time_created ${direction}, seq ${direction} LIMIT ?
1112
+ ), texts AS (
1113
+ SELECT session_id, seq, time_created, CASE
1114
+ WHEN json_extract(data, '$.synthetic') = 1 OR json_extract(data, '$.ignored') = 1 THEN ''
1115
+ WHEN json_type(data, '$.text') = 'text' THEN json_extract(data, '$.text')
1116
+ ELSE COALESCE((SELECT group_concat(json_extract(value, '$.text'), char(10))
1117
+ FROM json_each(users.data, '$.content')
1118
+ WHERE json_extract(value, '$.type') IN ('text', 'input_text')
1119
+ AND COALESCE(json_extract(value, '$.synthetic'), 0) <> 1
1120
+ AND COALESCE(json_extract(value, '$.ignored'), 0) <> 1), '') END AS text
1121
+ FROM users WHERE json_valid(data)
1122
+ ) SELECT seq, time_created, CASE WHEN length(text) > ?
1123
+ THEN substr(text, 1, ?) || ? || substr(text, -?) ELSE text END AS text,
1124
+ EXISTS (
1125
+ SELECT 1 FROM session_message assistant_reply
1126
+ WHERE assistant_reply.session_id = texts.session_id
1127
+ AND assistant_reply.type = 'assistant'
1128
+ AND assistant_reply.seq < texts.seq
1129
+ AND EXISTS (
1130
+ SELECT 1 FROM session_message prior_user
1131
+ WHERE prior_user.session_id = texts.session_id
1132
+ AND prior_user.type = 'user'
1133
+ AND prior_user.seq < assistant_reply.seq
1134
+ )
1135
+ ) AS follows_assistant_reply
1136
+ FROM texts ORDER BY time_created ${direction}, seq ${direction}`).all(sessionID, boundaryMs, limit, maxChars, head, truncationMarker, tail);
1137
+ }
1138
+ storedRowsAfter(sessionID, after) {
1139
+ const row = this.prepare("SELECT COUNT(*) AS count FROM session_message WHERE session_id = ? AND seq > ?").get(sessionID, after);
1140
+ return row.count;
1141
+ }
1142
+ storedMessageCount(sessionID) {
1143
+ return trackDecodeOperation("storedMessageCount", () => {
1144
+ const row = this.prepare("SELECT COUNT(*) AS count FROM session_message WHERE session_id = ?").get(sessionID);
1145
+ return typeof row?.count === "number" ? row.count : 0;
1146
+ });
1147
+ }
1148
+ messageById(sessionID, id) {
1149
+ return trackDecodeOperation("messageById", () => {
1150
+ const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
1151
+ const row = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
1152
+ WHERE session_id = ? AND id = ? AND type IN (${rawTypes}) LIMIT 1`).get(sessionID, id, ...RAW_MESSAGE_TYPES);
1153
+ return row ? decode(row) : null;
1154
+ });
1155
+ }
1156
+ messageTimes(sessionID, ids) {
1157
+ const times = new Map;
1158
+ for (let offset = 0;offset < ids.length; offset += 500) {
1159
+ const batch = ids.slice(offset, offset + 500);
1160
+ const rows = this.prepare(`SELECT id, COALESCE(time_created, json_extract(data, '$.time.created')) AS created,
1161
+ json_extract(data, '$.time.completed') AS completed
1162
+ FROM session_message WHERE session_id = ? AND id IN (${batch.map(() => "?").join(",")})`).all(sessionID, ...batch);
1163
+ for (const row of rows)
1164
+ times.set(row.id, {
1165
+ created: typeof row.created === "number" ? row.created : undefined,
1166
+ completed: typeof row.completed === "number" ? row.completed : undefined
1167
+ });
1168
+ }
1169
+ return times;
1170
+ }
1171
+ messageExistsById(sessionID, id) {
1172
+ return trackDecodeOperation("messageExistsById", () => {
1173
+ const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
1174
+ const row = this.prepare(`SELECT 1 FROM session_message
1175
+ WHERE id = ? AND session_id = ? AND type IN (${rawTypes}) LIMIT 1`).get(id, sessionID, ...RAW_MESSAGE_TYPES);
1176
+ return row != null;
1177
+ });
1178
+ }
1179
+ messageOrdinalById(sessionID, id) {
1180
+ return trackDecodeOperation("messageOrdinalById", () => {
1181
+ const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
1182
+ const row = this.prepare(`WITH target AS (
1183
+ SELECT seq FROM session_message
1184
+ WHERE session_id = ? AND id = ? AND type IN (${rawTypes})
1185
+ LIMIT 1
1186
+ )
1187
+ SELECT (
1188
+ SELECT COUNT(*) FROM session_message
1189
+ WHERE session_id = ?
1190
+ AND type IN (${rawTypes})
1191
+ AND seq <= target.seq
1192
+ ) AS ordinal
1193
+ FROM target`).get(sessionID, id, ...RAW_MESSAGE_TYPES, sessionID, ...RAW_MESSAGE_TYPES);
1194
+ return typeof row?.ordinal === "number" ? row.ordinal : null;
1195
+ });
1196
+ }
1197
+ messageIdOrdinals(sessionID, fromOrdinal, toOrdinal) {
1198
+ return trackDecodeOperation("messageIdOrdinals", () => {
1199
+ if (!Number.isSafeInteger(fromOrdinal) || fromOrdinal < 1)
1200
+ throw new Error("Invalid raw-message range start");
1201
+ if (!Number.isSafeInteger(toOrdinal) || toOrdinal < fromOrdinal)
1202
+ throw new Error("Invalid raw-message range end");
1203
+ const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
1204
+ const rows = this.prepare(`SELECT id FROM session_message
1205
+ WHERE session_id = ? AND type IN (${rawTypes})
1206
+ ORDER BY seq ASC LIMIT ? OFFSET ?`).all(sessionID, ...RAW_MESSAGE_TYPES, toOrdinal - fromOrdinal + 1, fromOrdinal - 1);
1207
+ return new Map(rows.map((row, index) => [row.id, fromOrdinal + index]));
1208
+ });
1209
+ }
1210
+ messageOrdinalPage(sessionID, after, limit) {
1211
+ return trackDecodeOperation("messageOrdinalPage", () => {
1212
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e4)
1213
+ throw new Error("Invalid page limit");
1214
+ const rows = after ? this.prepare(`SELECT id, type, seq, json_valid(data) AS valid
1215
+ FROM session_message
1216
+ WHERE session_id = ? AND (seq > ? OR (seq = ? AND id > ?))
1217
+ 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
1218
+ FROM session_message
1219
+ WHERE session_id = ?
1220
+ ORDER BY seq ASC, id ASC LIMIT ?`).all(sessionID, limit);
1221
+ const rawTypes = new Set(RAW_MESSAGE_TYPES);
1222
+ return rows.map((row) => ({
1223
+ id: row.id,
1224
+ timeCreated: row.seq,
1225
+ contributesOrdinal: rawTypes.has(row.type),
1226
+ hasValidInfo: row.valid === 1
1227
+ }));
1228
+ });
1229
+ }
1230
+ range(sessionID, after, through) {
1231
+ return trackDecodeOperation("range", () => through <= after ? [] : this.all(sessionID, after, undefined, through));
1232
+ }
1233
+ spanRowStamps(sessionID, after, through) {
1234
+ if (!Number.isSafeInteger(after) || !Number.isSafeInteger(through))
1235
+ throw new Error("Invalid seq span");
1236
+ const rows = this.prepare(`SELECT seq, id, time_updated AS updated, length(CAST(data AS BLOB)) AS bytes
1237
+ FROM session_message WHERE session_id = ? AND seq > ? AND seq <= ?
1238
+ ORDER BY seq ASC`).all(sessionID, after, through);
1239
+ const stamps = new Map;
1240
+ for (const row of rows)
1241
+ stamps.set(row.seq, `${row.id}\x00${row.updated ?? ""}\x00${row.bytes ?? ""}`);
1242
+ return stamps;
1243
+ }
1244
+ rawRowsThrough(sessionID, throughSeq, limit) {
1245
+ return trackDecodeOperation("rawRowsThrough", () => {
1246
+ if (!Number.isSafeInteger(throughSeq))
1247
+ throw new Error("Invalid seq upper bound");
1248
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e4)
1249
+ throw new Error("Invalid page limit");
1250
+ const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
1251
+ return this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
1252
+ WHERE session_id = ? AND type IN (${rawTypes}) AND seq <= ?
1253
+ ORDER BY seq DESC LIMIT ?`).all(sessionID, ...RAW_MESSAGE_TYPES, throughSeq, limit).map(decode);
1254
+ });
1255
+ }
1256
+ instructionRowsThrough(sessionID, throughSeq) {
1257
+ return trackDecodeOperation("instructionRowsThrough", () => {
1258
+ if (!Number.isSafeInteger(throughSeq))
1259
+ throw new Error("Invalid seq upper bound");
1260
+ return this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
1261
+ WHERE session_id = ? AND type = 'system' AND seq <= ?
1262
+ ORDER BY seq ASC`).all(sessionID, throughSeq).map(decode);
1263
+ });
1264
+ }
1265
+ sequenceForId(sessionID, id) {
1266
+ if (!id)
1267
+ return;
1268
+ const row = this.prepare("SELECT seq FROM session_message WHERE session_id = ? AND id = ? LIMIT 1").get(sessionID, id);
1269
+ return typeof row?.seq === "number" ? row.seq : undefined;
1270
+ }
1271
+ syntheticMessageIDs(sessionID) {
1272
+ const rows = this.prepare("SELECT id FROM session_message WHERE session_id = ? AND type = 'synthetic'").all(sessionID);
1273
+ return new Set(rows.map((row) => row.id));
1274
+ }
1275
+ forkOrigin(sessionID) {
1276
+ const columns = new Set(this.prepare("PRAGMA table_info(session_v2)").all().map((column) => column.name));
1277
+ if (!columns.has("fork_session_id") || !columns.has("fork_boundary"))
1278
+ return null;
1279
+ const row = this.prepare("SELECT fork_session_id, fork_boundary FROM session_v2 WHERE id = ?").get(sessionID);
1280
+ if (!row?.fork_session_id || !row.fork_boundary)
1281
+ return null;
1282
+ try {
1283
+ const boundary = JSON.parse(row.fork_boundary);
1284
+ if (boundary.type !== "before" && boundary.type !== "through" || typeof boundary.messageID !== "string" || boundary.messageID.length === 0)
1285
+ return null;
1286
+ return {
1287
+ parentSessionID: row.fork_session_id,
1288
+ boundary: { type: boundary.type, messageID: boundary.messageID }
1289
+ };
1290
+ } catch {
1291
+ return null;
1292
+ }
1293
+ }
1294
+ sessionExists(sessionID) {
1295
+ return this.prepare("SELECT 1 FROM session_v2 WHERE id = ?").get(sessionID) != null;
1296
+ }
1297
+ rowStampsThrough(sessionID, throughSeq) {
1298
+ return this.prepare(`SELECT id, type, seq, time_created FROM session_message
1299
+ WHERE session_id = ? AND seq <= ? ORDER BY seq ASC`).all(sessionID, throughSeq);
1300
+ }
1301
+ latestSequenceForIds(sessionID, ids) {
1302
+ let latest = -1;
1303
+ for (let offset = 0;offset < ids.length; offset += 500) {
1304
+ const chunk = ids.slice(offset, offset + 500);
1305
+ const row = this.prepare(`SELECT MAX(seq) AS seq FROM session_message
1306
+ WHERE session_id = ? AND id IN (${chunk.map(() => "?").join(", ")})`).get(sessionID, ...chunk);
1307
+ if (typeof row?.seq === "number")
1308
+ latest = Math.max(latest, row.seq);
1309
+ }
1310
+ return latest;
1311
+ }
1312
+ compactionByStatus(sessionID, status) {
1313
+ const row = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
1314
+ WHERE session_id = ? AND type = 'compaction'
1315
+ AND json_extract(data, '$.status') = ?
1316
+ ORDER BY seq DESC LIMIT 1`).get(sessionID, status);
1317
+ return row ? decode(row) : undefined;
1318
+ }
1319
+ latestCompaction(sessionID) {
1320
+ return trackDecodeOperation("latestCompaction", () => this.compactionByStatus(sessionID, "completed"));
1321
+ }
1322
+ latestRunningCompaction(sessionID) {
1323
+ return trackDecodeOperation("latestRunningCompaction", () => this.compactionByStatus(sessionID, "running"));
1324
+ }
1325
+ idleRows(sessionID, after = -1) {
1326
+ return trackDecodeOperation("idleRows", () => this.all(sessionID, after, "idle"));
1327
+ }
1328
+ earliestSequence(sessionID) {
1329
+ const row = this.prepare("SELECT MIN(seq) AS seq FROM session_message WHERE session_id = ?").get(sessionID);
1330
+ return typeof row?.seq === "number" ? row.seq : undefined;
1331
+ }
1332
+ latestSequence(sessionID) {
1333
+ const row = this.prepare("SELECT MAX(seq) AS seq FROM session_message WHERE session_id = ?").get(sessionID);
1334
+ return typeof row?.seq === "number" ? row.seq : -1;
1335
+ }
1336
+ assistantSince(sessionID, afterSeq) {
1337
+ return trackDecodeOperation("assistantSince", () => {
1338
+ const rows = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
1339
+ WHERE session_id = ? AND type = 'assistant' AND seq > ?
1340
+ ORDER BY seq ASC`).all(sessionID, afterSeq);
1341
+ return rows.map((row) => decode(row));
1342
+ });
1343
+ }
1344
+ latestAssistant(sessionID) {
1345
+ return trackDecodeOperation("latestAssistant", () => {
1346
+ const row = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
1347
+ WHERE session_id = ? AND type = 'assistant'
1348
+ ORDER BY seq DESC LIMIT 1`).get(sessionID);
1349
+ return row ? decode(row) : undefined;
1350
+ });
1351
+ }
1352
+ latestIdle(sessionID) {
1353
+ return trackDecodeOperation("latestIdle", () => {
1354
+ const row = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
1355
+ WHERE session_id = ? AND type = 'idle'
1356
+ ORDER BY seq DESC LIMIT 1`).get(sessionID);
1357
+ return row ? decode(row) : undefined;
1358
+ });
1359
+ }
1360
+ window(sessionID) {
1361
+ return trackDecodeOperation("window", () => this.db.transaction(() => {
1362
+ const cut = this.latestCompaction(sessionID);
1363
+ return this.all(sessionID, cut ? cut.seq - 1 : -1);
1364
+ }).deferred());
1365
+ }
1366
+ history(sessionID) {
1367
+ return trackDecodeOperation("history", () => this.all(sessionID, -1));
1368
+ }
1369
+ all(sessionID, after, type, through) {
1370
+ const rows = [];
1371
+ for (;; ) {
1372
+ const page = this.page(sessionID, { after, through, type });
1373
+ rows.push(...page.rows);
1374
+ if (page.rows.length < 100 || page.cursor === undefined)
1375
+ return rows;
1376
+ after = page.cursor;
1377
+ }
1378
+ }
597
1379
  }
598
1380
 
599
1381
  // src/features/magic-context/storage-session-tables.ts
@@ -642,6 +1424,22 @@ var SESSION_SCOPED_TABLES = [
642
1424
  { table: "lkg_slots" },
643
1425
  { table: "lkg_slot_chunks" }
644
1426
  ];
1427
+ var cleanupStatements = new WeakMap;
1428
+ function cleanupStatement(db, sql) {
1429
+ let cache = cleanupStatements.get(db);
1430
+ if (!cache) {
1431
+ cache = new Map;
1432
+ cleanupStatements.set(db, cache);
1433
+ }
1434
+ let statement = cache.get(sql);
1435
+ if (!statement) {
1436
+ if (cache.size >= 256)
1437
+ cache.clear();
1438
+ statement = prepareCachedStatement2(db, sql);
1439
+ cache.set(sql, statement);
1440
+ }
1441
+ return statement;
1442
+ }
645
1443
  function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
646
1444
  if (sessionIds.length === 0)
647
1445
  return 0;
@@ -649,7 +1447,7 @@ function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
649
1447
  if (options.rustModuleCleanupAcknowledged !== true) {
650
1448
  const placeholders = sessionIds.map(() => "?").join(", ");
651
1449
  const harnessPredicate = harness === undefined ? "" : " AND harness = ?";
652
- const protectedRows = db.prepare(`SELECT session_id
1450
+ const protectedRows = cleanupStatement(db, `SELECT session_id
653
1451
  FROM pending_session_cleanup
654
1452
  WHERE session_id IN (${placeholders})
655
1453
  AND harness LIKE '%:rust'${harnessPredicate}`).all(...sessionIds, ...harness === undefined ? [] : [`${harness}:rust`]);
@@ -661,11 +1459,11 @@ function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
661
1459
  const placeholders = deletableSessionIds.map(() => "?").join(", ");
662
1460
  const harnessTables = SESSION_SCOPED_TABLES.filter((definition) => definition.harnessScoped);
663
1461
  if (options.preserveTagRevisionTombstones) {
664
- 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));
665
- const record = db.prepare("INSERT OR IGNORE INTO schema_migrations_meta (key, value) VALUES (?, ?)");
1462
+ 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));
1463
+ const record = cleanupStatement(db, "INSERT OR IGNORE INTO schema_migrations_meta (key, value) VALUES (?, ?)");
666
1464
  for (const owner of owners)
667
1465
  record.run(tagTombstoneOwnerKey(owner.session_id, owner.harness), owner.harness);
668
- const fallback = db.prepare(`INSERT INTO schema_migrations_meta (key, value)
1466
+ const fallback = cleanupStatement(db, `INSERT INTO schema_migrations_meta (key, value)
669
1467
  SELECT ?, ? WHERE NOT EXISTS (SELECT 1 FROM schema_migrations_meta WHERE key >= ? AND key < ?)`);
670
1468
  for (const id of deletableSessionIds)
671
1469
  fallback.run(tagTombstoneOwnerKey(id, getHarness2()), getHarness2(), ...tagTombstoneOwnerRange(id));
@@ -674,7 +1472,7 @@ function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
674
1472
  deleteChunkEmbedBackoffForSession(db, sessionId);
675
1473
  for (const definition of SESSION_SCOPED_TABLES) {
676
1474
  if (definition.table === "message_history_fts") {
677
- db.prepare(`DELETE FROM message_history_fts
1475
+ cleanupStatement(db, `DELETE FROM message_history_fts
678
1476
  WHERE rowid IN (
679
1477
  SELECT fts_rowid
680
1478
  FROM message_fts_rowid_map
@@ -689,7 +1487,7 @@ function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
689
1487
  const bindHarness = harness !== undefined && definition.harnessScoped === true;
690
1488
  if (bindHarness)
691
1489
  predicates.push("harness = ?");
692
- const statement = db.prepare(`DELETE FROM ${definition.table} WHERE ${predicates.join(" AND ")}`);
1490
+ const statement = cleanupStatement(db, `DELETE FROM ${definition.table} WHERE ${predicates.join(" AND ")}`);
693
1491
  if (bindHarness)
694
1492
  statement.run(...deletableSessionIds, harness);
695
1493
  else
@@ -697,9 +1495,9 @@ function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
697
1495
  }
698
1496
  deleteSessionActivity(db, deletableSessionIds);
699
1497
  if (!options.preserveTagRevisionTombstones) {
700
- const retireOwner = db.prepare(harness === undefined ? "DELETE FROM schema_migrations_meta WHERE key >= ? AND key < ?" : "DELETE FROM schema_migrations_meta WHERE key = ?");
1498
+ const retireOwner = cleanupStatement(db, harness === undefined ? "DELETE FROM schema_migrations_meta WHERE key >= ? AND key < ?" : "DELETE FROM schema_migrations_meta WHERE key = ?");
701
1499
  const survivingRows = harnessTables.map((definition) => `EXISTS (SELECT 1 FROM ${definition.table} WHERE session_id = ?${definition.extraPredicate ? ` AND ${definition.extraPredicate}` : ""})`).join(" OR ");
702
- const retire = db.prepare(`DELETE FROM schema_migrations_meta WHERE key IN (?, ?)
1500
+ const retire = cleanupStatement(db, `DELETE FROM schema_migrations_meta WHERE key IN (?, ?)
703
1501
  AND NOT (${survivingRows})
704
1502
  AND NOT EXISTS (SELECT 1 FROM schema_migrations_meta AS owners WHERE owners.key >= ? AND owners.key < ?)`);
705
1503
  for (const id of deletableSessionIds) {
@@ -710,10 +1508,59 @@ function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
710
1508
  retire.run(tagIdentityRevisionKey(id), tagRevisionKey(id), ...harnessTables.map(() => id), ...tagTombstoneOwnerRange(id));
711
1509
  }
712
1510
  }
713
- for (const sessionId of deletableSessionIds)
714
- clearLkgRecoveryFence(sessionId);
1511
+ if (!options.deferRecoveryFenceCleanup)
1512
+ for (const sessionId of deletableSessionIds)
1513
+ clearLkgRecoveryFence(sessionId);
715
1514
  return deletableSessionIds.length;
716
1515
  }
1516
+ var SESSION_CLEANUP_SLICE_ROWS = 64;
1517
+ function deleteSessionScopedRowsSlice(db, sessionId, harness) {
1518
+ if (cleanupStatement(db, "SELECT 1 FROM pending_session_cleanup WHERE session_id = ? AND harness = ?").get(sessionId, `${harness}:rust`))
1519
+ return { complete: true, deleted: 0 };
1520
+ cleanupStatement(db, "INSERT OR IGNORE INTO schema_migrations_meta(key,value) VALUES (?,?)").run(tagTombstoneOwnerKey(sessionId, harness), harness);
1521
+ let remaining = SESSION_CLEANUP_SLICE_ROWS;
1522
+ const started = performance.now();
1523
+ remaining -= deleteChunkEmbedBackoffForSession(db, sessionId, remaining);
1524
+ if (remaining <= 0)
1525
+ return { complete: false, deleted: 0 };
1526
+ for (const definition of SESSION_SCOPED_TABLES) {
1527
+ if (definition.table === "message_history_fts") {
1528
+ const rows = cleanupStatement(db, "SELECT fts_rowid AS id FROM message_fts_rowid_map WHERE session_id = ? LIMIT ?").all(sessionId, remaining);
1529
+ const removeFts = cleanupStatement(db, "DELETE FROM message_history_fts WHERE rowid = ?");
1530
+ const removeMap = cleanupStatement(db, "DELETE FROM message_fts_rowid_map WHERE session_id = ? AND fts_rowid = ?");
1531
+ for (const row of rows) {
1532
+ removeFts.run(row.id);
1533
+ removeMap.run(sessionId, row.id);
1534
+ }
1535
+ remaining -= rows.length;
1536
+ if (remaining > 0 && !getMessageFtsRowidMapBackfillProgress2(db).completed)
1537
+ remaining -= cleanupStatement(db, `DELETE FROM message_history_fts WHERE rowid IN (
1538
+ SELECT rowid FROM message_history_fts WHERE session_id = ? LIMIT ?
1539
+ )`).run(sessionId, remaining).changes;
1540
+ } else {
1541
+ const predicates = ["session_id = ?"];
1542
+ if (definition.extraPredicate)
1543
+ predicates.push(definition.extraPredicate);
1544
+ if (definition.harnessScoped)
1545
+ predicates.push("harness = ?");
1546
+ const args = [sessionId, ...definition.harnessScoped ? [harness] : [], remaining];
1547
+ const withoutRowid = definition.table === "temporal_decisions" || definition.table === "session_replay_decisions";
1548
+ const key = withoutRowid ? "message_id" : "rowid";
1549
+ remaining -= cleanupStatement(db, `DELETE FROM ${definition.table}
1550
+ WHERE ${withoutRowid ? "session_id = ? AND " : ""}${key} IN (
1551
+ SELECT ${key} FROM ${definition.table} WHERE ${predicates.join(" AND ")} LIMIT ?
1552
+ )`).run(...withoutRowid ? [sessionId] : [], ...args).changes;
1553
+ }
1554
+ if (remaining <= 0 || performance.now() - started >= 10)
1555
+ return { complete: false, deleted: 0 };
1556
+ }
1557
+ return {
1558
+ complete: true,
1559
+ deleted: deleteSessionScopedRows(db, [sessionId], harness, {
1560
+ deferRecoveryFenceCleanup: true
1561
+ })
1562
+ };
1563
+ }
717
1564
 
718
1565
  // src/features/magic-context/message-index.ts
719
1566
  var MESSAGE_HISTORY_ORPHAN_SWEEP_BATCH_SIZE = 200;
@@ -944,7 +1791,7 @@ function toMessageIndexSource2(message) {
944
1791
  role: message.role,
945
1792
  createdAt: message.createdAt,
946
1793
  sourceVersion: normalizeSourceVersion(message.version),
947
- contentHash: createHash3("sha256").update(content).digest("hex"),
1794
+ contentHash: createHash4("sha256").update(content).digest("hex"),
948
1795
  content
949
1796
  };
950
1797
  }
@@ -1046,10 +1893,10 @@ function getIndexableContent2(role, parts) {
1046
1893
  if (!hasMeaningfulUserText2(parts)) {
1047
1894
  return "";
1048
1895
  }
1049
- return extractTexts2(parts).map(cleanUserText2).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
1896
+ return extractTexts2(parts).map(removeTaskNotifications).map(cleanUserText2).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
1050
1897
  }
1051
1898
  if (role === "assistant") {
1052
- return extractTexts2(parts).map(removeSystemReminders).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
1899
+ return extractTexts2(parts).map(removeTaskNotifications).map(removeSystemReminders).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
1053
1900
  }
1054
1901
  return "";
1055
1902
  }
@@ -1184,7 +2031,7 @@ function indexItemsAfterOrdinal(db, sessionId, items, finalWatermark, toSource)
1184
2031
  return inserted;
1185
2032
  }
1186
2033
  function harnessSupportsOpenCodeOrphanSweep(harness) {
1187
- return harness === "opencode";
2034
+ return harness === "opencode" || harness === "opencode2";
1188
2035
  }
1189
2036
  function getMessageHistoryOrphanSweepState(db, harness) {
1190
2037
  return db.prepare("SELECT cursor_session_id, last_swept_at FROM message_history_orphan_sweep WHERE harness = ?").get(harness) ?? {};
@@ -1213,7 +2060,7 @@ function getOpenCodeSessionScopedCandidateSourceSql(harness) {
1213
2060
  UNION
1214
2061
  `);
1215
2062
  }
1216
- function sweepOrphanedOpenCodeMessageIndexesInBackground(db, openReadableOpenCodeDb, options = {}) {
2063
+ function* sweepOrphanedOpenCodeMessageIndexesInBackground(db, openReadableOpenCodeDb, options = {}) {
1217
2064
  const now = options.now ?? Date.now();
1218
2065
  const batchSize = Math.max(1, Math.floor(options.batchSize ?? MESSAGE_HISTORY_ORPHAN_SWEEP_BATCH_SIZE));
1219
2066
  const safetyAgeMs = Math.max(0, options.safetyAgeMs ?? MESSAGE_HISTORY_ORPHAN_SAFETY_AGE_MS);
@@ -1227,15 +2074,6 @@ function sweepOrphanedOpenCodeMessageIndexesInBackground(db, openReadableOpenCod
1227
2074
  }
1228
2075
  if (!harnessSupportsOpenCodeOrphanSweep(harness)) {
1229
2076
  persistMessageHistoryOrphanSweepState(db, cursor, now + unavailableReprobeMs - cooldownMs, harness);
1230
- if (harness === "opencode2") {
1231
- return {
1232
- status: "unavailable",
1233
- scanned: 0,
1234
- deleted: 0,
1235
- cursor,
1236
- reason: "OpenCode 2 keeps its sessions in session_v2, which this sweep does not read"
1237
- };
1238
- }
1239
2077
  return { status: "unavailable", scanned: 0, deleted: 0, cursor };
1240
2078
  }
1241
2079
  let openCodeDb = null;
@@ -1248,7 +2086,27 @@ function sweepOrphanedOpenCodeMessageIndexesInBackground(db, openReadableOpenCod
1248
2086
  persistMessageHistoryOrphanSweepState(db, cursor, now + unavailableReprobeMs - cooldownMs, harness);
1249
2087
  return { status: "source_unavailable", scanned: 0, deleted: 0, cursor };
1250
2088
  }
2089
+ let v2Reader;
1251
2090
  try {
2091
+ let sessionExists;
2092
+ if (harness === "opencode2") {
2093
+ try {
2094
+ const path = getSqliteDatabasePath2(openCodeDb);
2095
+ if (!path)
2096
+ throw new Error("OpenCode 2 source has no durable path");
2097
+ v2Reader = new V2StoreReader(path);
2098
+ const liveIds = new Set(v2Reader.sessionIds());
2099
+ if (liveIds.size === 0)
2100
+ throw new Error("OpenCode 2 source has no sessions");
2101
+ sessionExists = (id) => liveIds.has(id);
2102
+ } catch {
2103
+ persistMessageHistoryOrphanSweepState(db, cursor, now + unavailableReprobeMs - cooldownMs, harness);
2104
+ return { status: "source_unavailable", scanned: 0, deleted: 0, cursor };
2105
+ }
2106
+ } else {
2107
+ const query = openCodeDb.prepare("SELECT 1 FROM session WHERE id = ? LIMIT 1");
2108
+ sessionExists = (id) => !!query.get(id);
2109
+ }
1252
2110
  const cutoff = now - safetyAgeMs;
1253
2111
  const candidateSourceSql = getOpenCodeSessionScopedCandidateSourceSql(harness);
1254
2112
  const candidates = db.prepare(`SELECT session_id
@@ -1263,39 +2121,56 @@ function sweepOrphanedOpenCodeMessageIndexesInBackground(db, openReadableOpenCod
1263
2121
  )
1264
2122
  ORDER BY session_id ASC
1265
2123
  LIMIT ?`).all(cursor, cutoff, batchSize);
1266
- const sessionExists = openCodeDb.prepare("SELECT 1 FROM session WHERE id = ? LIMIT 1");
1267
- const missingSessionIds = candidates.filter((candidate) => !sessionExists.get(candidate.session_id)).map((candidate) => candidate.session_id);
2124
+ const missingSessionIds = candidates.filter((candidate) => !sessionExists(candidate.session_id)).map((candidate) => candidate.session_id);
1268
2125
  const nextCursor = candidates.length < batchSize ? "" : candidates[candidates.length - 1]?.session_id ?? cursor;
1269
2126
  const completedAt = candidates.length < batchSize ? now : null;
1270
- db.exec("BEGIN IMMEDIATE");
1271
- const transactionStartedAt = performance.now();
1272
- let committed = false;
1273
2127
  let deleted = 0;
1274
- try {
1275
- const stillEligible = db.prepare(`SELECT 1
1276
- FROM (${candidateSourceSql}) AS session_candidates
1277
- WHERE session_id = ?
1278
- AND NOT EXISTS (
1279
- SELECT 1
1280
- FROM message_history_index
1281
- WHERE message_history_index.session_id = session_candidates.session_id
1282
- AND message_history_index.harness = '${harness}'
1283
- AND message_history_index.updated_at > ?
1284
- )
1285
- LIMIT 1`);
1286
- const eligibleSessionIds = missingSessionIds.filter((sessionId) => !sessionExists.get(sessionId) && stillEligible.get(sessionId, cutoff));
1287
- deleted = deleteSessionScopedRows(db, eligibleSessionIds, harness);
1288
- persistMessageHistoryOrphanSweepState(db, nextCursor, completedAt, harness);
1289
- db.exec("COMMIT");
1290
- committed = true;
1291
- logSlowWriteTransaction("message_index_orphan_sweep", transactionStartedAt);
1292
- } finally {
1293
- if (!committed) {
2128
+ let slices = 0;
2129
+ const stillEligible = db.prepare(`SELECT 1 WHERE NOT EXISTS (
2130
+ SELECT 1 FROM message_history_index
2131
+ WHERE session_id = ? AND harness = '${harness}' AND updated_at > ?
2132
+ )`);
2133
+ for (const sessionId of missingSessionIds) {
2134
+ let complete = false;
2135
+ do {
2136
+ let removed = 0;
2137
+ db.exec("BEGIN IMMEDIATE");
2138
+ const transactionStartedAt = performance.now();
2139
+ let committed = false;
1294
2140
  try {
1295
- db.exec("ROLLBACK");
1296
- } catch {}
1297
- }
2141
+ const hostStillMissing = v2Reader ? !v2Reader.sessionExists(sessionId) : !sessionExists(sessionId);
2142
+ if (hostStillMissing && stillEligible.get(sessionId, cutoff)) {
2143
+ const slice = deleteSessionScopedRowsSlice(db, sessionId, harness);
2144
+ removed = slice.deleted;
2145
+ complete = slice.complete;
2146
+ } else
2147
+ complete = true;
2148
+ db.exec("COMMIT");
2149
+ committed = true;
2150
+ logSlowWriteTransaction("message_index_orphan_sweep", transactionStartedAt);
2151
+ } finally {
2152
+ if (!committed) {
2153
+ try {
2154
+ db.exec("ROLLBACK");
2155
+ } catch {}
2156
+ }
2157
+ }
2158
+ deleted += removed;
2159
+ if (removed)
2160
+ clearLkgRecoveryFence(sessionId);
2161
+ yield;
2162
+ if (++slices >= Math.max(1, options.maxSlices ?? 512)) {
2163
+ return {
2164
+ status: "swept",
2165
+ scanned: candidates.length,
2166
+ deleted,
2167
+ cursor,
2168
+ partial: true
2169
+ };
2170
+ }
2171
+ } while (!complete);
1298
2172
  }
2173
+ persistMessageHistoryOrphanSweepState(db, nextCursor, completedAt, harness);
1299
2174
  return {
1300
2175
  status: "swept",
1301
2176
  scanned: candidates.length,
@@ -1303,11 +2178,24 @@ function sweepOrphanedOpenCodeMessageIndexesInBackground(db, openReadableOpenCod
1303
2178
  cursor: nextCursor
1304
2179
  };
1305
2180
  } finally {
2181
+ v2Reader?.close();
1306
2182
  closeQuietly(openCodeDb);
1307
2183
  }
1308
2184
  }
1309
- function sweepOrphanedOpenCodeMessageIndexes2(db, openReadableOpenCodeDb, options = {}) {
1310
- return withoutSqliteTransformPass2(() => withSqliteBackgroundWriter2(() => sweepOrphanedOpenCodeMessageIndexesInBackground(db, openReadableOpenCodeDb, options)));
2185
+ async function sweepOrphanedOpenCodeMessageIndexesCooperatively2(db, openSource, options = {}) {
2186
+ return withoutSqliteTransformPass2(() => withSqliteBackgroundWriter2(async () => {
2187
+ const sweep = sweepOrphanedOpenCodeMessageIndexesInBackground(db, openSource, options);
2188
+ try {
2189
+ for (;; ) {
2190
+ const step = sweep.next();
2191
+ if (step.done)
2192
+ return step.value;
2193
+ await new Promise((resolve) => setTimeout(resolve, 0));
2194
+ }
2195
+ } finally {
2196
+ sweep.return({ status: "source_unavailable", scanned: 0, deleted: 0, cursor: "" });
2197
+ }
2198
+ }));
1311
2199
  }
1312
2200
 
1313
- export { BoundedSessionMap, advanceSessionActivity, observeSessionActivity, backfillSessionActivity, clearCompressionDepth, clearCompressionDepthRange, LkgRecoveryFenceError, clearLkgFenceProcessState, lkgServedArrayDigest, hasCleanLkgRecoveryFence, lkgFenceRequiresRebuild, lkgRecoveryFenceUnclean, dirtyLkgRecoveryRevision, ownsLkgDirtyFence, invalidateCleanLkgRecoveryFence, prepareLkgCaptureFence, certifyCommittedLkgCapture, markLkgRecoveryDirty, commitLkgWithRecoveryFence, clearLkgRecoveryFence, deleteSessionScopedRows, MESSAGE_INDEX_MAX_FTS_BYTES2, messageIndexSkipKey2, toMessageIndexSource2, getMessageIndexSourceIdentity2, isMessageIndexSourceCurrent2, getLastIndexedOrdinal2, getIndexedMessageCorpusSize2, markMessageIndexDirty2, getMessageIndexReconciliationStartOrdinal2, isMessageIndexReconciledThrough2, deleteIndexedMessage2, clearIndexedMessagesInTransaction2, clearIndexedMessages2, getIndexableContent2, indexSingleMessage2, indexMessagesAfterOrdinal2, indexSourcesAfterOrdinal2, sweepOrphanedOpenCodeMessageIndexes2 };
2201
+ export { BoundedSessionMap, advanceSessionActivity, observeSessionActivity, backfillSessionActivity, 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, deleteSessionScopedRows, MESSAGE_INDEX_MAX_FTS_BYTES2, messageIndexSkipKey2, toMessageIndexSource2, getMessageIndexSourceIdentity2, isMessageIndexSourceCurrent2, getLastIndexedOrdinal2, getIndexedMessageCorpusSize2, markMessageIndexDirty2, getMessageIndexReconciliationStartOrdinal2, isMessageIndexReconciledThrough2, deleteIndexedMessage2, clearIndexedMessagesInTransaction2, clearIndexedMessages2, getIndexableContent2, indexSingleMessage2, indexMessagesAfterOrdinal2, indexSourcesAfterOrdinal2, sweepOrphanedOpenCodeMessageIndexesCooperatively2 };