@cortexkit/opencode-magic-context 0.45.0 → 0.46.1

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 (396) hide show
  1. package/README.md +26 -0
  2. package/dist/{announcement-f75t58v9.js → announcement-y35wzg2r.js} +1 -1
  3. package/dist/config/index.d.ts.map +1 -1
  4. package/dist/config/live-run-config.d.ts.map +1 -1
  5. package/dist/config/migrate-config-location.d.ts.map +1 -1
  6. package/dist/config/schema/magic-context.d.ts +13 -7
  7. package/dist/config/schema/magic-context.d.ts.map +1 -1
  8. package/dist/{conflict-warning-hook-5yrz2r24.js → conflict-warning-hook-7fj2b9jw.js} +6 -5
  9. package/dist/context-sh2m2vde.js +4525 -0
  10. package/dist/{derive-budgets-a63zsm5c.js → derive-budgets-7nj7r1rv.js} +2 -2
  11. package/dist/embedding-worker.js +4 -4
  12. package/dist/features/magic-context/checkout-claim-fake-subc.test-support.d.ts +45 -0
  13. package/dist/features/magic-context/checkout-claim-fake-subc.test-support.d.ts.map +1 -0
  14. package/dist/features/magic-context/checkout-claim.d.ts +155 -0
  15. package/dist/features/magic-context/checkout-claim.d.ts.map +1 -0
  16. package/dist/features/magic-context/compaction-marker.d.ts +20 -3
  17. package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
  18. package/dist/features/magic-context/compartment-storage.d.ts +23 -2
  19. package/dist/features/magic-context/compartment-storage.d.ts.map +1 -1
  20. package/dist/features/magic-context/dreamer/classify.d.ts.map +1 -1
  21. package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts.map +1 -1
  22. package/dist/features/magic-context/dreamer/provider-output-failure.d.ts +6 -2
  23. package/dist/features/magic-context/dreamer/provider-output-failure.d.ts.map +1 -1
  24. package/dist/features/magic-context/dreamer/provider-output-failure.test-support.d.ts +30 -0
  25. package/dist/features/magic-context/dreamer/provider-output-failure.test-support.d.ts.map +1 -0
  26. package/dist/features/magic-context/dreamer/storage-dream-runs.d.ts.map +1 -1
  27. package/dist/features/magic-context/dreamer/task-executor.d.ts +9 -0
  28. package/dist/features/magic-context/dreamer/task-executor.d.ts.map +1 -1
  29. package/dist/features/magic-context/dreamer/task-gates.d.ts.map +1 -1
  30. package/dist/features/magic-context/dreamer/task-scheduler.d.ts.map +1 -1
  31. package/dist/features/magic-context/dreamer/verify-diff.d.ts.map +1 -1
  32. package/dist/features/magic-context/dreamer/verify-gate.d.ts.map +1 -1
  33. package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
  34. package/dist/features/magic-context/git-commits/search-git-commits.d.ts.map +1 -1
  35. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  36. package/dist/features/magic-context/memory/project-identity-cache.d.ts.map +1 -1
  37. package/dist/features/magic-context/memory/storage-memory-verifications.d.ts.map +1 -1
  38. package/dist/features/magic-context/memory/storage-memory.d.ts +3 -0
  39. package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
  40. package/dist/features/magic-context/memory/verification-paths.d.ts +2 -2
  41. package/dist/features/magic-context/memory/verification-paths.d.ts.map +1 -1
  42. package/dist/features/magic-context/merged-reasoning-decisions.d.ts.map +1 -1
  43. package/dist/features/magic-context/message-index-async.d.ts +3 -3
  44. package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
  45. package/dist/features/magic-context/message-index.d.ts +2 -0
  46. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  47. package/dist/features/magic-context/migration-v95-perf-indexes.d.ts +12 -0
  48. package/dist/features/magic-context/migration-v95-perf-indexes.d.ts.map +1 -0
  49. package/dist/features/magic-context/migration-worker-client.d.ts +4 -4
  50. package/dist/features/magic-context/migration-worker-client.d.ts.map +1 -1
  51. package/dist/features/magic-context/migrations.d.ts +1 -0
  52. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  53. package/dist/features/magic-context/mural/render-trigger.d.ts.map +1 -1
  54. package/dist/features/magic-context/mural/resolve-mural.d.ts +15 -4
  55. package/dist/features/magic-context/mural/resolve-mural.d.ts.map +1 -1
  56. package/dist/features/magic-context/mural/source-revision.d.ts +7 -0
  57. package/dist/features/magic-context/mural/source-revision.d.ts.map +1 -0
  58. package/dist/features/magic-context/mural/storage-mural-cues.d.ts.map +1 -1
  59. package/dist/features/magic-context/reclaim-protection.d.ts +15 -10
  60. package/dist/features/magic-context/reclaim-protection.d.ts.map +1 -1
  61. package/dist/features/magic-context/session-cache-ttl.d.ts +4 -2
  62. package/dist/features/magic-context/session-cache-ttl.d.ts.map +1 -1
  63. package/dist/features/magic-context/smart-notes/capabilities.d.ts +3 -0
  64. package/dist/features/magic-context/smart-notes/capabilities.d.ts.map +1 -1
  65. package/dist/features/magic-context/smart-notes/compiler.d.ts.map +1 -1
  66. package/dist/features/magic-context/smart-notes/github-token.d.ts +5 -0
  67. package/dist/features/magic-context/smart-notes/github-token.d.ts.map +1 -0
  68. package/dist/features/magic-context/smart-notes/runner.d.ts +1 -0
  69. package/dist/features/magic-context/smart-notes/runner.d.ts.map +1 -1
  70. package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts +12 -0
  71. package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts.map +1 -1
  72. package/dist/features/magic-context/smart-notes/ssrf-guard.d.ts +6 -0
  73. package/dist/features/magic-context/smart-notes/ssrf-guard.d.ts.map +1 -1
  74. package/dist/features/magic-context/smart-notes/storage.d.ts.map +1 -1
  75. package/dist/features/magic-context/smart-notes/types.d.ts +2 -0
  76. package/dist/features/magic-context/smart-notes/types.d.ts.map +1 -1
  77. package/dist/features/magic-context/storage-clone.d.ts.map +1 -1
  78. package/dist/features/magic-context/storage-db.d.ts +6 -9
  79. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  80. package/dist/features/magic-context/storage-meta-shared.d.ts +1 -1
  81. package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
  82. package/dist/features/magic-context/storage-ops.d.ts +8 -1
  83. package/dist/features/magic-context/storage-ops.d.ts.map +1 -1
  84. package/dist/features/magic-context/storage-replay-document.d.ts +7 -0
  85. package/dist/features/magic-context/storage-replay-document.d.ts.map +1 -1
  86. package/dist/features/magic-context/storage-schema-helpers.d.ts +2 -0
  87. package/dist/features/magic-context/storage-schema-helpers.d.ts.map +1 -1
  88. package/dist/features/magic-context/storage-session-tables.d.ts.map +1 -1
  89. package/dist/features/magic-context/storage-source.d.ts.map +1 -1
  90. package/dist/features/magic-context/storage-tags.d.ts +11 -3
  91. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  92. package/dist/features/magic-context/storage.d.ts +2 -2
  93. package/dist/features/magic-context/storage.d.ts.map +1 -1
  94. package/dist/features/magic-context/temporal-decisions.d.ts +10 -0
  95. package/dist/features/magic-context/temporal-decisions.d.ts.map +1 -0
  96. package/dist/features/magic-context/tool-definition-tokens.d.ts +2 -0
  97. package/dist/features/magic-context/tool-definition-tokens.d.ts.map +1 -1
  98. package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
  99. package/dist/features/magic-context/types.d.ts +7 -6
  100. package/dist/features/magic-context/types.d.ts.map +1 -1
  101. package/dist/features/magic-context/window-report-ledger.d.ts.map +1 -1
  102. package/dist/hooks/auto-update-checker/index.d.ts.map +1 -1
  103. package/dist/hooks/magic-context/apply-operations.d.ts +8 -0
  104. package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
  105. package/dist/hooks/magic-context/cache-busting-signals.d.ts +6 -3
  106. package/dist/hooks/magic-context/cache-busting-signals.d.ts.map +1 -1
  107. package/dist/hooks/magic-context/channel2-cycle.d.ts +2 -0
  108. package/dist/hooks/magic-context/channel2-cycle.d.ts.map +1 -1
  109. package/dist/hooks/magic-context/compaction-marker-manager.d.ts +7 -0
  110. package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -1
  111. package/dist/hooks/magic-context/compartment-prompt.d.ts +2 -2
  112. package/dist/hooks/magic-context/compartment-prompt.d.ts.map +1 -1
  113. package/dist/hooks/magic-context/compartment-render-epoch.d.ts +8 -0
  114. package/dist/hooks/magic-context/compartment-render-epoch.d.ts.map +1 -1
  115. package/dist/hooks/magic-context/compartment-runner-drop-queue.d.ts +18 -4
  116. package/dist/hooks/magic-context/compartment-runner-drop-queue.d.ts.map +1 -1
  117. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  118. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
  119. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  120. package/dist/hooks/magic-context/ctx-reduce-availability.d.ts +12 -0
  121. package/dist/hooks/magic-context/ctx-reduce-availability.d.ts.map +1 -1
  122. package/dist/hooks/magic-context/decision-calibration.d.ts +3 -0
  123. package/dist/hooks/magic-context/decision-calibration.d.ts.map +1 -1
  124. package/dist/hooks/magic-context/default-protection-pressure.test-support.d.ts +37 -0
  125. package/dist/hooks/magic-context/default-protection-pressure.test-support.d.ts.map +1 -0
  126. package/dist/hooks/magic-context/drop-stale-reduce-calls.d.ts +4 -0
  127. package/dist/hooks/magic-context/drop-stale-reduce-calls.d.ts.map +1 -1
  128. package/dist/hooks/magic-context/edit-marker.d.ts.map +1 -1
  129. package/dist/hooks/magic-context/emergency-drop.d.ts +2 -0
  130. package/dist/hooks/magic-context/emergency-drop.d.ts.map +1 -1
  131. package/dist/hooks/magic-context/emergency-fail-closed.d.ts +21 -1
  132. package/dist/hooks/magic-context/emergency-fail-closed.d.ts.map +1 -1
  133. package/dist/hooks/magic-context/event-handler.d.ts +5 -0
  134. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  135. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +30 -1
  136. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
  137. package/dist/hooks/magic-context/heuristic-cleanup.d.ts +2 -0
  138. package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
  139. package/dist/hooks/magic-context/historian-prompt-fit.d.ts +4 -2
  140. package/dist/hooks/magic-context/historian-prompt-fit.d.ts.map +1 -1
  141. package/dist/hooks/magic-context/hook.d.ts +2 -1
  142. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  143. package/dist/hooks/magic-context/inject-compartments.d.ts +9 -10
  144. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  145. package/dist/hooks/magic-context/lkg-measured-request.d.ts +69 -0
  146. package/dist/hooks/magic-context/lkg-measured-request.d.ts.map +1 -0
  147. package/dist/hooks/magic-context/lkg-persist.d.ts +2 -0
  148. package/dist/hooks/magic-context/lkg-persist.d.ts.map +1 -1
  149. package/dist/hooks/magic-context/lkg-replay-fit.d.ts +14 -0
  150. package/dist/hooks/magic-context/lkg-replay-fit.d.ts.map +1 -1
  151. package/dist/hooks/magic-context/lkg-replay.d.ts +10 -1
  152. package/dist/hooks/magic-context/lkg-replay.d.ts.map +1 -1
  153. package/dist/hooks/magic-context/lkg-slot.d.ts +4 -0
  154. package/dist/hooks/magic-context/lkg-slot.d.ts.map +1 -1
  155. package/dist/hooks/magic-context/module-state-sync.d.ts +2 -0
  156. package/dist/hooks/magic-context/module-state-sync.d.ts.map +1 -1
  157. package/dist/hooks/magic-context/module-transport.d.ts.map +1 -1
  158. package/dist/hooks/magic-context/module-wire.d.ts.map +1 -1
  159. package/dist/hooks/magic-context/note-nudger.d.ts +17 -5
  160. package/dist/hooks/magic-context/note-nudger.d.ts.map +1 -1
  161. package/dist/hooks/magic-context/read-session-chunk.d.ts +1 -1
  162. package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
  163. package/dist/hooks/magic-context/read-session-db.d.ts +1 -0
  164. package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
  165. package/dist/hooks/magic-context/read-session-formatting.d.ts +7 -0
  166. package/dist/hooks/magic-context/read-session-formatting.d.ts.map +1 -1
  167. package/dist/hooks/magic-context/read-session-raw.d.ts +4 -0
  168. package/dist/hooks/magic-context/read-session-raw.d.ts.map +1 -1
  169. package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
  170. package/dist/hooks/magic-context/recomp-orchestrator.d.ts +2 -0
  171. package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
  172. package/dist/hooks/magic-context/reference-retrieval.d.ts +27 -17
  173. package/dist/hooks/magic-context/reference-retrieval.d.ts.map +1 -1
  174. package/dist/hooks/magic-context/request-hook-order.d.ts +56 -0
  175. package/dist/hooks/magic-context/request-hook-order.d.ts.map +1 -0
  176. package/dist/hooks/magic-context/rust-mode-transform.d.ts +22 -3
  177. package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
  178. package/dist/hooks/magic-context/strip-content.d.ts +4 -2
  179. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  180. package/dist/hooks/magic-context/supersession-reclaim.d.ts +4 -0
  181. package/dist/hooks/magic-context/supersession-reclaim.d.ts.map +1 -1
  182. package/dist/hooks/magic-context/system-prompt-hash.d.ts +7 -0
  183. package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
  184. package/dist/hooks/magic-context/tail-hygiene-walk.d.ts +12 -3
  185. package/dist/hooks/magic-context/tail-hygiene-walk.d.ts.map +1 -1
  186. package/dist/hooks/magic-context/temporal-awareness.d.ts +6 -3
  187. package/dist/hooks/magic-context/temporal-awareness.d.ts.map +1 -1
  188. package/dist/hooks/magic-context/temporal-served-projection.d.ts +4 -0
  189. package/dist/hooks/magic-context/temporal-served-projection.d.ts.map +1 -0
  190. package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
  191. package/dist/hooks/magic-context/tool-reclaim.d.ts +2 -0
  192. package/dist/hooks/magic-context/tool-reclaim.d.ts.map +1 -1
  193. package/dist/hooks/magic-context/transform-message-helpers.d.ts +5 -0
  194. package/dist/hooks/magic-context/transform-message-helpers.d.ts.map +1 -1
  195. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +41 -17
  196. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  197. package/dist/hooks/magic-context/transform.d.ts +12 -0
  198. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  199. package/dist/hooks/magic-context/wrapup-orchestrator.d.ts.map +1 -1
  200. package/dist/{index-vvtfyw1z.js → index-0d3g3461.js} +5 -13
  201. package/dist/index-1bc61ans.js +154 -0
  202. package/dist/{index-n96ag4rk.js → index-1zx87sdm.js} +5 -1
  203. package/dist/{index-4krbw8z9.js → index-3h0ybwah.js} +608 -102
  204. package/dist/{index-gd5qx1j9.js → index-5eqk6z3s.js} +62 -1
  205. package/dist/index-5gq3b2ya.js +494 -0
  206. package/dist/{index-8fgkmxmy.js → index-5p8jdswx.js} +4 -3
  207. package/dist/{index-75zjgwga.js → index-7qw742ev.js} +1 -1
  208. package/dist/{index-f85teqck.js → index-8da2zjhz.js} +3 -256
  209. package/dist/{index-q7yesyk9.js → index-8x2v7gg9.js} +25 -24
  210. package/dist/{index-w9jwmntv.js → index-b35pxds2.js} +9 -1
  211. package/dist/{v2/server-a4m2b1yv.js → index-fg34g6a5.js} +55 -1
  212. package/dist/index-hpecdzec.js +28506 -0
  213. package/dist/{index-2ntg5sd4.js → index-k5bdx1bx.js} +9 -5
  214. package/dist/{index-bmzf7zxv.js → index-majxda46.js} +23 -5
  215. package/dist/{index-2aq21j41.js → index-pkbcrdqs.js} +418 -127
  216. package/dist/{index-remp7e2g.js → index-q4yp4b52.js} +16 -1
  217. package/dist/{index-fr2z4qy8.js → index-r5hmbx3t.js} +132 -169
  218. package/dist/index-sfnj6h8h.js +259 -0
  219. package/dist/{index-xgt9k6c6.js → index-sgthv3f7.js} +2 -1
  220. package/dist/index-sj29b3z2.js +30 -0
  221. package/dist/{index-16ny736w.js → index-sjthx78w.js} +20526 -19817
  222. package/dist/index.d.ts.map +1 -1
  223. package/dist/index.js +1983 -33118
  224. package/dist/{message-time-backfill-an311pmk.js → message-time-backfill-68gj6t3e.js} +2 -2
  225. package/dist/migration-worker.js +12 -7
  226. package/dist/plugin/dream-timer.d.ts +25 -1
  227. package/dist/plugin/dream-timer.d.ts.map +1 -1
  228. package/dist/plugin/hooks/create-session-hooks.d.ts +2 -1
  229. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  230. package/dist/plugin/messages-transform.d.ts +13 -0
  231. package/dist/plugin/messages-transform.d.ts.map +1 -1
  232. package/dist/plugin/rpc-handlers.d.ts +4 -0
  233. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  234. package/dist/plugin/subagent-tool-policy.d.ts +14 -0
  235. package/dist/plugin/subagent-tool-policy.d.ts.map +1 -0
  236. package/dist/plugin/tool-registry.d.ts +1 -0
  237. package/dist/plugin/tool-registry.d.ts.map +1 -1
  238. package/dist/{read-session-chunk-8rxs0tc9.js → read-session-chunk-egz7r68d.js} +8 -8
  239. package/dist/{recomp-orchestrator-nzamtw29.js → recomp-orchestrator-y15j0rb9.js} +15 -15
  240. package/dist/{send-session-notification-nqpv7cne.js → send-session-notification-dyw42dtr.js} +4 -4
  241. package/dist/shared/announcement.d.ts.map +1 -1
  242. package/dist/shared/cache-ttl-display.d.ts +6 -4
  243. package/dist/shared/cache-ttl-display.d.ts.map +1 -1
  244. package/dist/shared/model-cache-ttl.d.ts +1 -1
  245. package/dist/shared/model-cache-ttl.d.ts.map +1 -1
  246. package/dist/shared/model-suggestion-retry.d.ts +2 -0
  247. package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
  248. package/dist/shared/models-dev-cache.d.ts.map +1 -1
  249. package/dist/shared/protected-tools-policy.d.ts +6 -0
  250. package/dist/shared/protected-tools-policy.d.ts.map +1 -0
  251. package/dist/shared/quota-diagnostic.d.ts +4 -0
  252. package/dist/shared/quota-diagnostic.d.ts.map +1 -0
  253. package/dist/shared/rpc-server.d.ts.map +1 -1
  254. package/dist/shared/sqlite-context-pragmas.d.ts +12 -0
  255. package/dist/shared/sqlite-context-pragmas.d.ts.map +1 -0
  256. package/dist/shared/sqlite-helpers.d.ts +9 -0
  257. package/dist/shared/sqlite-helpers.d.ts.map +1 -1
  258. package/dist/shared/sqlite.d.ts +10 -0
  259. package/dist/shared/sqlite.d.ts.map +1 -1
  260. package/dist/shared/status-summary.d.ts +1 -0
  261. package/dist/shared/status-summary.d.ts.map +1 -1
  262. package/dist/shared/status-view-check.d.ts.map +1 -1
  263. package/dist/shared/status-view.d.ts +4 -0
  264. package/dist/shared/status-view.d.ts.map +1 -1
  265. package/dist/shared/storage-permissions.d.ts +24 -0
  266. package/dist/shared/storage-permissions.d.ts.map +1 -1
  267. package/dist/shared/temporal-upgrade-fixture.d.ts +19 -0
  268. package/dist/shared/temporal-upgrade-fixture.d.ts.map +1 -0
  269. package/dist/shared/user-facing-codes.d.ts +5 -0
  270. package/dist/shared/user-facing-codes.d.ts.map +1 -1
  271. package/dist/shared/write-transaction-timing.d.ts +8 -1
  272. package/dist/shared/write-transaction-timing.d.ts.map +1 -1
  273. package/dist/tools/ctx-expand/render.d.ts.map +1 -1
  274. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  275. package/dist/tools/ctx-reduce/constants.d.ts +2 -0
  276. package/dist/tools/ctx-reduce/constants.d.ts.map +1 -1
  277. package/dist/tools/ctx-reduce/tools.d.ts +4 -4
  278. package/dist/tools/ctx-reduce/tools.d.ts.map +1 -1
  279. package/dist/{transform-postprocess-phase-414phndq.js → transform-postprocess-phase-bqhz6vdr.js} +15 -15
  280. package/dist/v2/context-zecmzk5s.js +52209 -0
  281. package/dist/v2/{derive-budgets-v0krf3t7.js → derive-budgets-kdgp4s3r.js} +4 -3
  282. package/dist/v2/embedding-worker.js +6 -5
  283. package/dist/v2/fold/boundary.d.ts +2 -0
  284. package/dist/v2/fold/boundary.d.ts.map +1 -1
  285. package/dist/v2/fold/owner.d.ts.map +1 -1
  286. package/dist/v2/hidden-child-native.d.ts +7 -0
  287. package/dist/v2/hidden-child-native.d.ts.map +1 -1
  288. package/dist/v2/hidden-child-record.d.ts +8 -0
  289. package/dist/v2/hidden-child-record.d.ts.map +1 -1
  290. package/dist/v2/hidden-completion.d.ts.map +1 -1
  291. package/dist/v2/hooks/channel2.d.ts +2 -0
  292. package/dist/v2/hooks/channel2.d.ts.map +1 -1
  293. package/dist/v2/hooks/context.d.ts +2 -2
  294. package/dist/v2/hooks/context.d.ts.map +1 -1
  295. package/dist/v2/hooks/dream-timer.d.ts +39 -0
  296. package/dist/v2/hooks/dream-timer.d.ts.map +1 -0
  297. package/dist/v2/hooks/dream-trigger.d.ts +15 -1
  298. package/dist/v2/hooks/dream-trigger.d.ts.map +1 -1
  299. package/dist/v2/hooks/hidden-child.d.ts +54 -3
  300. package/dist/v2/hooks/hidden-child.d.ts.map +1 -1
  301. package/dist/v2/hooks/payload.d.ts.map +1 -1
  302. package/dist/v2/hooks/store.d.ts.map +1 -1
  303. package/dist/v2/hooks/tools.d.ts +1 -1
  304. package/dist/v2/hooks/tools.d.ts.map +1 -1
  305. package/dist/v2/hooks/types.d.ts +12 -1
  306. package/dist/v2/hooks/types.d.ts.map +1 -1
  307. package/dist/v2/hooks/update-check.d.ts.map +1 -1
  308. package/dist/v2/{message-time-backfill-0n93vrd7.js → message-time-backfill-r6qkem1x.js} +3 -3
  309. package/dist/v2/migration-worker.js +13 -8
  310. package/dist/v2/{read-session-chunk-hjza38hr.js → read-session-chunk-8vjp6581.js} +9 -9
  311. package/dist/v2/recomp-orchestrator-8ejxvrzz.js +39 -0
  312. package/dist/v2/{safe-notification-target-wtj0bbc2.js → safe-notification-target-eb1n1vfb.js} +2 -2
  313. package/dist/v2/{send-session-notification-mq9frrtm.js → send-session-notification-qs0j78v6.js} +6 -6
  314. package/dist/v2/server-14vq4kpm.js +30 -0
  315. package/dist/v2/server-1bc61ans.js +154 -0
  316. package/dist/v2/{server-n96ag4rk.js → server-1zx87sdm.js} +5 -1
  317. package/dist/v2/server-3n4q9xme.js +79 -0
  318. package/dist/v2/{server-rhdgxp1t.js → server-51dv1pr5.js} +63 -2
  319. package/dist/v2/{server-5317ykaf.js → server-6r257mf5.js} +1 -1
  320. package/dist/v2/server-84w6rvvf.js +14 -0
  321. package/dist/v2/{server-qpg6wf72.js → server-ajzeteqw.js} +9 -1
  322. package/dist/v2/server-c5gz8wph.js +3892 -0
  323. package/dist/v2/{server-v5xgft5v.js → server-e29d7p7c.js} +188 -3533
  324. package/dist/{index-a4m2b1yv.js → v2/server-fg34g6a5.js} +55 -1
  325. package/dist/v2/{server-e36nf2p1.js → server-frbm99q5.js} +1595 -1599
  326. package/dist/v2/{server-remp7e2g.js → server-g0fjrkta.js} +16 -1
  327. package/dist/v2/{server-c4g2y1z8.js → server-jc7y02q0.js} +2816 -5392
  328. package/dist/v2/{server-z19x7eqz.js → server-mcfawjgc.js} +4 -4
  329. package/dist/v2/{server-hfjfbg59.js → server-qp5hx1ad.js} +608 -105
  330. package/dist/v2/server-vs5gpnd4.js +493 -0
  331. package/dist/v2/{server-w99cyt6a.js → server-wvjvfhck.js} +14 -6
  332. package/dist/v2/server-xa4xe3rv.js +3351 -0
  333. package/dist/v2/{server-0jqw05sa.js → server-y8maxeft.js} +6 -84
  334. package/dist/v2/server.d.ts.map +1 -1
  335. package/dist/v2/server.js +104 -49288
  336. package/dist/v2/store-reader.d.ts +39 -1
  337. package/dist/v2/store-reader.d.ts.map +1 -1
  338. package/dist/v2/tool-result.d.ts +8 -0
  339. package/dist/v2/tool-result.d.ts.map +1 -0
  340. package/dist/v2/tui/index.d.ts.map +1 -1
  341. package/dist/v2/{wrapup-orchestrator-tbyx78dz.js → wrapup-orchestrator-0snhvwcv.js} +20 -16
  342. package/dist/wrapup-orchestrator-39amhv9s.js +26 -0
  343. package/package.json +26 -10
  344. package/src/config/index.ts +21 -4
  345. package/src/config/latch-permanence-guard.test.ts +6 -1
  346. package/src/config/live-run-config.ts +1 -0
  347. package/src/config/migrate-config-location.test.ts +31 -1
  348. package/src/config/migrate-config-location.ts +8 -41
  349. package/src/config/protected-tools.test.ts +70 -0
  350. package/src/config/raw-loader.test.ts +15 -0
  351. package/src/config/raw-loader.ts +1 -1
  352. package/src/config/removed-agent-source-fence.test.ts +10 -1
  353. package/src/config/schema/magic-context.test.ts +20 -1
  354. package/src/config/schema/magic-context.ts +26 -7
  355. package/src/shared/announcement.ts +6 -5
  356. package/src/shared/cache-ttl-display.test.ts +1 -1
  357. package/src/shared/cache-ttl-display.ts +17 -16
  358. package/src/shared/fold-render-lock-fence.test.ts +84 -0
  359. package/src/shared/fold-render-lock-fixtures.test.ts +276 -0
  360. package/src/shared/model-cache-ttl.test.ts +2 -2
  361. package/src/shared/model-cache-ttl.ts +8 -4
  362. package/src/shared/model-suggestion-retry.test.ts +104 -1
  363. package/src/shared/model-suggestion-retry.ts +63 -6
  364. package/src/shared/models-dev-cache.ts +3 -11
  365. package/src/shared/protected-tools-policy.ts +23 -0
  366. package/src/shared/quota-diagnostic.ts +9 -0
  367. package/src/shared/rpc-server.ts +6 -54
  368. package/src/shared/sqlite-context-pragmas.test.ts +105 -0
  369. package/src/shared/sqlite-context-pragmas.ts +24 -0
  370. package/src/shared/sqlite-helpers.ts +17 -0
  371. package/src/shared/sqlite.ts +75 -0
  372. package/src/shared/status-detail-text.test.ts +18 -1
  373. package/src/shared/status-summary.ts +11 -0
  374. package/src/shared/status-view-check.ts +5 -0
  375. package/src/shared/status-view.test.ts +11 -2
  376. package/src/shared/status-view.ts +12 -1
  377. package/src/shared/storage-permissions.test.ts +204 -0
  378. package/src/shared/storage-permissions.ts +192 -0
  379. package/src/shared/temporal-upgrade-fixture.ts +50 -0
  380. package/src/shared/user-facing-codes.ts +6 -0
  381. package/src/shared/write-transaction-timing.test.ts +39 -0
  382. package/src/shared/write-transaction-timing.ts +25 -1
  383. package/src/tui/dialogs/status-dialog-render.test.ts +1 -1
  384. package/src/tui/index.tsx +3 -1
  385. package/src/tui/startup-order.test.ts +16 -0
  386. package/src/tui-compiled/index.tsx +3 -1
  387. package/src/v2/tui/index.ts +9 -2
  388. package/dist/index-anq4nn6f.js +0 -131
  389. package/dist/index-e44zzy84.js +0 -10
  390. package/dist/index-x23gah8c.js +0 -16
  391. package/dist/v2/recomp-orchestrator-1aq7h44c.js +0 -36
  392. package/dist/v2/server-e44zzy84.js +0 -10
  393. package/dist/v2/server-fgmxwrgy.js +0 -16
  394. package/dist/v2/server-r7cmye3k.js +0 -131
  395. package/dist/wrapup-orchestrator-h28bqsy6.js +0 -26
  396. package/dist/v2/{server-ckpc8pxy.js → server-1qbnzc80.js} +8 -8
@@ -6,14 +6,21 @@ import {
6
6
  importPluginModule,
7
7
  writeForwardedLogLine,
8
8
  log
9
- } from "./server-ckpc8pxy.js";
9
+ } from "./server-1qbnzc80.js";
10
10
  import {
11
+ ensureColumn,
12
+ healAllNullColumns,
13
+ installV95PerfSchema,
14
+ setDatabase,
15
+ loadToolDefinitionMeasurements
16
+ } from "./server-vs5gpnd4.js";
17
+ import {
18
+ closeQuietly,
11
19
  resolveOpenCodeDbPath,
12
20
  hasV1MessageTables,
13
21
  openCodeDbPathExists,
14
- getOpenCodeDbProbeDescriptions,
15
- closeQuietly
16
- } from "./server-qpg6wf72.js";
22
+ getOpenCodeDbProbeDescriptions
23
+ } from "./server-ajzeteqw.js";
17
24
  import {
18
25
  registerSlowWriteReporter,
19
26
  registerSqliteDiagnosticSink,
@@ -23,1479 +30,1610 @@ import {
23
30
  withoutSqliteTransformPass,
24
31
  isTransientSqliteError,
25
32
  withPrivilegedWriter
26
- } from "./server-a4m2b1yv.js";
27
- import {
28
- setDatabase,
29
- loadToolDefinitionMeasurements
30
- } from "./server-r7cmye3k.js";
33
+ } from "./server-fg34g6a5.js";
31
34
  import {
32
35
  getErrorMessage
33
36
  } from "./server-y8v6yerm.js";
34
37
  import {
35
- shouldEnforcePrivateStoragePermissions
36
- } from "./server-e44zzy84.js";
38
+ shouldEnforcePrivateStoragePermissions,
39
+ ensureStorageDirectorySync,
40
+ writeStorageFileSync,
41
+ writeStorageFileAtomicSync,
42
+ copyStorageFileSync,
43
+ copyStorageTreeSync,
44
+ tightenStorageTreeSync
45
+ } from "./server-1bc61ans.js";
37
46
  import {
38
47
  logSlowWriteTransaction
39
- } from "./server-fgmxwrgy.js";
40
-
41
- // src/features/magic-context/migrations.ts
42
- import { isMainThread } from "node:worker_threads";
43
-
44
- // src/features/magic-context/memory/constants.ts
45
- var V2_MEMORY_CATEGORIES = [
46
- "PROJECT_RULES",
47
- "ARCHITECTURE",
48
- "CONSTRAINTS",
49
- "CONFIG_VALUES",
50
- "NAMING"
51
- ];
52
- var PROMOTABLE_CATEGORIES = [
53
- ...V2_MEMORY_CATEGORIES,
54
- "ARCHITECTURE_DECISIONS",
55
- "CONFIG_DEFAULTS",
56
- "USER_PREFERENCES",
57
- "USER_DIRECTIVES",
58
- "ENVIRONMENT",
59
- "WORKFLOW_RULES",
60
- "KNOWN_ISSUES"
61
- ];
62
- var CATEGORY_PRIORITY = [
63
- ...V2_MEMORY_CATEGORIES,
64
- "USER_DIRECTIVES",
65
- "USER_PREFERENCES",
66
- "CONFIG_DEFAULTS",
67
- "ARCHITECTURE_DECISIONS",
68
- "ENVIRONMENT",
69
- "WORKFLOW_RULES",
70
- "KNOWN_ISSUES"
71
- ];
72
- var MEMORY_CATEGORY_ORDER_UNKNOWN = 99;
73
- var MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
74
- acc[category] = index;
75
- return acc;
76
- }, {});
77
- var MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
78
- function getMemoryCategoryOrder(category) {
79
- return MEMORY_CATEGORY_ORDER_PRIORITY[category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
80
- }
81
- var CATEGORY_DEFAULT_TTL = {
82
- WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
83
- KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
84
- };
48
+ } from "./server-14vq4kpm.js";
85
49
 
86
- // src/shared/xml-unescape.ts
87
- var XML_ENTITY_REGEX = /&(amp|apos|quot|lt|gt);/g;
88
- var XML_ENTITIES = {
89
- amp: "&",
90
- apos: "'",
91
- quot: '"',
92
- lt: "<",
93
- gt: ">"
94
- };
95
- function unescapeXml(s) {
96
- return s.replace(XML_ENTITY_REGEX, (entity, name) => XML_ENTITIES[name] ?? entity);
50
+ // src/features/magic-context/fail-closed-block.ts
51
+ var FAIL_CLOSED_DOCTOR_COMMAND = "npx @cortexkit/magic-context@latest doctor";
52
+ function attachFailClosedBlockingProcessEvidence(process2, evidence) {
53
+ Object.defineProperties(process2, {
54
+ startTime: { configurable: true, value: evidence.startTime },
55
+ commandLine: { configurable: true, value: evidence.commandLine }
56
+ });
57
+ return process2;
97
58
  }
98
59
 
99
- // src/hooks/magic-context/compartment-parser.ts
100
- var COMPARTMENT_OPEN_REGEX = /<compartment\s+((?:[^>"]|"[^"\n]*")*?)\s*>/g;
101
- var COMPARTMENT_CLOSE_TAG = "</compartment>";
102
- var COMPARTMENT_CLOSE_FOLLOWER_REGEX = /^\s*(?:$|<(?:compartment\s|\/compartments\s*>|\/output\s*>|(?:facts|events|meta|user_observations|primer_candidates|unprocessed_from)\s*>))/;
103
- var ATTR_START_REGEX = /\bstart="(\d+)"/;
104
- var ATTR_END_REGEX = /\bend="(\d+)"/;
105
- var ATTR_TITLE_REGEX = /\btitle="([^"]*)"/;
106
- var ATTR_EPISODE_REGEX = /\bepisode_type="([^"]*)"/;
107
- var ATTR_IMPORTANCE_REGEX = /\bimportance="(\d+)"/;
108
- var TIER_OPEN_REGEX = /<p(\d)\s*(\/?)>/g;
109
- var TIER_CLOSE_REGEX = /<\/p(\d)\s*>/g;
110
- var TIER_CLOSE_FOLLOWER_REGEX = /^\s*(?:$|<\/?p\d\s*\/?>)/;
111
- var TIER_CLOSE_ANY_REGEX = /<\/p\d/;
112
- var TIER_OPEN_ANY_REGEX = /<p\d/;
113
- var CATEGORY_BLOCK_REGEX = /<([A-Za-z_][A-Za-z0-9_-]*)>(.*?)<\/\1>/gs;
114
- var HISTORIAN_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
115
- var FACT_ITEM_REGEX = /^\s*\*\s*(.+)$/gm;
116
- var UNPROCESSED_REGEX = /<unprocessed_from>(\d+)<\/unprocessed_from>/;
117
- var USER_OBSERVATIONS_REGEX = /<user_observations>(.*?)<\/user_observations>/s;
118
- var USER_OBS_ITEM_REGEX = /^\s*\*\s*(.+)$/gm;
119
- var PRIMER_CANDIDATES_REGEX = /<primer_candidates>(.*?)<\/primer_candidates>/s;
120
- var PRIMER_ELEMENT_REGEX = /<primer\s+at_compartment="(\d+)"\s*>(.*?)<\/primer>/gs;
121
- var PRIMER_ITEM_REGEX = /^\s*(?:\*|-|\d+\.)\s*(.+)$/gm;
122
- var FACTS_BLOCK_REGEX = /<facts>(.*?)<\/facts>/s;
123
- var EVENTS_BLOCK_REGEX = /<events>(.*?)<\/events>/s;
124
- var EVENT_ELEMENT_REGEX = /<([a-z_]+)\s+at_compartment="(\d+)"\s*>(.*?)<\/\1>/gs;
125
- var EVENT_FIELD_REGEX = /<([a-z_]+)\s*>(.*?)<\/\1>/gs;
126
- function searchFrom(s, regex, from) {
127
- const at = s.slice(from).search(regex);
128
- return at === -1 ? -1 : from + at;
129
- }
130
- function hasTierOpenerFrom(inner, digit, from) {
131
- return new RegExp(`<p${digit}\\s*/?>`).test(inner.slice(from));
132
- }
133
- function findMatchingTierClose(inner, bodyStart, digit, found) {
134
- const closeRegex = new RegExp(TIER_CLOSE_REGEX.source, "g");
135
- closeRegex.lastIndex = bodyStart;
136
- for (let close = closeRegex.exec(inner);close; close = closeRegex.exec(inner)) {
137
- const end = close.index + close[0].length;
138
- if (Number(close[1]) !== digit)
139
- continue;
140
- if (!TIER_CLOSE_FOLLOWER_REGEX.test(inner.slice(end)))
141
- continue;
142
- const body = inner.slice(bodyStart, close.index);
143
- for (const open of body.matchAll(new RegExp(TIER_OPEN_REGEX.source, "g"))) {
144
- const other = Number(open[1]);
145
- const otherwiseMissing = other >= 1 && other <= 4 && other !== digit && !found.has(other) && !hasTierOpenerFrom(inner, other, end);
146
- if (otherwiseMissing)
147
- return;
148
- }
149
- return { start: close.index, end };
60
+ class FailClosedBlockingError extends Error {
61
+ code = "FAIL_CLOSED_BLOCKING";
62
+ reason;
63
+ constructor(message, reason, options) {
64
+ super(message, options);
65
+ this.name = "FailClosedBlockingError";
66
+ this.reason = reason;
150
67
  }
151
- return;
152
68
  }
153
- function extractTiers(inner) {
154
- const tiers = new Map;
155
- const openRegex = new RegExp(TIER_OPEN_REGEX.source, "g");
156
- let pos = 0;
157
- for (;; ) {
158
- openRegex.lastIndex = pos;
159
- const open = openRegex.exec(inner);
160
- if (!open)
161
- break;
162
- const digit = Number(open[1]);
163
- const bodyStart = open.index + open[0].length;
164
- let bodyEnd = bodyStart;
165
- pos = bodyStart;
166
- if (open[2] !== "/") {
167
- const close = findMatchingTierClose(inner, bodyStart, digit, tiers);
168
- if (close) {
169
- bodyEnd = close.start;
170
- pos = close.end;
171
- } else {
172
- const closeAt = searchFrom(inner, TIER_CLOSE_ANY_REGEX, bodyStart);
173
- bodyEnd = closeAt === -1 ? inner.length : closeAt;
174
- pos = closeAt === -1 ? inner.length : closeAt + 1;
175
- const openInside = searchFrom(inner.slice(0, bodyEnd), TIER_OPEN_ANY_REGEX, bodyStart);
176
- if (openInside !== -1) {
177
- bodyEnd = openInside;
178
- pos = openInside;
179
- }
180
- }
181
- }
182
- if (!tiers.has(digit)) {
183
- tiers.set(digit, unescapeXml(inner.slice(bodyStart, bodyEnd).trim()));
184
- }
69
+ var OPENCODE_INTERNAL_AGENT_NAMES = new Set(["title", "summary", "compaction"]);
70
+ function isMagicContextHiddenAgentName(agent) {
71
+ if (agent === "smart-note-compiler" || agent.startsWith("smart-note-")) {
72
+ return true;
185
73
  }
186
- return tiers;
187
- }
188
- function extractTiersFromInner(inner) {
189
- const tiers = extractTiers(inner);
190
- return { p1: tiers.get(1), p2: tiers.get(2), p3: tiers.get(3), p4: tiers.get(4) };
74
+ if (agent === "historian" || agent.startsWith("historian-"))
75
+ return true;
76
+ if (agent === "dreamer" || agent.startsWith("dreamer-"))
77
+ return true;
78
+ return false;
191
79
  }
192
- function findCompartmentElements(text) {
193
- const elements = [];
194
- const openRegex = new RegExp(COMPARTMENT_OPEN_REGEX.source, "g");
195
- let cursor = 0;
196
- for (;; ) {
197
- openRegex.lastIndex = cursor;
198
- const open = openRegex.exec(text);
199
- if (!open)
200
- break;
201
- const bodyStart = open.index + open[0].length;
202
- const nextOpen = openRegex.exec(text);
203
- const windowEnd = nextOpen ? nextOpen.index : text.length;
204
- const firstClose = text.indexOf(COMPARTMENT_CLOSE_TAG, bodyStart);
205
- if (firstClose === -1)
206
- break;
207
- let closeAt = firstClose;
208
- for (let at = firstClose;at !== -1 && at < windowEnd; at = text.indexOf(COMPARTMENT_CLOSE_TAG, at + 1)) {
209
- if (COMPARTMENT_CLOSE_FOLLOWER_REGEX.test(text.slice(at + COMPARTMENT_CLOSE_TAG.length))) {
210
- closeAt = at;
211
- break;
212
- }
213
- }
214
- const end = closeAt + COMPARTMENT_CLOSE_TAG.length;
215
- elements.push({
216
- start: open.index,
217
- end,
218
- attrs: open[1],
219
- inner: text.slice(bodyStart, closeAt)
220
- });
221
- cursor = end;
80
+ var MAX_FORMATTED_BLOCKING_PROCESSES = 8;
81
+ var MAX_FORMATTED_COMMAND_LINE_LENGTH = 80;
82
+ function normalizeFailClosedProcessKind(process2) {
83
+ switch (process2.kind) {
84
+ case "OpenCode server":
85
+ case "OpenCode instance (TUI/CLI)":
86
+ case "Pi":
87
+ case "process":
88
+ return process2.kind;
89
+ }
90
+ switch (process2.harness?.trim().toLowerCase()) {
91
+ case "opencode server":
92
+ return "OpenCode server";
93
+ case "opencode instance (tui/cli)":
94
+ case "opencode instance":
95
+ return "OpenCode instance (TUI/CLI)";
96
+ case "pi":
97
+ case "pi harness":
98
+ case "omp":
99
+ return "Pi";
100
+ default:
101
+ return "process";
222
102
  }
223
- return elements;
224
103
  }
225
- function textOutsideCompartments(text, elements) {
226
- let outside = "";
227
- let last = 0;
228
- for (const element of elements) {
229
- outside += text.slice(last, element.start);
230
- last = element.end;
104
+ function formatFailClosedProcessStartTime(startTime) {
105
+ if (!Number.isFinite(startTime) || startTime <= 0)
106
+ return "unverified";
107
+ try {
108
+ const parts = new Intl.DateTimeFormat("en-US", {
109
+ day: "numeric",
110
+ hour: "2-digit",
111
+ minute: "2-digit",
112
+ month: "short",
113
+ hour12: false
114
+ }).formatToParts(new Date(startTime));
115
+ const values = Object.fromEntries(parts.map(({ type, value }) => [type, value]));
116
+ if (!values.month || !values.day || !values.hour || !values.minute)
117
+ return "unverified";
118
+ return `${values.month} ${values.day} ${values.hour}:${values.minute}`;
119
+ } catch {
120
+ return "unverified";
231
121
  }
232
- return outside + text.slice(last);
233
122
  }
234
- function parseCompartmentOutput(text) {
235
- const compartments = [];
236
- const facts = [];
237
- let droppedFactBlocks = 0;
238
- let droppedFacts = 0;
239
- const elements = findCompartmentElements(text);
240
- for (const { attrs, inner } of elements) {
241
- const startMatch = attrs.match(ATTR_START_REGEX);
242
- const endMatch = attrs.match(ATTR_END_REGEX);
243
- const titleMatch = attrs.match(ATTR_TITLE_REGEX);
244
- if (!startMatch || !endMatch || !titleMatch)
245
- continue;
246
- const startMessage = parseInt(startMatch[1], 10);
247
- const endMessage = parseInt(endMatch[1], 10);
248
- const title = unescapeXml(titleMatch[1]);
249
- if (Number.isNaN(startMessage) || Number.isNaN(endMessage) || !title)
250
- continue;
251
- const episodeMatch = attrs.match(ATTR_EPISODE_REGEX);
252
- const importanceMatch = attrs.match(ATTR_IMPORTANCE_REGEX);
253
- const episodeType = episodeMatch ? unescapeXml(episodeMatch[1]) : undefined;
254
- const importance = importanceMatch ? parseInt(importanceMatch[1], 10) : undefined;
255
- const { p1, p2, p3, p4 } = extractTiersFromInner(inner);
256
- if (typeof p1 === "string" && p1.length > 0) {
257
- compartments.push({
258
- startMessage,
259
- endMessage,
260
- title,
261
- content: p1,
262
- p1,
263
- p2: typeof p2 === "string" ? p2 : p1,
264
- p3: typeof p3 === "string" ? p3 : typeof p2 === "string" ? p2 : p1,
265
- p4: typeof p4 === "string" ? p4 : "",
266
- importance,
267
- episodeType
268
- });
269
- continue;
270
- }
271
- const content = unescapeXml(inner.trim());
272
- if (content) {
273
- compartments.push({
274
- startMessage,
275
- endMessage,
276
- title,
277
- content,
278
- importance,
279
- episodeType
280
- });
281
- }
123
+ function formatFailClosedCommandLine(commandLine) {
124
+ if (typeof commandLine !== "string" || commandLine.trim().length === 0)
125
+ return "unverified";
126
+ try {
127
+ const normalized = commandLine.replaceAll("\x00", " ").replace(/\s+/g, " ").trim();
128
+ if (normalized.length === 0)
129
+ return "unverified";
130
+ const sanitized = sanitizeDiagnosticText(normalized).trim();
131
+ if (sanitized.length === 0)
132
+ return "unverified";
133
+ return sanitized.length > MAX_FORMATTED_COMMAND_LINE_LENGTH ? `${sanitized.slice(0, MAX_FORMATTED_COMMAND_LINE_LENGTH - 3)}...` : sanitized;
134
+ } catch {
135
+ return "unverified";
282
136
  }
283
- const outside = textOutsideCompartments(text, elements);
284
- const factsBlockMatch = outside.match(FACTS_BLOCK_REGEX);
285
- const factsScope = factsBlockMatch ? factsBlockMatch[1] : outside.replace(EVENTS_BLOCK_REGEX, "").replace(/<(meta|user_observations|primer_candidates)>.*?<\/\1>/gs, "").replace(/<\/?(?:output|compartments)>/g, "");
286
- for (const categoryMatch of factsScope.matchAll(CATEGORY_BLOCK_REGEX)) {
287
- const category = categoryMatch[1];
288
- const blockContent = categoryMatch[2];
289
- const items = [...blockContent.matchAll(FACT_ITEM_REGEX)].map((match) => unescapeXml(match[1].trim())).filter(Boolean);
290
- if (!HISTORIAN_CATEGORIES.has(category)) {
291
- if (items.length === 0)
292
- continue;
293
- droppedFactBlocks++;
294
- droppedFacts += items.length;
295
- log(`[historian] Dropped <facts> category ${category} (${items.length} facts)`);
137
+ }
138
+ function formatFailClosedBlockingProcesses(processes) {
139
+ const uniqueProcesses = new Map;
140
+ for (const process2 of processes) {
141
+ if (!Number.isInteger(process2.pid) || process2.pid <= 0)
296
142
  continue;
143
+ const candidate = {
144
+ kind: normalizeFailClosedProcessKind(process2),
145
+ pid: process2.pid,
146
+ startTime: process2.startTime,
147
+ commandLine: process2.commandLine
148
+ };
149
+ const previous = uniqueProcesses.get(process2.pid);
150
+ if (!previous || previous.kind === "process" && candidate.kind !== "process") {
151
+ uniqueProcesses.set(process2.pid, candidate);
152
+ } else {
153
+ uniqueProcesses.set(process2.pid, {
154
+ ...previous,
155
+ startTime: previous.startTime ?? candidate.startTime,
156
+ commandLine: previous.commandLine ?? candidate.commandLine
157
+ });
297
158
  }
298
- for (const content of items)
299
- facts.push({ category, content });
300
159
  }
301
- const unprocessedMatch = outside.match(UNPROCESSED_REGEX);
302
- const unprocessedFrom = unprocessedMatch ? parseInt(unprocessedMatch[1], 10) : null;
303
- const userObservations = [];
304
- const userObsMatch = outside.match(USER_OBSERVATIONS_REGEX);
305
- if (userObsMatch) {
306
- for (const itemMatch of userObsMatch[1].matchAll(USER_OBS_ITEM_REGEX)) {
307
- const obs = unescapeXml(itemMatch[1].trim());
308
- if (obs)
309
- userObservations.push(obs);
160
+ const entries = [...uniqueProcesses.values()];
161
+ const visible = entries.slice(0, MAX_FORMATTED_BLOCKING_PROCESSES);
162
+ const rendered = visible.map(({ kind, pid }) => `${kind} (PID ${pid})`);
163
+ const omitted = entries.length - visible.length;
164
+ if (omitted > 0)
165
+ rendered.push(`${omitted} more blocking process(es)`);
166
+ if (rendered.length === 0)
167
+ return "a live process";
168
+ const summary = rendered.length === 1 ? rendered[0] : `${rendered.slice(0, -1).join(", ")}, and ${rendered.at(-1)}`;
169
+ const evidence = entries.map(({ kind, pid, startTime, commandLine }) => `- PID ${pid}: ${kind}, started ${formatFailClosedProcessStartTime(startTime)}, cmd: ${formatFailClosedCommandLine(commandLine)}`).join(`
170
+ `);
171
+ return `${summary}
172
+ Blocking process evidence:
173
+ ${evidence}`;
174
+ }
175
+ function formatFailClosedBlockingMessage(reason) {
176
+ if (reason.kind === "migration_guard") {
177
+ if (reason.unreadableFile) {
178
+ const arm = reason.unreadableArm ?? "io";
179
+ const recovery = arm === "io" ? `If none of these processes are running, it is safe to delete ${reason.unreadableFile} and retry.` : `The file may be a recent incomplete write; retry after the file is older than the ten-minute grace window, or stop the relevant process before deleting it.`;
180
+ return [
181
+ `Magic Context cannot migrate the shared database because RPC discovery file ${reason.unreadableFile} is uncertain (${arm} arm), so the absence of a live process cannot be proven.`,
182
+ recovery,
183
+ `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
184
+ ].join(" ");
310
185
  }
186
+ return [
187
+ `Magic Context cannot migrate the shared database (one database serves every project on this machine) because ${formatFailClosedBlockingProcesses(reason.blockingProcesses)} may be running an older Magic Context build that would fail against the migrated database.`,
188
+ `The database is at upstream migration v${reason.persistedVersion}; this build needs v${reason.supportedVersion}.`,
189
+ "Restart the blocking process — even one from a different project — and it will pick up the new build and migrate on start; or shut it down and retry.",
190
+ "If that host loads a pinned or cached older Magic Context build, a restart alone is not enough: stop it or update its Magic Context build, then start this host once so it migrates the database with nothing older attached.",
191
+ `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
192
+ ].join(" ");
311
193
  }
312
- const primerCandidates = [];
313
- const primerMatch = outside.match(PRIMER_CANDIDATES_REGEX);
314
- if (primerMatch) {
315
- const block = primerMatch[1];
316
- let sawElement = false;
317
- for (const el of block.matchAll(PRIMER_ELEMENT_REGEX)) {
318
- sawElement = true;
319
- const question = unescapeXml(el[2].trim());
320
- if (question) {
321
- primerCandidates.push({
322
- question,
323
- originCompartmentIndex: Number.parseInt(el[1], 10)
324
- });
325
- }
326
- }
327
- if (!sawElement) {
328
- for (const itemMatch of block.matchAll(PRIMER_ITEM_REGEX)) {
329
- const question = unescapeXml(itemMatch[1].trim());
330
- if (question)
331
- primerCandidates.push({ question });
332
- }
333
- }
194
+ if (reason.kind === "schema_fence") {
195
+ return [
196
+ `Magic Context cannot operate: this Magic Context build is older than the database; upgrade/restart this harness (upstream migration lane v${reason.persistedVersion}, build supports through v${reason.supportedVersion}).`,
197
+ `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
198
+ ].join(" ");
334
199
  }
335
- const events = parseEvents(outside);
336
- const sortedByStart = compartments.map((compartment, emitted) => ({ compartment, emitted })).sort((a, b) => a.compartment.startMessage - b.compartment.startMessage);
337
- const sortedPosition = [];
338
- sortedByStart.forEach(({ emitted }, sorted) => {
339
- sortedPosition[emitted] = sorted + 1;
340
- });
341
- const remapAnchor = (anchor) => anchor >= 1 && anchor <= sortedPosition.length ? sortedPosition[anchor - 1] : anchor;
342
- return {
343
- compartments: sortedByStart.map(({ compartment }) => compartment),
344
- facts,
345
- droppedFactBlocks,
346
- droppedFacts,
347
- events: events.map((event) => event.atCompartment === null ? event : { ...event, atCompartment: remapAnchor(event.atCompartment) }),
348
- unprocessedFrom,
349
- userObservations,
350
- primerCandidates: primerCandidates.map((candidate) => candidate.originCompartmentIndex === undefined ? candidate : {
351
- ...candidate,
352
- originCompartmentIndex: remapAnchor(candidate.originCompartmentIndex)
353
- })
354
- };
200
+ const cause = reason.cause.trim().length > 0 ? reason.cause.trim() : "unknown storage error";
201
+ return [
202
+ `Magic Context cannot operate: persistent storage failed (${cause}).`,
203
+ "The plugin will not silently degrade to native compaction while enabled.",
204
+ `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
205
+ ].join(" ");
355
206
  }
356
- function parseEvents(text) {
357
- const blockMatch = text.match(EVENTS_BLOCK_REGEX);
358
- if (!blockMatch)
359
- return [];
360
- const block = blockMatch[1];
361
- const events = [];
362
- for (const elMatch of block.matchAll(EVENT_ELEMENT_REGEX)) {
363
- const kind = elMatch[1];
364
- const atRaw = parseInt(elMatch[2], 10);
365
- const atCompartment = Number.isNaN(atRaw) ? null : atRaw;
366
- const fields = {};
367
- for (const fieldMatch of elMatch[3].matchAll(EVENT_FIELD_REGEX)) {
368
- const name = fieldMatch[1];
369
- const value = unescapeXml(fieldMatch[2].trim());
370
- if (value)
371
- fields[name] = value;
207
+ function formatFailClosedBlockingSummary(reason) {
208
+ if (reason.kind === "migration_guard") {
209
+ if (reason.unreadableFile) {
210
+ return `Magic Context cannot migrate the shared database from v${reason.persistedVersion} to v${reason.supportedVersion}: RPC discovery file ${reason.unreadableFile} could not be read, so an older host may still be attached.`;
372
211
  }
373
- events.push({ kind, atCompartment, fields });
212
+ const blockers = reason.blockingProcesses.filter((process2) => Number.isInteger(process2.pid) && process2.pid > 0).map((process2) => `${normalizeFailClosedProcessKind(process2)} (PID ${process2.pid})`);
213
+ const named = blockers.length > 0 ? blockers.join(", ") : "a live process";
214
+ return `Magic Context cannot migrate the shared database from v${reason.persistedVersion} to v${reason.supportedVersion} while ${named} may still run an older Magic Context build. Stop or update that host, then start this one once.`;
374
215
  }
375
- return events;
376
- }
377
-
378
- // src/features/magic-context/lkg-prefix-chunks.ts
379
- import { createHash } from "node:crypto";
380
- var LKG_PREFIX_CHUNK_CHARS = 64 * 1024;
381
- function splitLkgPrefix(jsonPrefix) {
382
- const chunks = [];
383
- let start = 0;
384
- while (start < jsonPrefix.length) {
385
- let end = Math.min(start + LKG_PREFIX_CHUNK_CHARS, jsonPrefix.length);
386
- if (end < jsonPrefix.length) {
387
- const last = jsonPrefix.charCodeAt(end - 1);
388
- if (last >= 55296 && last <= 56319)
389
- end -= 1;
390
- }
391
- chunks.push(jsonPrefix.slice(start, end));
392
- start = end;
216
+ if (reason.kind === "schema_fence") {
217
+ return `Magic Context cannot operate: the shared database is at v${reason.persistedVersion}, newer than this build supports (v${reason.supportedVersion}). Update and restart this host.`;
393
218
  }
394
- return chunks;
395
- }
396
- function hashLkgChunk(body) {
397
- return createHash("sha256").update(body).digest("hex");
219
+ const cause = reason.cause.trim().length > 0 ? reason.cause.trim() : "unknown storage error";
220
+ return `Magic Context cannot operate: persistent storage failed (${cause}).`;
398
221
  }
399
- function hashLkgChunkList(chunkHashes) {
400
- return createHash("sha256").update(chunkHashes.join(`
401
- `)).digest("hex");
222
+ function createFailClosedBlockingError(reason, options) {
223
+ return new FailClosedBlockingError(formatFailClosedBlockingMessage(reason), reason, options);
402
224
  }
403
- function layoutLkgPrefix(jsonPrefix) {
404
- const chunks = splitLkgPrefix(jsonPrefix);
405
- const chunkHashes = chunks.map(hashLkgChunk);
406
- return { chunks, chunkHashes, chars: jsonPrefix.length, hash: hashLkgChunkList(chunkHashes) };
225
+ function isFailClosedBlockingError(error) {
226
+ return error instanceof FailClosedBlockingError || typeof error === "object" && error !== null && error.name === "FailClosedBlockingError" && error.code === "FAIL_CLOSED_BLOCKING";
407
227
  }
408
-
409
- // src/features/magic-context/migration-v94-write-split.ts
410
- var LKG_SLOT_CHUNKS_DDL = `
411
- CREATE TABLE IF NOT EXISTS lkg_slot_chunks (
412
- session_id TEXT NOT NULL,
413
- chunk INTEGER NOT NULL,
414
- hash TEXT NOT NULL,
415
- body TEXT NOT NULL,
416
- PRIMARY KEY (session_id, chunk)
417
- );
418
- `;
419
- var SESSION_REPLAY_DECISIONS_DDL = `
420
- CREATE TABLE IF NOT EXISTS session_replay_decisions (
421
- session_id TEXT NOT NULL,
422
- message_id TEXT NOT NULL,
423
- decision TEXT NOT NULL,
424
- PRIMARY KEY (session_id, message_id)
425
- ) WITHOUT ROWID;
426
- `;
427
- function lkgSlotsDdl(table) {
428
- return `
429
- CREATE TABLE IF NOT EXISTS ${table} (
430
- session_id TEXT PRIMARY KEY,
431
- json_prefix_chars INTEGER NOT NULL,
432
- json_prefix_chunks INTEGER NOT NULL,
433
- json_prefix_hash TEXT NOT NULL,
434
- input_id_seq TEXT NOT NULL,
435
- input_content_digests TEXT NOT NULL,
436
- input_content_signatures TEXT,
437
- last_input_message_id TEXT NOT NULL,
438
- model_key TEXT,
439
- provider_key TEXT,
440
- captured_at INTEGER NOT NULL,
441
- row_version INTEGER,
442
- capture_sequence INTEGER
443
- );
444
- `;
228
+ function shouldBypassFailClosedBlock(input) {
229
+ if (input.isPiSubagentEnv === true)
230
+ return true;
231
+ if (input.isInternalChildSession === true)
232
+ return true;
233
+ const agent = typeof input.agent === "string" ? input.agent.trim() : "";
234
+ if (agent.length === 0)
235
+ return false;
236
+ if (OPENCODE_INTERNAL_AGENT_NAMES.has(agent))
237
+ return true;
238
+ if (isMagicContextHiddenAgentName(agent))
239
+ return true;
240
+ return false;
445
241
  }
446
- var LKG_SLOTS_DDL = lkgSlotsDdl("lkg_slots");
447
- var LKG_MOVE_WINDOW_MS = 24 * 60 * 60 * 1000;
448
- function tableExists(db, name) {
449
- return Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(name));
242
+ function resolveAgentNameFromMessages(messages) {
243
+ for (let i = messages.length - 1;i >= 0; i -= 1) {
244
+ const info = messages[i]?.info;
245
+ if (!info || typeof info !== "object")
246
+ continue;
247
+ const agent = info.agent;
248
+ if (typeof agent === "string" && agent.length > 0)
249
+ return agent;
250
+ }
251
+ return;
450
252
  }
451
- function columnExists(db, table, column) {
452
- const rows = db.prepare(`PRAGMA table_info(${table})`).all();
453
- return rows.some((row) => row.name === column);
253
+
254
+ // src/features/magic-context/memory/project-identity.ts
255
+ import { execFileSync } from "node:child_process";
256
+ import { createHash as createHash2 } from "node:crypto";
257
+ import { existsSync, realpathSync as realpathSync2, statSync } from "node:fs";
258
+ import { homedir } from "node:os";
259
+ import path3 from "node:path";
260
+
261
+ // src/features/magic-context/memory/project-identity-cache.ts
262
+ import { createHash } from "node:crypto";
263
+ import { readFileSync } from "node:fs";
264
+ import path2 from "node:path";
265
+
266
+ // src/shared/project-directory-key.ts
267
+ import { realpathSync } from "node:fs";
268
+ import path from "node:path";
269
+ function projectDirectoryKey(directory) {
270
+ const slashed = directory.replaceAll("\\", "/").replace(/^\/\/\?\/UNC\//i, "//").replace(/^\/\/\?\//, "");
271
+ if (/^[a-z]:\//i.test(slashed) || slashed.startsWith("//")) {
272
+ return path.win32.normalize(slashed).replaceAll("\\", "/").replace(/\/$/, "").toLowerCase();
273
+ }
274
+ return path.resolve(directory);
454
275
  }
455
- function splitLkgSlotPrefixes(db, now = Date.now()) {
456
- db.exec(LKG_SLOT_CHUNKS_DDL);
457
- if (!tableExists(db, "lkg_slots")) {
458
- db.exec(LKG_SLOTS_DDL);
459
- return;
276
+ var realpathForCanonicalDirectory = (directory) => realpathSync.native(directory);
277
+ function canonicalProjectDirectory(directory) {
278
+ let resolved = directory;
279
+ try {
280
+ resolved = realpathForCanonicalDirectory(directory);
281
+ } catch {}
282
+ return projectDirectoryKey(resolved);
283
+ }
284
+
285
+ // src/features/magic-context/memory/project-identity-cache.ts
286
+ function cachePath(directory, storageDir = getMagicContextStorageDir()) {
287
+ const hash = createHash("sha256").update(projectDirectoryKey(directory)).digest("hex");
288
+ return path2.join(storageDir, "project-identities", `${hash}.json`);
289
+ }
290
+ function rememberGitIdentity(directory, identity) {
291
+ try {
292
+ const destination = cachePath(directory);
293
+ writeStorageFileAtomicSync(destination, JSON.stringify({ directory: projectDirectoryKey(directory), identity }));
294
+ } catch {}
295
+ }
296
+ function readRememberedGitIdentity(directory, storageDir) {
297
+ try {
298
+ const record = JSON.parse(readFileSync(cachePath(directory, storageDir), "utf8"));
299
+ if (record.directory === projectDirectoryKey(directory) && /^git:[0-9a-f]{7,64}$/.test(record.identity))
300
+ return record.identity;
301
+ } catch {}
302
+ return;
303
+ }
304
+
305
+ // src/features/magic-context/memory/project-identity.ts
306
+ var GIT_TIMEOUT_MS = 5000;
307
+ var TRANSIENT_FAILURE_COOLDOWN_MS = 5 * 60 * 1000;
308
+ var identityCache = new Map;
309
+ var linkedGitWorktreeCache = new Map;
310
+ var lastKnownGitIdentityCache = new Map;
311
+ var directoryFallbackCache = new Map;
312
+ var transientFailureCooldown = new Map;
313
+ var dubiousOwnershipFallbackDirectories = new Set;
314
+ var dubiousOwnershipLoggedDirectories = new Set;
315
+ var dubiousOwnershipWarnedDirectories = new Set;
316
+ var transientGitIdentityReuseLoggedDirectories = new Set;
317
+ var sessionIdentityCache = new Map;
318
+ var homeProjectPermission = false;
319
+ var homeProjectSkipLogged = false;
320
+ var pausedIdentityReasons = new Map;
321
+ var pausedIdentityLogged = new Set;
322
+ function setHomeProjectPermission(allowed) {
323
+ homeProjectPermission = allowed;
324
+ }
325
+ function shouldSkipHomeProjectMemory(directory) {
326
+ try {
327
+ assertProjectAllowed(directory, homeProjectPermission);
328
+ return false;
329
+ } catch (error) {
330
+ if (!(error instanceof ProjectIdentityError) || error.errorClass !== "home_project_disabled")
331
+ throw error;
332
+ if (!homeProjectSkipLogged) {
333
+ homeProjectSkipLogged = true;
334
+ log("[magic-context] home project memory disabled; skipping memory features");
335
+ }
336
+ return true;
460
337
  }
461
- if (!columnExists(db, "lkg_slots", "json_prefix"))
462
- return;
463
- db.prepare("DELETE FROM lkg_slots WHERE NOT (captured_at >= ?)").run(now - LKG_MOVE_WINDOW_MS);
464
- db.exec("DROP TABLE IF EXISTS lkg_slots_v94");
465
- db.exec(lkgSlotsDdl("lkg_slots_v94"));
466
- const sessionIds = db.prepare("SELECT session_id FROM lkg_slots").all().map((row) => row.session_id);
467
- const readPrefix = db.prepare("SELECT json_prefix FROM lkg_slots WHERE session_id IS ?");
468
- const insertSlot = db.prepare(`INSERT OR IGNORE INTO lkg_slots_v94 (
469
- session_id, json_prefix_chars, json_prefix_chunks, json_prefix_hash,
470
- input_id_seq, input_content_digests, input_content_signatures,
471
- last_input_message_id, model_key, provider_key,
472
- captured_at, row_version, capture_sequence
473
- )
474
- SELECT session_id, ?, ?, ?,
475
- input_id_seq, input_content_digests, input_content_signatures,
476
- last_input_message_id, model_key, provider_key,
477
- captured_at, row_version, capture_sequence
478
- FROM lkg_slots WHERE session_id IS ?`);
479
- const insertChunk = db.prepare("INSERT INTO lkg_slot_chunks (session_id, chunk, hash, body) VALUES (?, ?, ?, ?)");
480
- db.prepare("DELETE FROM lkg_slot_chunks").run();
481
- for (const sessionId of sessionIds) {
482
- if (typeof sessionId !== "string")
483
- continue;
484
- const row = readPrefix.get(sessionId);
485
- if (typeof row?.json_prefix !== "string")
486
- continue;
487
- const layout = layoutLkgPrefix(row.json_prefix);
488
- const inserted = insertSlot.run(layout.chars, layout.chunks.length, layout.hash, sessionId);
489
- if (Number(inserted.changes) !== 1)
490
- continue;
491
- for (let index = 0;index < layout.chunks.length; index += 1) {
492
- insertChunk.run(sessionId, index, layout.chunkHashes[index], layout.chunks[index]);
338
+ }
339
+ var execFileSyncForIdentity = execFileSync;
340
+ var userHomeDirectoryForIdentity = () => homedir();
341
+ var nowMs = () => Date.now();
342
+ var filesystemProbeObserverForTests;
343
+
344
+ class ProjectIdentityError extends Error {
345
+ errorClass;
346
+ rawDirectory;
347
+ constructor(errorClass, rawDirectory, message, cause) {
348
+ super(message);
349
+ this.name = "ProjectIdentityError";
350
+ this.errorClass = errorClass;
351
+ this.rawDirectory = rawDirectory;
352
+ if (cause) {
353
+ this.cause = cause;
493
354
  }
494
355
  }
495
- db.exec("DROP TABLE lkg_slots");
496
- db.exec("ALTER TABLE lkg_slots_v94 RENAME TO lkg_slots");
497
356
  }
498
- function validDecisionSql(type, value) {
499
- return `(${type} = 'text' AND (
500
- ${value} IN ('keep', 'strip') OR (
501
- ${value} GLOB 'keep:[1-9]*'
502
- AND substr(${value}, 6) NOT GLOB '*[^0-9]*'
503
- AND length(${value}) <= 10
504
- AND CAST(substr(${value}, 6) AS INTEGER) BETWEEN 2 AND 10000
505
- )
506
- ))`;
357
+ function asError(error) {
358
+ return error instanceof Error ? error : undefined;
507
359
  }
508
- function splitReplayDecisions(db) {
509
- db.exec(SESSION_REPLAY_DECISIONS_DDL);
510
- if (!tableExists(db, "session_meta"))
360
+ function getErrorCode(error) {
361
+ if (error === null || typeof error !== "object" || !("code" in error)) {
511
362
  return;
512
- if (!columnExists(db, "session_meta", "trailing_blank_decisions"))
363
+ }
364
+ const code = error.code;
365
+ return typeof code === "string" ? code : undefined;
366
+ }
367
+ function getErrorSignal(error) {
368
+ if (error === null || typeof error !== "object" || !("signal" in error)) {
513
369
  return;
514
- const doc = "sm.trailing_blank_decisions";
515
- const hasVersionKey = `json_type(${doc}, '$.version') IS NOT NULL`;
516
- const versionIsDecision = validDecisionSql(`json_type(${doc}, '$.version')`, `json_extract(${doc}, '$.version')`);
517
- const isV2 = `(${hasVersionKey} AND NOT ${versionIsDecision})`;
518
- const v2Valid = `(
519
- json_type(${doc}, '$.version') IN ('integer', 'real')
520
- AND json_extract(${doc}, '$.version') = 2
521
- AND json_type(${doc}, '$.trailingBlank') = 'object'
522
- AND NOT EXISTS (
523
- SELECT 1 FROM json_each(${doc}, '$.trailingBlank') AS entry
524
- WHERE entry.key = '' OR NOT ${validDecisionSql("entry.type", "entry.value")}
525
- )
526
- )`;
527
- const v1Valid = `NOT EXISTS (
528
- SELECT 1 FROM json_each(${doc}) AS entry
529
- WHERE entry.key = '' OR NOT ${validDecisionSql("entry.type", "entry.value")}
530
- )`;
531
- db.exec("DROP TABLE IF EXISTS temp.v94_replay_documents");
532
- db.exec(`
533
- CREATE TEMP TABLE v94_replay_documents AS
534
- SELECT session_id, version FROM (
535
- SELECT sm.session_id AS session_id, CASE WHEN ${isV2} THEN 2 ELSE 1 END AS version,
536
- CASE WHEN ${isV2} THEN ${v2Valid} ELSE ${v1Valid} END AS strictly_valid
537
- FROM session_meta AS sm
538
- WHERE typeof(${doc}) = 'text' AND ${doc} <> ''
539
- AND json_valid(${doc}) AND json_type(${doc}) = 'object'
540
- ) WHERE strictly_valid;
541
- `);
542
- db.exec(`
543
- INSERT INTO session_replay_decisions (session_id, message_id, decision)
544
- SELECT sm.session_id, entry.key, entry.value
545
- FROM v94_replay_documents AS moved
546
- JOIN session_meta AS sm ON sm.session_id = moved.session_id
547
- JOIN json_each(
548
- ${doc}, CASE moved.version WHEN 2 THEN '$.trailingBlank' ELSE '$' END
549
- ) AS entry
550
- WHERE true
551
- ON CONFLICT(session_id, message_id) DO UPDATE SET decision = excluded.decision;
552
- `);
553
- db.exec(`
554
- UPDATE session_meta SET trailing_blank_decisions = (
555
- SELECT CASE moved.version
556
- WHEN 2 THEN json_set(session_meta.trailing_blank_decisions, '$.trailingBlank', json('{}'))
557
- ELSE ''
558
- END
559
- FROM v94_replay_documents AS moved WHERE moved.session_id = session_meta.session_id
560
- )
561
- WHERE session_id IN (SELECT session_id FROM v94_replay_documents);
562
- `);
563
- db.exec("DROP TABLE temp.v94_replay_documents");
370
+ }
371
+ const signal = error.signal;
372
+ return typeof signal === "string" ? signal : undefined;
564
373
  }
565
-
566
- // src/features/magic-context/opencode2-relabel.ts
567
- var OPENCODE2_RELABEL_STATE_KEY = "v87_opencode2_relabel";
568
- var V87_HARNESS_TWIN_RULES = [
569
- {
570
- table: "session_projects",
571
- keyColumns: ["session_id"],
572
- incomingIsNewer: "incoming.updated_at > existing.updated_at"
573
- },
574
- {
575
- table: "primer_candidates",
576
- keyColumns: [
577
- "project_path",
578
- "session_id",
579
- "source_start_message_id",
580
- "source_end_message_id"
581
- ],
582
- incomingIsNewer: "incoming.created_at > existing.created_at"
583
- },
584
- {
585
- table: "transform_decisions",
586
- keyColumns: ["session_id", "message_id"],
587
- incomingIsNewer: "incoming.ts_ms > existing.ts_ms"
374
+ function getErrorKilled(error) {
375
+ if (error === null || typeof error !== "object" || !("killed" in error)) {
376
+ return false;
588
377
  }
589
- ];
590
- function tableExists2(db, name) {
591
- return Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(name));
378
+ return error.killed === true;
592
379
  }
593
- function columnNames(db, table) {
594
- return new Set(db.prepare(`PRAGMA table_info(${table})`).all().map((column) => column.name));
380
+ function getErrorStderr(error) {
381
+ if (error === null || typeof error !== "object" || !("stderr" in error)) {
382
+ return "";
383
+ }
384
+ const stderr = error.stderr;
385
+ if (typeof stderr === "string") {
386
+ return stderr;
387
+ }
388
+ if (Buffer.isBuffer(stderr)) {
389
+ return stderr.toString("utf8");
390
+ }
391
+ return "";
595
392
  }
596
- function listSessionScopedHarnessTables(db, tables) {
597
- const shapes = [];
598
- for (const table of tables) {
599
- if (!tableExists2(db, table))
600
- continue;
601
- const columns = columnNames(db, table);
602
- if (!columns.has("harness"))
603
- continue;
604
- shapes.push({ table, hasSessionId: columns.has("session_id") });
393
+ function directoryFallback(directory) {
394
+ const canonical = path3.resolve(directory);
395
+ const hash = createHash2("md5").update(canonical, "utf8").digest("hex").slice(0, 12);
396
+ return `dir:${hash}`;
397
+ }
398
+ function rememberedIdentityOrDirectoryFallback(canonical) {
399
+ return readRememberedGitIdentity(canonical) ?? directoryFallback(canonical);
400
+ }
401
+ function assertDirectoryUsable(canonicalDirectory, rawDirectory) {
402
+ try {
403
+ const stat = statSync(canonicalDirectory);
404
+ if (!stat.isDirectory()) {
405
+ throw new ProjectIdentityError("unknown", rawDirectory, `Project path is not a directory: ${canonicalDirectory}`);
406
+ }
407
+ } catch (error) {
408
+ if (error instanceof ProjectIdentityError) {
409
+ throw error;
410
+ }
411
+ const code = getErrorCode(error);
412
+ if (code === "EACCES" || code === "EPERM") {
413
+ throw new ProjectIdentityError("permission_denied", rawDirectory, `Permission denied while accessing project directory: ${canonicalDirectory}`, asError(error));
414
+ }
415
+ throw new ProjectIdentityError("unknown", rawDirectory, `Unable to access project directory: ${canonicalDirectory}`, asError(error));
416
+ }
417
+ }
418
+ function isGitTimeoutError(error) {
419
+ const code = getErrorCode(error);
420
+ const signal = getErrorSignal(error);
421
+ return code === "ETIMEDOUT" || signal === "SIGTERM" || signal === "SIGKILL" || getErrorKilled(error);
422
+ }
423
+ function hasUnbornHead(directory) {
424
+ const options = {
425
+ cwd: directory,
426
+ encoding: "utf8",
427
+ env: { ...process.env, LC_ALL: "C", LANG: "C" },
428
+ stdio: ["ignore", "pipe", "pipe"],
429
+ timeout: GIT_TIMEOUT_MS,
430
+ windowsHide: true
431
+ };
432
+ try {
433
+ execFileSyncForIdentity("git", ["rev-parse", "--git-dir"], options);
434
+ try {
435
+ execFileSyncForIdentity("git", ["rev-parse", "--verify", "--quiet", "HEAD"], options);
436
+ return false;
437
+ } catch (error) {
438
+ return !isGitTimeoutError(error) && typeof error === "object" && error !== null && "status" in error && error.status === 1 && getErrorStderr(error) === "";
439
+ }
440
+ } catch {
441
+ return false;
605
442
  }
606
- return shapes;
607
443
  }
608
- function collectCandidateSessions(db, shapes) {
609
- const sessions = new Set;
610
- for (const shape of shapes) {
611
- if (!shape.hasSessionId)
612
- continue;
613
- const rows = db.prepare(`SELECT DISTINCT session_id AS id FROM ${shape.table}
614
- WHERE session_id IS NOT NULL AND harness IN ('opencode', 'opencode2')`).all();
615
- for (const row of rows)
616
- sessions.add(row.id);
444
+ function classifyGitError(error, rawDirectory) {
445
+ if (isGitTimeoutError(error)) {
446
+ return new ProjectIdentityError("git_timeout", rawDirectory, `git rev-list timed out after ${GIT_TIMEOUT_MS}ms`, asError(error));
617
447
  }
618
- return [...sessions].sort();
448
+ const code = getErrorCode(error);
449
+ if (code === "ENOENT") {
450
+ return new ProjectIdentityError("git_missing", rawDirectory, "git binary is not available in PATH", asError(error));
451
+ }
452
+ if (code === "EACCES" || code === "EPERM") {
453
+ return new ProjectIdentityError("permission_denied", rawDirectory, "Permission denied while spawning git", asError(error));
454
+ }
455
+ const stderr = getErrorStderr(error).toLowerCase();
456
+ if (stderr.includes("detected dubious ownership")) {
457
+ return new ProjectIdentityError("dubious_ownership", rawDirectory, "git refused to read the repository because it detected dubious ownership", asError(error));
458
+ }
459
+ if (stderr.includes("not a git repository") || stderr.includes("does not have any commits yet") || stderr.includes("ambiguous argument 'head'") || stderr.includes("unknown revision or path")) {
460
+ return new ProjectIdentityError("not_git_repo", rawDirectory, "Directory has no git root commit; caller may use directory fallback", asError(error));
461
+ }
462
+ return new ProjectIdentityError("unknown", rawDirectory, "git rev-list failed while resolving project identity", asError(error));
619
463
  }
620
- function openEvidenceStore(path) {
621
- const store = new Database(path, { readonly: true, fileMustExist: true });
464
+ function resolveProjectIdentityStrict(directory, allowHomeProject = homeProjectPermission) {
465
+ assertProjectAllowed(directory, allowHomeProject);
466
+ const canonical = path3.resolve(directory);
467
+ const cached = identityCache.get(canonical);
468
+ if (cached !== undefined) {
469
+ return cached;
470
+ }
471
+ assertDirectoryUsable(canonical, directory);
472
+ if (!hasGitDir(canonical)) {
473
+ throw new ProjectIdentityError("not_git_repo", directory, "Directory has no git metadata; caller may use directory fallback");
474
+ }
475
+ let output;
622
476
  try {
623
- const hasV1Messages = tableExists2(store, "message");
624
- const hasV1Session = tableExists2(store, "session");
625
- const hasV2 = tableExists2(store, "session_v2");
626
- const hasV2Messages = hasV2 && tableExists2(store, "session_message");
627
- if (!hasV1Messages && !hasV1Session && !hasV2) {
628
- store.close();
629
- return null;
630
- }
631
- const v1Message = hasV1Messages ? store.prepare("SELECT MAX(time_updated) AS newest FROM message WHERE session_id = ?") : null;
632
- const v1Session = hasV1Session ? store.prepare("SELECT time_updated AS newest FROM session WHERE id = ?") : null;
633
- const v2Message = hasV2Messages ? store.prepare("SELECT MAX(time_updated) AS newest FROM session_message WHERE session_id = ?") : null;
634
- const v2Session = hasV2 ? store.prepare("SELECT time_updated AS newest FROM session_v2 WHERE id = ?") : null;
635
- const newest = (statements, sessionId) => {
636
- let best = null;
637
- for (const statement of statements) {
638
- if (!statement)
639
- continue;
640
- const row = statement.get(sessionId);
641
- const value = row?.newest;
642
- if (typeof value !== "number")
643
- continue;
644
- if (best === null || value > best)
645
- best = value;
646
- }
647
- return best;
648
- };
649
- return {
650
- newestV1Activity: (sessionId) => newest([v1Message, v1Session], sessionId),
651
- newestV2Activity: (sessionId) => newest([v2Message, v2Session], sessionId),
652
- close: () => store.close()
653
- };
477
+ output = execFileSyncForIdentity("git", ["rev-list", "--max-parents=0", "HEAD"], {
478
+ cwd: canonical,
479
+ encoding: "utf8",
480
+ env: { ...process.env, LC_ALL: "C", LANG: "C" },
481
+ stdio: ["ignore", "pipe", "pipe"],
482
+ timeout: GIT_TIMEOUT_MS,
483
+ windowsHide: true
484
+ });
654
485
  } catch (error) {
655
- store.close();
656
- throw error;
486
+ const classified = classifyGitError(error, directory);
487
+ if (classified.errorClass === "not_git_repo" && hasUnbornHead(canonical)) {
488
+ throw new ProjectIdentityError("no_commits", directory, "Git repository has no commits yet");
489
+ }
490
+ throw classified;
491
+ }
492
+ const rootCommit = output.split(`
493
+ `).map((line) => line.trim().slice(0, 64)).filter((line) => /^[0-9a-f]{7,64}$/.test(line)).sort()[0];
494
+ if (!rootCommit) {
495
+ throw new ProjectIdentityError("unknown", directory, "git rev-list returned no valid root commit hash");
657
496
  }
497
+ const identity = `git:${rootCommit}`;
498
+ identityCache.set(canonical, identity);
499
+ lastKnownGitIdentityCache.set(canonical, identity);
500
+ rememberGitIdentity(canonical, identity);
501
+ const root = gitRootDirectory(canonical);
502
+ if (root && root !== canonical)
503
+ rememberGitIdentity(root, identity);
504
+ transientFailureCooldown.delete(canonical);
505
+ dubiousOwnershipFallbackDirectories.delete(canonical);
506
+ transientGitIdentityReuseLoggedDirectories.delete(canonical);
507
+ return identity;
658
508
  }
659
- function resolveHarnessFromEvidence(evidence, sessionId) {
660
- const v1 = evidence.newestV1Activity(sessionId);
661
- const v2 = evidence.newestV2Activity(sessionId);
662
- if (v1 === null && v2 === null)
663
- return null;
664
- if (v2 !== null && (v1 === null || v2 > v1))
665
- return "opencode2";
666
- if (v1 !== null && (v2 === null || v1 > v2))
667
- return "opencode";
668
- return null;
509
+ function shouldUseDirectoryFallback(error) {
510
+ return error.errorClass !== "home_project_disabled";
669
511
  }
670
- function prepareTwinResolver(db, table, keyColumns, incomingIsNewer) {
671
- const joinOn = keyColumns.map((column) => `incoming.${column} = existing.${column}`).join(" AND ");
672
- const dropIncoming = db.prepare(`DELETE FROM ${table} WHERE rowid IN (
673
- SELECT incoming.rowid FROM ${table} AS incoming
674
- JOIN ${table} AS existing ON ${joinOn} AND existing.harness = ?
675
- WHERE incoming.session_id = ? AND incoming.harness = ?
676
- AND NOT (${incomingIsNewer})
677
- )`);
678
- const dropExisting = db.prepare(`DELETE FROM ${table} WHERE rowid IN (
679
- SELECT existing.rowid FROM ${table} AS existing
680
- JOIN ${table} AS incoming ON ${joinOn} AND incoming.harness = ?
681
- WHERE existing.session_id = ? AND existing.harness = ?
682
- AND (${incomingIsNewer})
683
- )`);
684
- return {
685
- run(sessionId, target, source) {
686
- dropIncoming.run(target, sessionId, source);
687
- dropExisting.run(source, sessionId, target);
512
+ function getActiveCooldown(canonical) {
513
+ const until = transientFailureCooldown.get(canonical);
514
+ if (until === undefined)
515
+ return;
516
+ if (nowMs() < until)
517
+ return until;
518
+ transientFailureCooldown.delete(canonical);
519
+ return;
520
+ }
521
+ function lastKnownGitIdentity(canonical) {
522
+ return lastKnownGitIdentityCache.get(canonical) ?? identityCache.get(canonical) ?? readRememberedGitIdentity(canonical);
523
+ }
524
+ function nearestLastKnownGitIdentity(canonical) {
525
+ const visited = new Set;
526
+ const walk = (start) => {
527
+ let current = start;
528
+ while (!visited.has(current)) {
529
+ visited.add(current);
530
+ const cached = lastKnownGitIdentity(current);
531
+ if (cached !== undefined)
532
+ return { identity: cached, source: current };
533
+ if (existsSync(path3.join(current, ".git")))
534
+ break;
535
+ const parent = path3.dirname(current);
536
+ if (parent === current)
537
+ break;
538
+ current = parent;
688
539
  }
540
+ return;
689
541
  };
542
+ const exactOrAncestor = walk(canonical);
543
+ if (exactOrAncestor)
544
+ return exactOrAncestor;
545
+ try {
546
+ const realCanonical = realpathSync2.native(canonical);
547
+ if (realCanonical !== canonical)
548
+ return walk(realCanonical);
549
+ } catch {}
550
+ return;
690
551
  }
691
- function writeUnresolvedState(db, reason, resolution, sessionIds) {
692
- if (!tableExists2(db, "schema_migrations_meta"))
552
+ function reuseLastKnownGitIdentity(canonical) {
553
+ const cached = nearestLastKnownGitIdentity(canonical);
554
+ if (cached === undefined)
693
555
  return;
694
- const value = {
695
- reason,
696
- lookedFor: getOpenCodeDbProbeDescriptions(resolution).join(", "),
697
- sessionIds: [...sessionIds],
698
- recordedAt: Date.now()
699
- };
700
- db.prepare(`INSERT INTO schema_migrations_meta (key, value) VALUES (?, ?)
701
- ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(OPENCODE2_RELABEL_STATE_KEY, JSON.stringify(value));
556
+ if (!transientGitIdentityReuseLoggedDirectories.has(canonical)) {
557
+ transientGitIdentityReuseLoggedDirectories.add(canonical);
558
+ const sourceNote = cached.source === canonical ? "" : ` from ancestor ${cached.source}`;
559
+ log(`[magic-context] git identity resolution is temporarily unavailable for ${canonical}; reusing the last successful project identity${sourceNote} to avoid splitting project-scoped memory`);
560
+ }
561
+ return cached.identity;
702
562
  }
703
- function clearUnresolvedState(db) {
704
- if (!tableExists2(db, "schema_migrations_meta"))
563
+ function formatDubiousOwnershipWarning(canonical) {
564
+ return `Magic Context: git refused to read ${canonical} (dubious ownership — the repo is owned by a different user). Memory features are paused unless a previous git identity is available. Fix: git config --global --add safe.directory ${canonical}`;
565
+ }
566
+ function recordDubiousOwnershipFallback(canonical) {
567
+ dubiousOwnershipFallbackDirectories.add(canonical);
568
+ if (dubiousOwnershipLoggedDirectories.has(canonical))
705
569
  return;
706
- db.prepare("DELETE FROM schema_migrations_meta WHERE key = ?").run(OPENCODE2_RELABEL_STATE_KEY);
570
+ dubiousOwnershipLoggedDirectories.add(canonical);
571
+ log(`[magic-context] ${formatDubiousOwnershipWarning(canonical)}`);
707
572
  }
708
- function repairOpenCode2HarnessLabels(db, options) {
709
- const shapes = listSessionScopedHarnessTables(db, options.tables);
710
- const candidateSessionIds = collectCandidateSessions(db, shapes);
711
- if (candidateSessionIds.length === 0) {
712
- clearUnresolvedState(db);
713
- return {
714
- status: "no_candidates",
715
- storePath: null,
716
- candidateSessionIds,
717
- relabelledSessions: []
718
- };
573
+ function takeDubiousOwnershipProjectIdentityWarning(directory) {
574
+ const canonical = path3.resolve(directory);
575
+ if (!dubiousOwnershipFallbackDirectories.has(canonical))
576
+ return null;
577
+ if (dubiousOwnershipWarnedDirectories.has(canonical))
578
+ return null;
579
+ dubiousOwnershipWarnedDirectories.add(canonical);
580
+ return formatDubiousOwnershipWarning(canonical);
581
+ }
582
+ function canonicalUserHomeDirectory() {
583
+ const homeDirectory = userHomeDirectoryForIdentity();
584
+ try {
585
+ return realpathSync2.native(homeDirectory);
586
+ } catch {
587
+ return homeDirectory;
719
588
  }
720
- const resolution = resolveOpenCodeDbPath("v1", { env: options.env });
721
- let evidence = null;
722
- let reason = null;
723
- if (!openCodeDbPathExists(resolution)) {
724
- reason = "store_not_found";
725
- } else {
726
- try {
727
- evidence = openEvidenceStore(resolution.path);
728
- if (!evidence)
729
- reason = "store_schema_unknown";
730
- } catch (error) {
731
- reason = "store_unreadable";
732
- log(`[migration] OpenCode session database at ${resolution.path} could not be read: ${error instanceof Error ? error.message : String(error)}`);
589
+ }
590
+ function isUserHomeDirectory(directory) {
591
+ if (projectDirectoryKey(directory) === projectDirectoryKey(userHomeDirectoryForIdentity()))
592
+ return true;
593
+ try {
594
+ return projectDirectoryKey(realpathSync2.native(path3.resolve(directory))) === projectDirectoryKey(canonicalUserHomeDirectory());
595
+ } catch {
596
+ return false;
597
+ }
598
+ }
599
+ function assertProjectAllowed(directory, allowHomeProject) {
600
+ const canonical = path3.resolve(directory);
601
+ if (!allowHomeProject && (isUserHomeDirectory(directory) || isUserHomeDirectory(gitRootDirectory(canonical) ?? canonical))) {
602
+ throw new ProjectIdentityError("home_project_disabled", directory, "Home project memory is disabled; set allow_home_project to opt in");
603
+ }
604
+ }
605
+ function resolveProjectIdentity(directory, allowHomeProject = homeProjectPermission) {
606
+ assertProjectAllowed(directory, allowHomeProject);
607
+ const canonical = path3.resolve(directory);
608
+ const cachedFallback = directoryFallbackCache.get(canonical);
609
+ if (cachedFallback !== undefined) {
610
+ if (!hasGitDir(canonical)) {
611
+ return cachedFallback;
733
612
  }
613
+ directoryFallbackCache.delete(canonical);
734
614
  }
735
- if (!evidence || reason !== null) {
736
- evidence?.close();
737
- writeUnresolvedState(db, reason ?? "store_not_found", resolution, candidateSessionIds);
738
- log(`[migration] OpenCode harness labels left unverified for ${candidateSessionIds.length} session(s): ` + `no readable OpenCode session database (${reason ?? "store_not_found"}) at ${resolution.path}. ` + "Nothing was relabelled; `magic-context doctor` reports the affected sessions.");
739
- return {
740
- status: "unresolved",
741
- reason: reason ?? "store_not_found",
742
- storePath: null,
743
- candidateSessionIds,
744
- relabelledSessions: []
745
- };
615
+ if (getActiveCooldown(canonical) !== undefined) {
616
+ if (hasGitDir(canonical)) {
617
+ const cachedGitIdentity = reuseLastKnownGitIdentity(canonical);
618
+ if (cachedGitIdentity !== undefined) {
619
+ return cachedGitIdentity;
620
+ }
621
+ throw new ProjectIdentityError("git_identity_unavailable", directory, "Git identity unavailable; memory features paused until the retry cooldown expires");
622
+ }
623
+ return rememberedIdentityOrDirectoryFallback(canonical);
746
624
  }
747
- const relabelledSessions = [];
748
625
  try {
749
- const updates = shapes.filter((shape) => shape.hasSessionId).map((shape) => db.prepare(`UPDATE ${shape.table} SET harness = ? WHERE session_id = ? AND harness = ?`));
750
- const presentTables = new Set(shapes.map((shape) => shape.table));
751
- const twinResolvers = V87_HARNESS_TWIN_RULES.filter((rule) => presentTables.has(rule.table)).map((rule) => prepareTwinResolver(db, rule.table, rule.keyColumns, rule.incomingIsNewer));
752
- for (const sessionId of candidateSessionIds) {
753
- const target = resolveHarnessFromEvidence(evidence, sessionId);
754
- if (target === null)
755
- continue;
756
- const source = target === "opencode" ? "opencode2" : "opencode";
757
- for (const resolver of twinResolvers)
758
- resolver.run(sessionId, target, source);
759
- let moved = 0;
760
- for (const update of updates) {
761
- moved += Number(update.run(target, sessionId, source).changes ?? 0);
626
+ return resolveProjectIdentityStrict(directory, allowHomeProject);
627
+ } catch (error) {
628
+ if (error instanceof ProjectIdentityError && shouldUseDirectoryFallback(error)) {
629
+ if (error.errorClass === "no_commits")
630
+ return directoryFallback(canonical);
631
+ if (!hasGitDir(canonical)) {
632
+ if (error.errorClass === "permission_denied")
633
+ throw error;
634
+ const resolved = rememberedIdentityOrDirectoryFallback(canonical);
635
+ directoryFallbackCache.set(canonical, resolved);
636
+ transientFailureCooldown.delete(canonical);
637
+ return resolved;
762
638
  }
763
- if (moved > 0)
764
- relabelledSessions.push({ sessionId, harness: target });
639
+ transientFailureCooldown.set(canonical, nowMs() + TRANSIENT_FAILURE_COOLDOWN_MS);
640
+ const cachedGitIdentity = reuseLastKnownGitIdentity(canonical);
641
+ if (error.errorClass === "dubious_ownership")
642
+ recordDubiousOwnershipFallback(canonical);
643
+ if (cachedGitIdentity !== undefined) {
644
+ return cachedGitIdentity;
645
+ }
646
+ throw error;
765
647
  }
766
- } finally {
767
- evidence.close();
648
+ throw error;
768
649
  }
769
- clearUnresolvedState(db);
770
- if (relabelledSessions.length > 0) {
771
- const toOpenCode2 = relabelledSessions.filter((entry) => entry.harness === "opencode2").length;
772
- log(`[migration] OpenCode harness labels repaired from ${resolution.path}: ` + `${toOpenCode2} session(s) restored to opencode2, ` + `${relabelledSessions.length - toOpenCode2} session(s) set to opencode.`);
650
+ }
651
+ function resolveProjectIdentityOrFallback(directory, allowHomeProject = homeProjectPermission) {
652
+ try {
653
+ return resolveProjectIdentity(directory, allowHomeProject);
654
+ } catch (error) {
655
+ const canonical = path3.resolve(directory);
656
+ if (hasGitDir(canonical) || error instanceof ProjectIdentityError && error.errorClass === "home_project_disabled")
657
+ throw error;
658
+ const fallback = rememberedIdentityOrDirectoryFallback(canonical);
659
+ const message = error instanceof Error ? error.message : String(error);
660
+ log(`[magic-context] project identity resolution failed for ${canonical}; using directory fallback ${fallback}: ${message}`);
661
+ return fallback;
773
662
  }
774
- return {
775
- status: "resolved",
776
- storePath: resolution.path,
777
- candidateSessionIds,
778
- relabelledSessions
779
- };
780
663
  }
781
-
782
- // src/features/magic-context/storage-compartment-history-version.ts
783
- function definitions(db) {
784
- const hasPrivilegeTable = Boolean(db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='context_privilege_state'").get());
785
- const externalUpdate = hasPrivilegeTable ? "CASE WHEN COALESCE((SELECT enabled FROM context_privilege_state WHERE id=1),0)=0 THEN 1 ELSE 0 END" : "1";
786
- return [
787
- {
788
- type: "table",
789
- name: "compartment_history_versions",
790
- sql: `CREATE TABLE compartment_history_versions (
791
- session_id TEXT PRIMARY KEY NOT NULL,
792
- generation TEXT NOT NULL DEFAULT (lower(hex(randomblob(16)))),
793
- version INTEGER NOT NULL DEFAULT 0 CHECK (version >= 0),
794
- rewrite_version INTEGER NOT NULL DEFAULT 0 CHECK (rewrite_version >= 0),
795
- seeded INTEGER NOT NULL DEFAULT 0 CHECK (seeded IN (0, 1))
796
- )`
797
- },
798
- {
799
- type: "trigger",
800
- name: "compartment_history_ai",
801
- sql: `CREATE TRIGGER compartment_history_ai AFTER INSERT ON compartments BEGIN
802
- INSERT INTO compartment_history_versions(session_id, version) VALUES (NEW.session_id, 1)
803
- ON CONFLICT(session_id) DO UPDATE SET version = version + 1;
804
- END`
805
- },
806
- {
807
- type: "trigger",
808
- name: "compartment_history_ad",
809
- sql: `CREATE TRIGGER compartment_history_ad AFTER DELETE ON compartments BEGIN
810
- INSERT INTO compartment_history_versions(session_id, version) VALUES (OLD.session_id, 1)
811
- ON CONFLICT(session_id) DO UPDATE SET version = version + 1;
812
- END`
813
- },
814
- {
815
- type: "trigger",
816
- name: "compartment_history_au",
817
- sql: `CREATE TRIGGER compartment_history_au AFTER UPDATE ON compartments BEGIN
818
- INSERT INTO compartment_history_versions(session_id, version, rewrite_version) VALUES (OLD.session_id, 1, ${externalUpdate})
819
- ON CONFLICT(session_id) DO UPDATE SET version = version + 1, rewrite_version = rewrite_version + ${externalUpdate};
820
- INSERT INTO compartment_history_versions(session_id, version, rewrite_version)
821
- SELECT NEW.session_id, 1, ${externalUpdate} WHERE NEW.session_id != OLD.session_id
822
- ON CONFLICT(session_id) DO UPDATE SET version = version + 1, rewrite_version = rewrite_version + ${externalUpdate};
823
- END`
664
+ function hasGitDir(canonical) {
665
+ if (hasGitDirInAncestorChain(canonical)) {
666
+ return true;
667
+ }
668
+ try {
669
+ const realCanonical = realpathSync2.native(canonical);
670
+ return realCanonical !== canonical && hasGitDirInAncestorChain(realCanonical);
671
+ } catch {
672
+ return false;
673
+ }
674
+ }
675
+ function gitRootInAncestorChain(startDirectory) {
676
+ let current = startDirectory;
677
+ while (true) {
678
+ if (existsSync(path3.join(current, ".git"))) {
679
+ try {
680
+ return realpathSync2.native(current);
681
+ } catch {
682
+ return path3.resolve(current);
683
+ }
824
684
  }
825
- ];
685
+ const parent = path3.dirname(current);
686
+ if (parent === current) {
687
+ return null;
688
+ }
689
+ current = parent;
690
+ }
826
691
  }
827
- function installedDefinitions(db) {
828
- const rows = db.prepare(`SELECT type, name, sql FROM sqlite_master
829
- WHERE name IN ('compartment_history_versions', 'compartment_history_ai', 'compartment_history_ad', 'compartment_history_au')`).all();
830
- return new Map(rows.map((row) => [`${row.type}:${row.name}`, row.sql]));
692
+ function hasGitDirInAncestorChain(startDirectory) {
693
+ return gitRootInAncestorChain(startDirectory) !== null;
831
694
  }
832
- function matches(expected, installed) {
833
- return expected.every((definition) => installed.get(`${definition.type}:${definition.name}`) === definition.sql);
695
+ function gitRootDirectory(canonical) {
696
+ const direct = gitRootInAncestorChain(canonical);
697
+ if (direct)
698
+ return direct;
699
+ try {
700
+ const realCanonical = realpathSync2.native(canonical);
701
+ return realCanonical === canonical ? null : gitRootInAncestorChain(realCanonical);
702
+ } catch {
703
+ return null;
704
+ }
834
705
  }
835
- function installCompartmentHistoryVersions(db) {
836
- if (!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='compartments'").get())
837
- return;
838
- if (matches(definitions(db), installedDefinitions(db)))
839
- return;
840
- db.transaction(() => {
841
- const expected = definitions(db);
842
- const installed = installedDefinitions(db);
843
- if (matches(expected, installed))
844
- return;
845
- const table = expected[0];
846
- const existingTable = installed.get(`table:${table.name}`);
847
- if (existingTable !== undefined && existingTable !== table.sql) {
848
- throw new Error("compartment_history_versions schema differs from v93; refusing to replace revision data");
706
+ function directoryHasGitMetadata(directory) {
707
+ return gitRootDirectory(path3.resolve(directory)) !== null;
708
+ }
709
+ function describeUnresolvedProjectIdentity(directory) {
710
+ const resolvedDirectory = path3.resolve(directory);
711
+ const canonicalHome = canonicalUserHomeDirectory();
712
+ const canonicalDirectory = (() => {
713
+ try {
714
+ return realpathSync2.native(resolvedDirectory);
715
+ } catch {
716
+ return resolvedDirectory;
849
717
  }
850
- if (existingTable === undefined)
851
- db.exec(table.sql);
852
- db.exec(`INSERT OR IGNORE INTO compartment_history_versions(session_id, seeded)
853
- SELECT DISTINCT session_id, 1 FROM compartments`);
854
- for (const trigger of expected.slice(1)) {
855
- if (installed.get(`trigger:${trigger.name}`) === trigger.sql)
856
- continue;
857
- db.exec(`DROP TRIGGER IF EXISTS ${trigger.name}`);
858
- db.exec(trigger.sql);
718
+ })();
719
+ if (isUserHomeDirectory(directory) || gitRootDirectory(canonicalDirectory) === canonicalHome) {
720
+ return `this session runs in your home directory (${canonicalHome}), which Magic Context does not treat as a project. ` + "Start OpenCode inside a project folder, or set `allow_home_project: true` in the user-level " + "magic-context.jsonc (~/.config/cortexkit/) to give home sessions their own memory.";
721
+ }
722
+ if (hasGitDir(resolvedDirectory))
723
+ return `git identity resolution for ${resolvedDirectory} is temporarily unavailable. Memory features are paused; retry after git access recovers.`;
724
+ return `the session directory ${resolvedDirectory} could not be read as a project.`;
725
+ }
726
+ function isUsableProjectIdentity(identity) {
727
+ return typeof identity === "string" && identity.trim().length > 0;
728
+ }
729
+ function resolveProjectIdentityForSession(directory, allowHomeProject = homeProjectPermission) {
730
+ const resolvedDirectory = path3.resolve(directory);
731
+ const cacheKey = `${allowHomeProject ? "1" : "0"}\x00${resolvedDirectory}`;
732
+ const cached = sessionIdentityCache.get(cacheKey);
733
+ if (cached && (cached.revalidateAt === null || nowMs() < cached.revalidateAt) && !(cached.identity?.startsWith("dir:") && hasGitDir(resolvedDirectory))) {
734
+ return cached.identity;
735
+ }
736
+ sessionIdentityCache.delete(cacheKey);
737
+ filesystemProbeObserverForTests?.();
738
+ const canonicalDirectory = (() => {
739
+ try {
740
+ filesystemProbeObserverForTests?.();
741
+ return realpathSync2.native(resolvedDirectory);
742
+ } catch {
743
+ return resolvedDirectory;
744
+ }
745
+ })();
746
+ filesystemProbeObserverForTests?.();
747
+ const homeRoot = gitRootDirectory(canonicalDirectory);
748
+ const inheritsHomeRepository = homeRoot !== null && isUserHomeDirectory(homeRoot);
749
+ let identity;
750
+ if (!allowHomeProject && (isUserHomeDirectory(directory) || inheritsHomeRepository)) {
751
+ identity = undefined;
752
+ } else {
753
+ try {
754
+ identity = resolveProjectIdentityOrFallback(isUserHomeDirectory(directory) ? canonicalUserHomeDirectory() : directory, allowHomeProject);
755
+ } catch (error) {
756
+ if (!(error instanceof ProjectIdentityError))
757
+ throw error;
758
+ const reason = error.errorClass === "git_identity_unavailable" ? pausedIdentityReasons.get(resolvedDirectory) ?? error.errorClass : error.errorClass;
759
+ pausedIdentityReasons.set(resolvedDirectory, reason);
760
+ const logKey = `${resolvedDirectory}\x00${reason}`;
761
+ if (!pausedIdentityLogged.has(logKey)) {
762
+ pausedIdentityLogged.add(logKey);
763
+ log(`[magic-context] memory features paused for ${resolvedDirectory}: ${reason}`);
764
+ }
765
+ identity = undefined;
859
766
  }
860
- }).immediate();
861
- }
862
-
863
- // src/features/magic-context/storage-schema-helpers.ts
864
- function ensureColumn(db, table, column, definition) {
865
- if (!/^[a-z][a-z0-9_]*$/.test(table) || !/^[a-z][a-z0-9_]*$/.test(column) || !/^[A-Z0-9_"'(),[\]\s]+$/i.test(definition)) {
866
- throw new Error(`Unsafe schema identifier: ${table}.${column} ${definition}`);
867
767
  }
868
- const rows = db.prepare(`PRAGMA table_info(${table})`).all();
869
- if (rows.some((row) => row.name === column)) {
870
- return;
768
+ if (identity !== undefined)
769
+ pausedIdentityReasons.delete(resolvedDirectory);
770
+ sessionIdentityCache.set(cacheKey, {
771
+ identity,
772
+ revalidateAt: identity?.startsWith("git:") === true ? null : nowMs() + TRANSIENT_FAILURE_COOLDOWN_MS
773
+ });
774
+ return identity;
775
+ }
776
+ function normalizeStoredProjectPath(rawOrStored) {
777
+ if (rawOrStored.startsWith("git:") || rawOrStored.startsWith("dir:")) {
778
+ return rawOrStored;
871
779
  }
872
780
  try {
873
- db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
874
- } catch (err) {
875
- const recheck = db.prepare(`PRAGMA table_info(${table})`).all();
876
- if (recheck.some((row) => row.name === column)) {
877
- return;
878
- }
879
- throw err;
880
- }
881
- }
882
- function healAllNullColumns(db) {
883
- const existingColumns = getSessionMetaColumns(db);
884
- const fallbacks = [
885
- ["cache_ttl", ""],
886
- ["last_nudge_band", ""],
887
- ["last_nudge_level", ""],
888
- ["channel2_nudge_claim_token", ""],
889
- ["last_transform_error", ""],
890
- ["nudge_anchor_message_id", ""],
891
- ["nudge_anchor_text", ""],
892
- ["sticky_turn_reminder_text", ""],
893
- ["sticky_turn_reminder_message_id", ""],
894
- ["note_nudge_trigger_message_id", ""],
895
- ["note_nudge_sticky_text", ""],
896
- ["note_nudge_sticky_message_id", ""],
897
- ["last_todo_state", ""],
898
- ["todo_synthetic_call_id", ""],
899
- ["todo_synthetic_anchor_message_id", ""],
900
- ["todo_synthetic_state_json", ""],
901
- ["system_prompt_hash", ""],
902
- ["stripped_placeholder_ids", ""],
903
- ["stale_reduce_stripped_ids", ""],
904
- ["processed_image_stripped_ids", ""],
905
- ["merged_reasoning_stripped_ids", ""],
906
- ["thinking_binding_recovery_target", ""],
907
- ["trailing_blank_decisions", ""],
908
- ["memory_block_cache", ""],
909
- ["memory_block_ids", ""],
910
- ["compaction_marker_state", ""],
911
- ["key_files", ""],
912
- ["times_execute_threshold_reached", 0],
913
- ["compartment_in_progress", 0],
914
- ["historian_failure_count", 0],
915
- ["cleared_reasoning_through_tag", 0],
916
- ["memory_block_count", 0],
917
- ["system_prompt_tokens", 0],
918
- ["conversation_tokens", 0],
919
- ["tool_call_tokens", 0],
920
- ["note_nudge_trigger_pending", 0],
921
- ["observed_safe_input_tokens", 0],
922
- ["cache_alert_sent", 0],
923
- ["new_work_tokens", 0],
924
- ["total_input_tokens", 0],
925
- ["last_emergency_input_sample", 0],
926
- ["channel2_nudge_claimed_at", 0],
927
- ["last_usage_context_limit", 0],
928
- ["prior_boundary_ordinal", 1],
929
- ["protected_tail_policy_version", 0],
930
- ["protected_tail_drain_window_started_at", 0],
931
- ["protected_tail_drain_tokens", 0],
932
- ["recovery_no_eligible_head_count", 0],
933
- ["force_emergency_bypass_window_start", 0],
934
- ["force_emergency_bypass_used", 0],
935
- ["emergency_drain_active", 0],
936
- ["historian_drain_failure_at", 0]
937
- ];
938
- const presentFallbacks = fallbacks.filter(([column]) => existingColumns.has(column));
939
- if (presentFallbacks.length > 0) {
940
- const assignments = presentFallbacks.map(([column]) => `${column} = COALESCE(${column}, ?)`).join(", ");
941
- const nullPredicate = presentFallbacks.map(([column]) => `${column} IS NULL`).join(" OR ");
942
- db.prepare(`UPDATE session_meta SET ${assignments} WHERE ${nullPredicate}`).run(...presentFallbacks.map(([, fallback]) => fallback));
781
+ return resolveProjectIdentity(rawOrStored);
782
+ } catch {
783
+ return rawOrStored;
943
784
  }
944
- healMissingMemoryBlockIds(db, existingColumns);
945
785
  }
946
- function getSessionMetaColumns(db) {
947
- const rows = db.prepare("PRAGMA table_info(session_meta)").all();
948
- return new Set(rows.flatMap((row) => typeof row.name === "string" ? [row.name] : []));
949
- }
950
- function healMissingMemoryBlockIds(db, columns) {
951
- if (!columns.has("memory_block_cache") || !columns.has("memory_block_ids") || !columns.has("memory_block_count")) {
952
- return;
953
- }
954
- db.prepare("UPDATE session_meta SET memory_block_cache = '' WHERE memory_block_cache != '' AND (memory_block_ids IS NULL OR memory_block_ids = '') AND memory_block_count > 0").run();
786
+ function storedPathBelongsToIdentity(storedProjectPath, projectIdentity) {
787
+ return storedProjectPath === projectIdentity || normalizeStoredProjectPath(storedProjectPath) === projectIdentity;
955
788
  }
956
789
 
957
- // src/features/magic-context/workspaces.ts
958
- import { createHash as createHash4 } from "node:crypto";
959
-
960
- // src/features/magic-context/memory/project-identity.ts
961
- import { execFileSync } from "node:child_process";
962
- import { createHash as createHash3 } from "node:crypto";
963
- import { existsSync, realpathSync as realpathSync2, statSync } from "node:fs";
964
- import { homedir } from "node:os";
965
- import path3 from "node:path";
966
-
967
- // src/features/magic-context/memory/project-identity-cache.ts
968
- import { createHash as createHash2, randomUUID } from "node:crypto";
969
- import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
970
- import path2 from "node:path";
790
+ // src/features/magic-context/migrations.ts
791
+ import { isMainThread } from "node:worker_threads";
971
792
 
972
- // src/shared/project-directory-key.ts
973
- import { realpathSync } from "node:fs";
974
- import path from "node:path";
975
- function projectDirectoryKey(directory) {
976
- const slashed = directory.replaceAll("\\", "/").replace(/^\/\/\?\/UNC\//i, "//").replace(/^\/\/\?\//, "");
977
- if (/^[a-z]:\//i.test(slashed) || slashed.startsWith("//")) {
978
- return path.win32.normalize(slashed).replaceAll("\\", "/").replace(/\/$/, "").toLowerCase();
979
- }
980
- return path.resolve(directory);
981
- }
982
- var realpathForCanonicalDirectory = (directory) => realpathSync.native(directory);
983
- function canonicalProjectDirectory(directory) {
984
- let resolved = directory;
985
- try {
986
- resolved = realpathForCanonicalDirectory(directory);
987
- } catch {}
988
- return projectDirectoryKey(resolved);
793
+ // src/features/magic-context/memory/constants.ts
794
+ var V2_MEMORY_CATEGORIES = [
795
+ "PROJECT_RULES",
796
+ "ARCHITECTURE",
797
+ "CONSTRAINTS",
798
+ "CONFIG_VALUES",
799
+ "NAMING"
800
+ ];
801
+ var PROMOTABLE_CATEGORIES = [
802
+ ...V2_MEMORY_CATEGORIES,
803
+ "ARCHITECTURE_DECISIONS",
804
+ "CONFIG_DEFAULTS",
805
+ "USER_PREFERENCES",
806
+ "USER_DIRECTIVES",
807
+ "ENVIRONMENT",
808
+ "WORKFLOW_RULES",
809
+ "KNOWN_ISSUES"
810
+ ];
811
+ var CATEGORY_PRIORITY = [
812
+ ...V2_MEMORY_CATEGORIES,
813
+ "USER_DIRECTIVES",
814
+ "USER_PREFERENCES",
815
+ "CONFIG_DEFAULTS",
816
+ "ARCHITECTURE_DECISIONS",
817
+ "ENVIRONMENT",
818
+ "WORKFLOW_RULES",
819
+ "KNOWN_ISSUES"
820
+ ];
821
+ var MEMORY_CATEGORY_ORDER_UNKNOWN = 99;
822
+ var MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
823
+ acc[category] = index;
824
+ return acc;
825
+ }, {});
826
+ var MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
827
+ function getMemoryCategoryOrder(category) {
828
+ return MEMORY_CATEGORY_ORDER_PRIORITY[category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
989
829
  }
830
+ var CATEGORY_DEFAULT_TTL = {
831
+ WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
832
+ KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
833
+ };
990
834
 
991
- // src/features/magic-context/memory/project-identity-cache.ts
992
- function cachePath(directory, storageDir = getMagicContextStorageDir()) {
993
- const hash = createHash2("sha256").update(projectDirectoryKey(directory)).digest("hex");
994
- return path2.join(storageDir, "project-identities", `${hash}.json`);
995
- }
996
- function rememberGitIdentity(directory, identity) {
997
- try {
998
- const destination = cachePath(directory);
999
- mkdirSync(path2.dirname(destination), { recursive: true });
1000
- const temporary = `${destination}.${randomUUID()}.tmp`;
1001
- writeFileSync(temporary, JSON.stringify({ directory: projectDirectoryKey(directory), identity }), { mode: 384 });
1002
- renameSync(temporary, destination);
1003
- } catch {}
1004
- }
1005
- function readRememberedGitIdentity(directory, storageDir) {
1006
- try {
1007
- const record = JSON.parse(readFileSync(cachePath(directory, storageDir), "utf8"));
1008
- if (record.directory === projectDirectoryKey(directory) && /^git:[0-9a-f]{7,64}$/.test(record.identity))
1009
- return record.identity;
1010
- } catch {}
1011
- return;
835
+ // src/shared/xml-unescape.ts
836
+ var XML_ENTITY_REGEX = /&(amp|apos|quot|lt|gt);/g;
837
+ var XML_ENTITIES = {
838
+ amp: "&",
839
+ apos: "'",
840
+ quot: '"',
841
+ lt: "<",
842
+ gt: ">"
843
+ };
844
+ function unescapeXml(s) {
845
+ return s.replace(XML_ENTITY_REGEX, (entity, name) => XML_ENTITIES[name] ?? entity);
1012
846
  }
1013
847
 
1014
- // src/features/magic-context/memory/project-identity.ts
1015
- var GIT_TIMEOUT_MS = 5000;
1016
- var TRANSIENT_FAILURE_COOLDOWN_MS = 5 * 60 * 1000;
1017
- var identityCache = new Map;
1018
- var linkedGitWorktreeCache = new Map;
1019
- var lastKnownGitIdentityCache = new Map;
1020
- var directoryFallbackCache = new Map;
1021
- var transientFailureCooldown = new Map;
1022
- var dubiousOwnershipFallbackDirectories = new Set;
1023
- var dubiousOwnershipLoggedDirectories = new Set;
1024
- var dubiousOwnershipWarnedDirectories = new Set;
1025
- var transientGitIdentityReuseLoggedDirectories = new Set;
1026
- var sessionIdentityCache = new Map;
1027
- var homeProjectPermission = false;
1028
- var homeProjectSkipLogged = false;
1029
- var pausedIdentityReasons = new Map;
1030
- var pausedIdentityLogged = new Set;
1031
- function setHomeProjectPermission(allowed) {
1032
- homeProjectPermission = allowed;
848
+ // src/hooks/magic-context/compartment-parser.ts
849
+ var COMPARTMENT_OPEN_REGEX = /<compartment\s+((?:[^>"]|"[^"\n]*")*?)\s*>/g;
850
+ var COMPARTMENT_CLOSE_TAG = "</compartment>";
851
+ var COMPARTMENT_CLOSE_FOLLOWER_REGEX = /^\s*(?:$|<(?:compartment\s|\/compartments\s*>|\/output\s*>|(?:facts|events|meta|user_observations|primer_candidates|unprocessed_from)\s*>))/;
852
+ var ATTR_START_REGEX = /\bstart="(\d+)"/;
853
+ var ATTR_END_REGEX = /\bend="(\d+)"/;
854
+ var ATTR_TITLE_REGEX = /\btitle="([^"]*)"/;
855
+ var ATTR_EPISODE_REGEX = /\bepisode_type="([^"]*)"/;
856
+ var ATTR_IMPORTANCE_REGEX = /\bimportance="(\d+)"/;
857
+ var TIER_OPEN_REGEX = /<p(\d)\s*(\/?)>/g;
858
+ var TIER_CLOSE_REGEX = /<\/p(\d)\s*>/g;
859
+ var TIER_CLOSE_FOLLOWER_REGEX = /^\s*(?:$|<\/?p\d\s*\/?>)/;
860
+ var TIER_CLOSE_ANY_REGEX = /<\/p\d/;
861
+ var TIER_OPEN_ANY_REGEX = /<p\d/;
862
+ var CATEGORY_BLOCK_REGEX = /<([A-Za-z_][A-Za-z0-9_-]*)>(.*?)<\/\1>/gs;
863
+ var HISTORIAN_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
864
+ var FACT_ITEM_REGEX = /^\s*\*\s*(.+)$/gm;
865
+ var UNPROCESSED_REGEX = /<unprocessed_from>(\d+)<\/unprocessed_from>/;
866
+ var USER_OBSERVATIONS_REGEX = /<user_observations>(.*?)<\/user_observations>/s;
867
+ var USER_OBS_ITEM_REGEX = /^\s*\*\s*(.+)$/gm;
868
+ var PRIMER_CANDIDATES_REGEX = /<primer_candidates>(.*?)<\/primer_candidates>/s;
869
+ var PRIMER_ELEMENT_REGEX = /<primer\s+at_compartment="(\d+)"\s*>(.*?)<\/primer>/gs;
870
+ var PRIMER_ITEM_REGEX = /^\s*(?:\*|-|\d+\.)\s*(.+)$/gm;
871
+ var FACTS_BLOCK_REGEX = /<facts>(.*?)<\/facts>/s;
872
+ var EVENTS_BLOCK_REGEX = /<events>(.*?)<\/events>/s;
873
+ var EVENT_ELEMENT_REGEX = /<([a-z_]+)\s+at_compartment="(\d+)"\s*>(.*?)<\/\1>/gs;
874
+ var EVENT_FIELD_REGEX = /<([a-z_]+)\s*>(.*?)<\/\1>/gs;
875
+ function searchFrom(s, regex, from) {
876
+ const at = s.slice(from).search(regex);
877
+ return at === -1 ? -1 : from + at;
1033
878
  }
1034
- function shouldSkipHomeProjectMemory(directory) {
1035
- try {
1036
- assertProjectAllowed(directory, homeProjectPermission);
1037
- return false;
1038
- } catch (error) {
1039
- if (!(error instanceof ProjectIdentityError) || error.errorClass !== "home_project_disabled")
1040
- throw error;
1041
- if (!homeProjectSkipLogged) {
1042
- homeProjectSkipLogged = true;
1043
- log("[magic-context] home project memory disabled; skipping memory features");
879
+ function hasTierOpenerFrom(inner, digit, from) {
880
+ return new RegExp(`<p${digit}\\s*/?>`).test(inner.slice(from));
881
+ }
882
+ function findMatchingTierClose(inner, bodyStart, digit, found) {
883
+ const closeRegex = new RegExp(TIER_CLOSE_REGEX.source, "g");
884
+ closeRegex.lastIndex = bodyStart;
885
+ for (let close = closeRegex.exec(inner);close; close = closeRegex.exec(inner)) {
886
+ const end = close.index + close[0].length;
887
+ if (Number(close[1]) !== digit)
888
+ continue;
889
+ if (!TIER_CLOSE_FOLLOWER_REGEX.test(inner.slice(end)))
890
+ continue;
891
+ const body = inner.slice(bodyStart, close.index);
892
+ for (const open of body.matchAll(new RegExp(TIER_OPEN_REGEX.source, "g"))) {
893
+ const other = Number(open[1]);
894
+ const otherwiseMissing = other >= 1 && other <= 4 && other !== digit && !found.has(other) && !hasTierOpenerFrom(inner, other, end);
895
+ if (otherwiseMissing)
896
+ return;
1044
897
  }
1045
- return true;
898
+ return { start: close.index, end };
1046
899
  }
900
+ return;
1047
901
  }
1048
- var execFileSyncForIdentity = execFileSync;
1049
- var userHomeDirectoryForIdentity = () => homedir();
1050
- var nowMs = () => Date.now();
1051
- var filesystemProbeObserverForTests;
1052
-
1053
- class ProjectIdentityError extends Error {
1054
- errorClass;
1055
- rawDirectory;
1056
- constructor(errorClass, rawDirectory, message, cause) {
1057
- super(message);
1058
- this.name = "ProjectIdentityError";
1059
- this.errorClass = errorClass;
1060
- this.rawDirectory = rawDirectory;
1061
- if (cause) {
1062
- this.cause = cause;
902
+ function extractTiers(inner) {
903
+ const tiers = new Map;
904
+ const openRegex = new RegExp(TIER_OPEN_REGEX.source, "g");
905
+ let pos = 0;
906
+ for (;; ) {
907
+ openRegex.lastIndex = pos;
908
+ const open = openRegex.exec(inner);
909
+ if (!open)
910
+ break;
911
+ const digit = Number(open[1]);
912
+ const bodyStart = open.index + open[0].length;
913
+ let bodyEnd = bodyStart;
914
+ pos = bodyStart;
915
+ if (open[2] !== "/") {
916
+ const close = findMatchingTierClose(inner, bodyStart, digit, tiers);
917
+ if (close) {
918
+ bodyEnd = close.start;
919
+ pos = close.end;
920
+ } else {
921
+ const closeAt = searchFrom(inner, TIER_CLOSE_ANY_REGEX, bodyStart);
922
+ bodyEnd = closeAt === -1 ? inner.length : closeAt;
923
+ pos = closeAt === -1 ? inner.length : closeAt + 1;
924
+ const openInside = searchFrom(inner.slice(0, bodyEnd), TIER_OPEN_ANY_REGEX, bodyStart);
925
+ if (openInside !== -1) {
926
+ bodyEnd = openInside;
927
+ pos = openInside;
928
+ }
929
+ }
930
+ }
931
+ if (!tiers.has(digit)) {
932
+ tiers.set(digit, unescapeXml(inner.slice(bodyStart, bodyEnd).trim()));
1063
933
  }
1064
934
  }
935
+ return tiers;
1065
936
  }
1066
- function asError(error) {
1067
- return error instanceof Error ? error : undefined;
1068
- }
1069
- function getErrorCode(error) {
1070
- if (error === null || typeof error !== "object" || !("code" in error)) {
1071
- return;
1072
- }
1073
- const code = error.code;
1074
- return typeof code === "string" ? code : undefined;
937
+ function extractTiersFromInner(inner) {
938
+ const tiers = extractTiers(inner);
939
+ return { p1: tiers.get(1), p2: tiers.get(2), p3: tiers.get(3), p4: tiers.get(4) };
1075
940
  }
1076
- function getErrorSignal(error) {
1077
- if (error === null || typeof error !== "object" || !("signal" in error)) {
1078
- return;
941
+ function findCompartmentElements(text) {
942
+ const elements = [];
943
+ const openRegex = new RegExp(COMPARTMENT_OPEN_REGEX.source, "g");
944
+ let cursor = 0;
945
+ for (;; ) {
946
+ openRegex.lastIndex = cursor;
947
+ const open = openRegex.exec(text);
948
+ if (!open)
949
+ break;
950
+ const bodyStart = open.index + open[0].length;
951
+ const nextOpen = openRegex.exec(text);
952
+ const windowEnd = nextOpen ? nextOpen.index : text.length;
953
+ const firstClose = text.indexOf(COMPARTMENT_CLOSE_TAG, bodyStart);
954
+ if (firstClose === -1)
955
+ break;
956
+ let closeAt = firstClose;
957
+ for (let at = firstClose;at !== -1 && at < windowEnd; at = text.indexOf(COMPARTMENT_CLOSE_TAG, at + 1)) {
958
+ if (COMPARTMENT_CLOSE_FOLLOWER_REGEX.test(text.slice(at + COMPARTMENT_CLOSE_TAG.length))) {
959
+ closeAt = at;
960
+ break;
961
+ }
962
+ }
963
+ const end = closeAt + COMPARTMENT_CLOSE_TAG.length;
964
+ elements.push({
965
+ start: open.index,
966
+ end,
967
+ attrs: open[1],
968
+ inner: text.slice(bodyStart, closeAt)
969
+ });
970
+ cursor = end;
1079
971
  }
1080
- const signal = error.signal;
1081
- return typeof signal === "string" ? signal : undefined;
972
+ return elements;
1082
973
  }
1083
- function getErrorKilled(error) {
1084
- if (error === null || typeof error !== "object" || !("killed" in error)) {
1085
- return false;
974
+ function textOutsideCompartments(text, elements) {
975
+ let outside = "";
976
+ let last = 0;
977
+ for (const element of elements) {
978
+ outside += text.slice(last, element.start);
979
+ last = element.end;
1086
980
  }
1087
- return error.killed === true;
981
+ return outside + text.slice(last);
1088
982
  }
1089
- function getErrorStderr(error) {
1090
- if (error === null || typeof error !== "object" || !("stderr" in error)) {
1091
- return "";
1092
- }
1093
- const stderr = error.stderr;
1094
- if (typeof stderr === "string") {
1095
- return stderr;
983
+ function parseCompartmentOutput(text) {
984
+ const compartments = [];
985
+ const facts = [];
986
+ let droppedFactBlocks = 0;
987
+ let droppedFacts = 0;
988
+ const elements = findCompartmentElements(text);
989
+ for (const { attrs, inner } of elements) {
990
+ const startMatch = attrs.match(ATTR_START_REGEX);
991
+ const endMatch = attrs.match(ATTR_END_REGEX);
992
+ const titleMatch = attrs.match(ATTR_TITLE_REGEX);
993
+ if (!startMatch || !endMatch || !titleMatch)
994
+ continue;
995
+ const startMessage = parseInt(startMatch[1], 10);
996
+ const endMessage = parseInt(endMatch[1], 10);
997
+ const title = unescapeXml(titleMatch[1]);
998
+ if (Number.isNaN(startMessage) || Number.isNaN(endMessage) || !title)
999
+ continue;
1000
+ const episodeMatch = attrs.match(ATTR_EPISODE_REGEX);
1001
+ const importanceMatch = attrs.match(ATTR_IMPORTANCE_REGEX);
1002
+ const episodeType = episodeMatch ? unescapeXml(episodeMatch[1]) : undefined;
1003
+ const importance = importanceMatch ? parseInt(importanceMatch[1], 10) : undefined;
1004
+ const { p1, p2, p3, p4 } = extractTiersFromInner(inner);
1005
+ if (typeof p1 === "string" && p1.length > 0) {
1006
+ compartments.push({
1007
+ startMessage,
1008
+ endMessage,
1009
+ title,
1010
+ content: p1,
1011
+ p1,
1012
+ p2: typeof p2 === "string" ? p2 : p1,
1013
+ p3: typeof p3 === "string" ? p3 : typeof p2 === "string" ? p2 : p1,
1014
+ p4: typeof p4 === "string" ? p4 : "",
1015
+ importance,
1016
+ episodeType
1017
+ });
1018
+ continue;
1019
+ }
1020
+ const content = unescapeXml(inner.trim());
1021
+ if (content) {
1022
+ compartments.push({
1023
+ startMessage,
1024
+ endMessage,
1025
+ title,
1026
+ content,
1027
+ importance,
1028
+ episodeType
1029
+ });
1030
+ }
1096
1031
  }
1097
- if (Buffer.isBuffer(stderr)) {
1098
- return stderr.toString("utf8");
1032
+ const outside = textOutsideCompartments(text, elements);
1033
+ const factsBlockMatch = outside.match(FACTS_BLOCK_REGEX);
1034
+ const factsScope = factsBlockMatch ? factsBlockMatch[1] : outside.replace(EVENTS_BLOCK_REGEX, "").replace(/<(meta|user_observations|primer_candidates)>.*?<\/\1>/gs, "").replace(/<\/?(?:output|compartments)>/g, "");
1035
+ for (const categoryMatch of factsScope.matchAll(CATEGORY_BLOCK_REGEX)) {
1036
+ const category = categoryMatch[1];
1037
+ const blockContent = categoryMatch[2];
1038
+ const items = [...blockContent.matchAll(FACT_ITEM_REGEX)].map((match) => unescapeXml(match[1].trim())).filter(Boolean);
1039
+ if (!HISTORIAN_CATEGORIES.has(category)) {
1040
+ if (items.length === 0)
1041
+ continue;
1042
+ droppedFactBlocks++;
1043
+ droppedFacts += items.length;
1044
+ log(`[historian] Dropped <facts> category ${category} (${items.length} facts)`);
1045
+ continue;
1046
+ }
1047
+ for (const content of items)
1048
+ facts.push({ category, content });
1099
1049
  }
1100
- return "";
1101
- }
1102
- function directoryFallback(directory) {
1103
- const canonical = path3.resolve(directory);
1104
- const hash = createHash3("md5").update(canonical, "utf8").digest("hex").slice(0, 12);
1105
- return `dir:${hash}`;
1106
- }
1107
- function rememberedIdentityOrDirectoryFallback(canonical) {
1108
- return readRememberedGitIdentity(canonical) ?? directoryFallback(canonical);
1109
- }
1110
- function assertDirectoryUsable(canonicalDirectory, rawDirectory) {
1111
- try {
1112
- const stat = statSync(canonicalDirectory);
1113
- if (!stat.isDirectory()) {
1114
- throw new ProjectIdentityError("unknown", rawDirectory, `Project path is not a directory: ${canonicalDirectory}`);
1050
+ const unprocessedMatch = outside.match(UNPROCESSED_REGEX);
1051
+ const unprocessedFrom = unprocessedMatch ? parseInt(unprocessedMatch[1], 10) : null;
1052
+ const userObservations = [];
1053
+ const userObsMatch = outside.match(USER_OBSERVATIONS_REGEX);
1054
+ if (userObsMatch) {
1055
+ for (const itemMatch of userObsMatch[1].matchAll(USER_OBS_ITEM_REGEX)) {
1056
+ const obs = unescapeXml(itemMatch[1].trim());
1057
+ if (obs)
1058
+ userObservations.push(obs);
1115
1059
  }
1116
- } catch (error) {
1117
- if (error instanceof ProjectIdentityError) {
1118
- throw error;
1060
+ }
1061
+ const primerCandidates = [];
1062
+ const primerMatch = outside.match(PRIMER_CANDIDATES_REGEX);
1063
+ if (primerMatch) {
1064
+ const block = primerMatch[1];
1065
+ let sawElement = false;
1066
+ for (const el of block.matchAll(PRIMER_ELEMENT_REGEX)) {
1067
+ sawElement = true;
1068
+ const question = unescapeXml(el[2].trim());
1069
+ if (question) {
1070
+ primerCandidates.push({
1071
+ question,
1072
+ originCompartmentIndex: Number.parseInt(el[1], 10)
1073
+ });
1074
+ }
1119
1075
  }
1120
- const code = getErrorCode(error);
1121
- if (code === "EACCES" || code === "EPERM") {
1122
- throw new ProjectIdentityError("permission_denied", rawDirectory, `Permission denied while accessing project directory: ${canonicalDirectory}`, asError(error));
1076
+ if (!sawElement) {
1077
+ for (const itemMatch of block.matchAll(PRIMER_ITEM_REGEX)) {
1078
+ const question = unescapeXml(itemMatch[1].trim());
1079
+ if (question)
1080
+ primerCandidates.push({ question });
1081
+ }
1123
1082
  }
1124
- throw new ProjectIdentityError("unknown", rawDirectory, `Unable to access project directory: ${canonicalDirectory}`, asError(error));
1125
1083
  }
1084
+ const events = parseEvents(outside);
1085
+ const sortedByStart = compartments.map((compartment, emitted) => ({ compartment, emitted })).sort((a, b) => a.compartment.startMessage - b.compartment.startMessage);
1086
+ const sortedPosition = [];
1087
+ sortedByStart.forEach(({ emitted }, sorted) => {
1088
+ sortedPosition[emitted] = sorted + 1;
1089
+ });
1090
+ const remapAnchor = (anchor) => anchor >= 1 && anchor <= sortedPosition.length ? sortedPosition[anchor - 1] : anchor;
1091
+ return {
1092
+ compartments: sortedByStart.map(({ compartment }) => compartment),
1093
+ facts,
1094
+ droppedFactBlocks,
1095
+ droppedFacts,
1096
+ events: events.map((event) => event.atCompartment === null ? event : { ...event, atCompartment: remapAnchor(event.atCompartment) }),
1097
+ unprocessedFrom,
1098
+ userObservations,
1099
+ primerCandidates: primerCandidates.map((candidate) => candidate.originCompartmentIndex === undefined ? candidate : {
1100
+ ...candidate,
1101
+ originCompartmentIndex: remapAnchor(candidate.originCompartmentIndex)
1102
+ })
1103
+ };
1126
1104
  }
1127
- function isGitTimeoutError(error) {
1128
- const code = getErrorCode(error);
1129
- const signal = getErrorSignal(error);
1130
- return code === "ETIMEDOUT" || signal === "SIGTERM" || signal === "SIGKILL" || getErrorKilled(error);
1105
+ function parseEvents(text) {
1106
+ const blockMatch = text.match(EVENTS_BLOCK_REGEX);
1107
+ if (!blockMatch)
1108
+ return [];
1109
+ const block = blockMatch[1];
1110
+ const events = [];
1111
+ for (const elMatch of block.matchAll(EVENT_ELEMENT_REGEX)) {
1112
+ const kind = elMatch[1];
1113
+ const atRaw = parseInt(elMatch[2], 10);
1114
+ const atCompartment = Number.isNaN(atRaw) ? null : atRaw;
1115
+ const fields = {};
1116
+ for (const fieldMatch of elMatch[3].matchAll(EVENT_FIELD_REGEX)) {
1117
+ const name = fieldMatch[1];
1118
+ const value = unescapeXml(fieldMatch[2].trim());
1119
+ if (value)
1120
+ fields[name] = value;
1121
+ }
1122
+ events.push({ kind, atCompartment, fields });
1123
+ }
1124
+ return events;
1131
1125
  }
1132
- function hasUnbornHead(directory) {
1133
- const options = {
1134
- cwd: directory,
1135
- encoding: "utf8",
1136
- env: { ...process.env, LC_ALL: "C", LANG: "C" },
1137
- stdio: ["ignore", "pipe", "pipe"],
1138
- timeout: GIT_TIMEOUT_MS,
1139
- windowsHide: true
1140
- };
1141
- try {
1142
- execFileSyncForIdentity("git", ["rev-parse", "--git-dir"], options);
1143
- try {
1144
- execFileSyncForIdentity("git", ["rev-parse", "--verify", "--quiet", "HEAD"], options);
1145
- return false;
1146
- } catch (error) {
1147
- return !isGitTimeoutError(error) && typeof error === "object" && error !== null && "status" in error && error.status === 1 && getErrorStderr(error) === "";
1126
+
1127
+ // src/features/magic-context/lkg-prefix-chunks.ts
1128
+ import { createHash as createHash3 } from "node:crypto";
1129
+ var LKG_PREFIX_CHUNK_CHARS = 64 * 1024;
1130
+ function splitLkgPrefix(jsonPrefix) {
1131
+ const chunks = [];
1132
+ let start = 0;
1133
+ while (start < jsonPrefix.length) {
1134
+ let end = Math.min(start + LKG_PREFIX_CHUNK_CHARS, jsonPrefix.length);
1135
+ if (end < jsonPrefix.length) {
1136
+ const last = jsonPrefix.charCodeAt(end - 1);
1137
+ if (last >= 55296 && last <= 56319)
1138
+ end -= 1;
1148
1139
  }
1149
- } catch {
1150
- return false;
1140
+ chunks.push(jsonPrefix.slice(start, end));
1141
+ start = end;
1151
1142
  }
1143
+ return chunks;
1152
1144
  }
1153
- function classifyGitError(error, rawDirectory) {
1154
- if (isGitTimeoutError(error)) {
1155
- return new ProjectIdentityError("git_timeout", rawDirectory, `git rev-list timed out after ${GIT_TIMEOUT_MS}ms`, asError(error));
1156
- }
1157
- const code = getErrorCode(error);
1158
- if (code === "ENOENT") {
1159
- return new ProjectIdentityError("git_missing", rawDirectory, "git binary is not available in PATH", asError(error));
1160
- }
1161
- if (code === "EACCES" || code === "EPERM") {
1162
- return new ProjectIdentityError("permission_denied", rawDirectory, "Permission denied while spawning git", asError(error));
1163
- }
1164
- const stderr = getErrorStderr(error).toLowerCase();
1165
- if (stderr.includes("detected dubious ownership")) {
1166
- return new ProjectIdentityError("dubious_ownership", rawDirectory, "git refused to read the repository because it detected dubious ownership", asError(error));
1145
+ function hashLkgChunk(body) {
1146
+ return createHash3("sha256").update(body).digest("hex");
1147
+ }
1148
+ function hashLkgChunkList(chunkHashes) {
1149
+ return createHash3("sha256").update(chunkHashes.join(`
1150
+ `)).digest("hex");
1151
+ }
1152
+ function layoutLkgPrefix(jsonPrefix) {
1153
+ const chunks = splitLkgPrefix(jsonPrefix);
1154
+ const chunkHashes = chunks.map(hashLkgChunk);
1155
+ return { chunks, chunkHashes, chars: jsonPrefix.length, hash: hashLkgChunkList(chunkHashes) };
1156
+ }
1157
+ function assembleLkgPrefix(expected, rows) {
1158
+ const { chars, chunks, hash } = expected;
1159
+ if (typeof chars !== "number" || !Number.isSafeInteger(chars) || typeof chunks !== "number" || !Number.isSafeInteger(chunks) || typeof hash !== "string" || rows.length !== chunks) {
1160
+ return;
1167
1161
  }
1168
- if (stderr.includes("not a git repository") || stderr.includes("does not have any commits yet") || stderr.includes("ambiguous argument 'head'") || stderr.includes("unknown revision or path")) {
1169
- return new ProjectIdentityError("not_git_repo", rawDirectory, "Directory has no git root commit; caller may use directory fallback", asError(error));
1162
+ const bodies = [];
1163
+ let length = 0;
1164
+ for (let index = 0;index < rows.length; index += 1) {
1165
+ const row = rows[index];
1166
+ if (row?.chunk !== index || typeof row.body !== "string")
1167
+ return;
1168
+ bodies.push(row.body);
1169
+ length += row.body.length;
1170
1170
  }
1171
- return new ProjectIdentityError("unknown", rawDirectory, "git rev-list failed while resolving project identity", asError(error));
1171
+ if (length !== chars)
1172
+ return;
1173
+ const chunkHashes = bodies.map(hashLkgChunk);
1174
+ if (chunkHashes.some((chunkHash, index) => rows[index]?.hash !== chunkHash))
1175
+ return;
1176
+ if (hashLkgChunkList(chunkHashes) !== hash)
1177
+ return;
1178
+ return { jsonPrefix: bodies.join(""), chunkHashes, hash };
1172
1179
  }
1173
- function resolveProjectIdentityStrict(directory, allowHomeProject = homeProjectPermission) {
1174
- assertProjectAllowed(directory, allowHomeProject);
1175
- const canonical = path3.resolve(directory);
1176
- const cached = identityCache.get(canonical);
1177
- if (cached !== undefined) {
1178
- return cached;
1179
- }
1180
- assertDirectoryUsable(canonical, directory);
1181
- if (!hasGitDir(canonical)) {
1182
- throw new ProjectIdentityError("not_git_repo", directory, "Directory has no git metadata; caller may use directory fallback");
1180
+
1181
+ // src/features/magic-context/migration-v94-write-split.ts
1182
+ var LKG_SLOT_CHUNKS_DDL = `
1183
+ CREATE TABLE IF NOT EXISTS lkg_slot_chunks (
1184
+ session_id TEXT NOT NULL,
1185
+ chunk INTEGER NOT NULL,
1186
+ hash TEXT NOT NULL,
1187
+ body TEXT NOT NULL,
1188
+ PRIMARY KEY (session_id, chunk)
1189
+ );
1190
+ `;
1191
+ var SESSION_REPLAY_DECISIONS_DDL = `
1192
+ CREATE TABLE IF NOT EXISTS session_replay_decisions (
1193
+ session_id TEXT NOT NULL,
1194
+ message_id TEXT NOT NULL,
1195
+ decision TEXT NOT NULL,
1196
+ PRIMARY KEY (session_id, message_id)
1197
+ ) WITHOUT ROWID;
1198
+ `;
1199
+ function lkgSlotsDdl(table) {
1200
+ return `
1201
+ CREATE TABLE IF NOT EXISTS ${table} (
1202
+ session_id TEXT PRIMARY KEY,
1203
+ json_prefix_chars INTEGER NOT NULL,
1204
+ json_prefix_chunks INTEGER NOT NULL,
1205
+ json_prefix_hash TEXT NOT NULL,
1206
+ input_id_seq TEXT NOT NULL,
1207
+ input_content_digests TEXT NOT NULL,
1208
+ input_content_signatures TEXT,
1209
+ last_input_message_id TEXT NOT NULL,
1210
+ model_key TEXT,
1211
+ provider_key TEXT,
1212
+ captured_at INTEGER NOT NULL,
1213
+ row_version INTEGER,
1214
+ capture_sequence INTEGER
1215
+ );
1216
+ `;
1217
+ }
1218
+ var LKG_SLOTS_DDL = lkgSlotsDdl("lkg_slots");
1219
+ var LKG_MOVE_WINDOW_MS = 24 * 60 * 60 * 1000;
1220
+ function tableExists(db, name) {
1221
+ return Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(name));
1222
+ }
1223
+ function columnExists(db, table, column) {
1224
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
1225
+ return rows.some((row) => row.name === column);
1226
+ }
1227
+ function splitLkgSlotPrefixes(db, now = Date.now()) {
1228
+ db.exec(LKG_SLOT_CHUNKS_DDL);
1229
+ if (!tableExists(db, "lkg_slots")) {
1230
+ db.exec(LKG_SLOTS_DDL);
1231
+ return;
1183
1232
  }
1184
- let output;
1185
- try {
1186
- output = execFileSyncForIdentity("git", ["rev-list", "--max-parents=0", "HEAD"], {
1187
- cwd: canonical,
1188
- encoding: "utf8",
1189
- env: { ...process.env, LC_ALL: "C", LANG: "C" },
1190
- stdio: ["ignore", "pipe", "pipe"],
1191
- timeout: GIT_TIMEOUT_MS,
1192
- windowsHide: true
1193
- });
1194
- } catch (error) {
1195
- const classified = classifyGitError(error, directory);
1196
- if (classified.errorClass === "not_git_repo" && hasUnbornHead(canonical)) {
1197
- throw new ProjectIdentityError("no_commits", directory, "Git repository has no commits yet");
1233
+ if (!columnExists(db, "lkg_slots", "json_prefix"))
1234
+ return;
1235
+ db.prepare("DELETE FROM lkg_slots WHERE NOT (captured_at >= ?)").run(now - LKG_MOVE_WINDOW_MS);
1236
+ db.exec("DROP TABLE IF EXISTS lkg_slots_v94");
1237
+ db.exec(lkgSlotsDdl("lkg_slots_v94"));
1238
+ const sessionIds = db.prepare("SELECT session_id FROM lkg_slots").all().map((row) => row.session_id);
1239
+ const readPrefix = db.prepare("SELECT json_prefix FROM lkg_slots WHERE session_id IS ?");
1240
+ const insertSlot = db.prepare(`INSERT OR IGNORE INTO lkg_slots_v94 (
1241
+ session_id, json_prefix_chars, json_prefix_chunks, json_prefix_hash,
1242
+ input_id_seq, input_content_digests, input_content_signatures,
1243
+ last_input_message_id, model_key, provider_key,
1244
+ captured_at, row_version, capture_sequence
1245
+ )
1246
+ SELECT session_id, ?, ?, ?,
1247
+ input_id_seq, input_content_digests, input_content_signatures,
1248
+ last_input_message_id, model_key, provider_key,
1249
+ captured_at, row_version, capture_sequence
1250
+ FROM lkg_slots WHERE session_id IS ?`);
1251
+ const insertChunk = db.prepare("INSERT INTO lkg_slot_chunks (session_id, chunk, hash, body) VALUES (?, ?, ?, ?)");
1252
+ db.prepare("DELETE FROM lkg_slot_chunks").run();
1253
+ for (const sessionId of sessionIds) {
1254
+ if (typeof sessionId !== "string")
1255
+ continue;
1256
+ const row = readPrefix.get(sessionId);
1257
+ if (typeof row?.json_prefix !== "string")
1258
+ continue;
1259
+ const layout = layoutLkgPrefix(row.json_prefix);
1260
+ const inserted = insertSlot.run(layout.chars, layout.chunks.length, layout.hash, sessionId);
1261
+ if (Number(inserted.changes) !== 1)
1262
+ continue;
1263
+ for (let index = 0;index < layout.chunks.length; index += 1) {
1264
+ insertChunk.run(sessionId, index, layout.chunkHashes[index], layout.chunks[index]);
1198
1265
  }
1199
- throw classified;
1200
- }
1201
- const rootCommit = output.split(`
1202
- `).map((line) => line.trim().slice(0, 64)).filter((line) => /^[0-9a-f]{7,64}$/.test(line)).sort()[0];
1203
- if (!rootCommit) {
1204
- throw new ProjectIdentityError("unknown", directory, "git rev-list returned no valid root commit hash");
1205
1266
  }
1206
- const identity = `git:${rootCommit}`;
1207
- identityCache.set(canonical, identity);
1208
- lastKnownGitIdentityCache.set(canonical, identity);
1209
- rememberGitIdentity(canonical, identity);
1210
- const root = gitRootDirectory(canonical);
1211
- if (root && root !== canonical)
1212
- rememberGitIdentity(root, identity);
1213
- transientFailureCooldown.delete(canonical);
1214
- dubiousOwnershipFallbackDirectories.delete(canonical);
1215
- transientGitIdentityReuseLoggedDirectories.delete(canonical);
1216
- return identity;
1267
+ db.exec("DROP TABLE lkg_slots");
1268
+ db.exec("ALTER TABLE lkg_slots_v94 RENAME TO lkg_slots");
1217
1269
  }
1218
- function shouldUseDirectoryFallback(error) {
1219
- return error.errorClass !== "home_project_disabled";
1270
+ function validDecisionSql(type, value) {
1271
+ return `(${type} = 'text' AND (
1272
+ ${value} IN ('keep', 'strip') OR (
1273
+ ${value} GLOB 'keep:[1-9]*'
1274
+ AND substr(${value}, 6) NOT GLOB '*[^0-9]*'
1275
+ AND length(${value}) <= 10
1276
+ AND CAST(substr(${value}, 6) AS INTEGER) BETWEEN 2 AND 10000
1277
+ )
1278
+ ))`;
1220
1279
  }
1221
- function getActiveCooldown(canonical) {
1222
- const until = transientFailureCooldown.get(canonical);
1223
- if (until === undefined)
1280
+ function splitReplayDecisions(db) {
1281
+ db.exec(SESSION_REPLAY_DECISIONS_DDL);
1282
+ if (!tableExists(db, "session_meta"))
1224
1283
  return;
1225
- if (nowMs() < until)
1226
- return until;
1227
- transientFailureCooldown.delete(canonical);
1228
- return;
1229
- }
1230
- function lastKnownGitIdentity(canonical) {
1231
- return lastKnownGitIdentityCache.get(canonical) ?? identityCache.get(canonical) ?? readRememberedGitIdentity(canonical);
1232
- }
1233
- function nearestLastKnownGitIdentity(canonical) {
1234
- const visited = new Set;
1235
- const walk = (start) => {
1236
- let current = start;
1237
- while (!visited.has(current)) {
1238
- visited.add(current);
1239
- const cached = lastKnownGitIdentity(current);
1240
- if (cached !== undefined)
1241
- return { identity: cached, source: current };
1242
- if (existsSync(path3.join(current, ".git")))
1243
- break;
1244
- const parent = path3.dirname(current);
1245
- if (parent === current)
1246
- break;
1247
- current = parent;
1248
- }
1284
+ if (!columnExists(db, "session_meta", "trailing_blank_decisions"))
1249
1285
  return;
1250
- };
1251
- const exactOrAncestor = walk(canonical);
1252
- if (exactOrAncestor)
1253
- return exactOrAncestor;
1254
- try {
1255
- const realCanonical = realpathSync2.native(canonical);
1256
- if (realCanonical !== canonical)
1257
- return walk(realCanonical);
1258
- } catch {}
1259
- return;
1286
+ const doc = "sm.trailing_blank_decisions";
1287
+ const hasVersionKey = `json_type(${doc}, '$.version') IS NOT NULL`;
1288
+ const versionIsDecision = validDecisionSql(`json_type(${doc}, '$.version')`, `json_extract(${doc}, '$.version')`);
1289
+ const isV2 = `(${hasVersionKey} AND NOT ${versionIsDecision})`;
1290
+ const v2Valid = `(
1291
+ json_type(${doc}, '$.version') IN ('integer', 'real')
1292
+ AND json_extract(${doc}, '$.version') = 2
1293
+ AND json_type(${doc}, '$.trailingBlank') = 'object'
1294
+ AND NOT EXISTS (
1295
+ SELECT 1 FROM json_each(${doc}, '$.trailingBlank') AS entry
1296
+ WHERE entry.key = '' OR NOT ${validDecisionSql("entry.type", "entry.value")}
1297
+ )
1298
+ )`;
1299
+ const v1Valid = `NOT EXISTS (
1300
+ SELECT 1 FROM json_each(${doc}) AS entry
1301
+ WHERE entry.key = '' OR NOT ${validDecisionSql("entry.type", "entry.value")}
1302
+ )`;
1303
+ db.exec("DROP TABLE IF EXISTS temp.v94_replay_documents");
1304
+ db.exec(`
1305
+ CREATE TEMP TABLE v94_replay_documents AS
1306
+ SELECT session_id, version FROM (
1307
+ SELECT sm.session_id AS session_id, CASE WHEN ${isV2} THEN 2 ELSE 1 END AS version,
1308
+ CASE WHEN ${isV2} THEN ${v2Valid} ELSE ${v1Valid} END AS strictly_valid
1309
+ FROM session_meta AS sm
1310
+ WHERE typeof(${doc}) = 'text' AND ${doc} <> ''
1311
+ AND json_valid(${doc}) AND json_type(${doc}) = 'object'
1312
+ ) WHERE strictly_valid;
1313
+ `);
1314
+ db.exec(`
1315
+ INSERT INTO session_replay_decisions (session_id, message_id, decision)
1316
+ SELECT sm.session_id, entry.key, entry.value
1317
+ FROM v94_replay_documents AS moved
1318
+ JOIN session_meta AS sm ON sm.session_id = moved.session_id
1319
+ JOIN json_each(
1320
+ ${doc}, CASE moved.version WHEN 2 THEN '$.trailingBlank' ELSE '$' END
1321
+ ) AS entry
1322
+ WHERE true
1323
+ ON CONFLICT(session_id, message_id) DO UPDATE SET decision = excluded.decision;
1324
+ `);
1325
+ db.exec(`
1326
+ UPDATE session_meta SET trailing_blank_decisions = (
1327
+ SELECT CASE moved.version
1328
+ WHEN 2 THEN json_set(session_meta.trailing_blank_decisions, '$.trailingBlank', json('{}'))
1329
+ ELSE ''
1330
+ END
1331
+ FROM v94_replay_documents AS moved WHERE moved.session_id = session_meta.session_id
1332
+ )
1333
+ WHERE session_id IN (SELECT session_id FROM v94_replay_documents);
1334
+ `);
1335
+ db.exec("DROP TABLE temp.v94_replay_documents");
1260
1336
  }
1261
- function reuseLastKnownGitIdentity(canonical) {
1262
- const cached = nearestLastKnownGitIdentity(canonical);
1263
- if (cached === undefined)
1264
- return;
1265
- if (!transientGitIdentityReuseLoggedDirectories.has(canonical)) {
1266
- transientGitIdentityReuseLoggedDirectories.add(canonical);
1267
- const sourceNote = cached.source === canonical ? "" : ` from ancestor ${cached.source}`;
1268
- log(`[magic-context] git identity resolution is temporarily unavailable for ${canonical}; reusing the last successful project identity${sourceNote} to avoid splitting project-scoped memory`);
1337
+
1338
+ // src/features/magic-context/opencode2-relabel.ts
1339
+ var OPENCODE2_RELABEL_STATE_KEY = "v87_opencode2_relabel";
1340
+ var V87_HARNESS_TWIN_RULES = [
1341
+ {
1342
+ table: "session_projects",
1343
+ keyColumns: ["session_id"],
1344
+ incomingIsNewer: "incoming.updated_at > existing.updated_at"
1345
+ },
1346
+ {
1347
+ table: "primer_candidates",
1348
+ keyColumns: [
1349
+ "project_path",
1350
+ "session_id",
1351
+ "source_start_message_id",
1352
+ "source_end_message_id"
1353
+ ],
1354
+ incomingIsNewer: "incoming.created_at > existing.created_at"
1355
+ },
1356
+ {
1357
+ table: "transform_decisions",
1358
+ keyColumns: ["session_id", "message_id"],
1359
+ incomingIsNewer: "incoming.ts_ms > existing.ts_ms"
1269
1360
  }
1270
- return cached.identity;
1271
- }
1272
- function formatDubiousOwnershipWarning(canonical) {
1273
- return `Magic Context: git refused to read ${canonical} (dubious ownership — the repo is owned by a different user). Memory features are paused unless a previous git identity is available. Fix: git config --global --add safe.directory ${canonical}`;
1274
- }
1275
- function recordDubiousOwnershipFallback(canonical) {
1276
- dubiousOwnershipFallbackDirectories.add(canonical);
1277
- if (dubiousOwnershipLoggedDirectories.has(canonical))
1278
- return;
1279
- dubiousOwnershipLoggedDirectories.add(canonical);
1280
- log(`[magic-context] ${formatDubiousOwnershipWarning(canonical)}`);
1281
- }
1282
- function takeDubiousOwnershipProjectIdentityWarning(directory) {
1283
- const canonical = path3.resolve(directory);
1284
- if (!dubiousOwnershipFallbackDirectories.has(canonical))
1285
- return null;
1286
- if (dubiousOwnershipWarnedDirectories.has(canonical))
1287
- return null;
1288
- dubiousOwnershipWarnedDirectories.add(canonical);
1289
- return formatDubiousOwnershipWarning(canonical);
1361
+ ];
1362
+ function tableExists2(db, name) {
1363
+ return Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(name));
1290
1364
  }
1291
- function canonicalUserHomeDirectory() {
1292
- const homeDirectory = userHomeDirectoryForIdentity();
1293
- try {
1294
- return realpathSync2.native(homeDirectory);
1295
- } catch {
1296
- return homeDirectory;
1297
- }
1365
+ function columnNames(db, table) {
1366
+ return new Set(db.prepare(`PRAGMA table_info(${table})`).all().map((column) => column.name));
1298
1367
  }
1299
- function isUserHomeDirectory(directory) {
1300
- if (projectDirectoryKey(directory) === projectDirectoryKey(userHomeDirectoryForIdentity()))
1301
- return true;
1302
- try {
1303
- return projectDirectoryKey(realpathSync2.native(path3.resolve(directory))) === projectDirectoryKey(canonicalUserHomeDirectory());
1304
- } catch {
1305
- return false;
1368
+ function listSessionScopedHarnessTables(db, tables) {
1369
+ const shapes = [];
1370
+ for (const table of tables) {
1371
+ if (!tableExists2(db, table))
1372
+ continue;
1373
+ const columns = columnNames(db, table);
1374
+ if (!columns.has("harness"))
1375
+ continue;
1376
+ shapes.push({ table, hasSessionId: columns.has("session_id") });
1306
1377
  }
1378
+ return shapes;
1307
1379
  }
1308
- function assertProjectAllowed(directory, allowHomeProject) {
1309
- const canonical = path3.resolve(directory);
1310
- if (!allowHomeProject && (isUserHomeDirectory(directory) || isUserHomeDirectory(gitRootDirectory(canonical) ?? canonical))) {
1311
- throw new ProjectIdentityError("home_project_disabled", directory, "Home project memory is disabled; set allow_home_project to opt in");
1380
+ function collectCandidateSessions(db, shapes) {
1381
+ const sessions = new Set;
1382
+ for (const shape of shapes) {
1383
+ if (!shape.hasSessionId)
1384
+ continue;
1385
+ const rows = db.prepare(`SELECT DISTINCT session_id AS id FROM ${shape.table}
1386
+ WHERE session_id IS NOT NULL AND harness IN ('opencode', 'opencode2')`).all();
1387
+ for (const row of rows)
1388
+ sessions.add(row.id);
1312
1389
  }
1390
+ return [...sessions].sort();
1313
1391
  }
1314
- function resolveProjectIdentity(directory, allowHomeProject = homeProjectPermission) {
1315
- assertProjectAllowed(directory, allowHomeProject);
1316
- const canonical = path3.resolve(directory);
1317
- const cachedFallback = directoryFallbackCache.get(canonical);
1318
- if (cachedFallback !== undefined) {
1319
- if (!hasGitDir(canonical)) {
1320
- return cachedFallback;
1392
+ function openEvidenceStore(path) {
1393
+ const store = new Database(path, { readonly: true, fileMustExist: true });
1394
+ try {
1395
+ const hasV1Messages = tableExists2(store, "message");
1396
+ const hasV1Session = tableExists2(store, "session");
1397
+ const hasV2 = tableExists2(store, "session_v2");
1398
+ const hasV2Messages = hasV2 && tableExists2(store, "session_message");
1399
+ if (!hasV1Messages && !hasV1Session && !hasV2) {
1400
+ store.close();
1401
+ return null;
1321
1402
  }
1322
- directoryFallbackCache.delete(canonical);
1323
- }
1324
- if (getActiveCooldown(canonical) !== undefined) {
1325
- if (hasGitDir(canonical)) {
1326
- const cachedGitIdentity = reuseLastKnownGitIdentity(canonical);
1327
- if (cachedGitIdentity !== undefined) {
1328
- return cachedGitIdentity;
1403
+ const v1Message = hasV1Messages ? store.prepare("SELECT MAX(time_updated) AS newest FROM message WHERE session_id = ?") : null;
1404
+ const v1Session = hasV1Session ? store.prepare("SELECT time_updated AS newest FROM session WHERE id = ?") : null;
1405
+ const v2Message = hasV2Messages ? store.prepare("SELECT MAX(time_updated) AS newest FROM session_message WHERE session_id = ?") : null;
1406
+ const v2Session = hasV2 ? store.prepare("SELECT time_updated AS newest FROM session_v2 WHERE id = ?") : null;
1407
+ const newest = (statements, sessionId) => {
1408
+ let best = null;
1409
+ for (const statement of statements) {
1410
+ if (!statement)
1411
+ continue;
1412
+ const row = statement.get(sessionId);
1413
+ const value = row?.newest;
1414
+ if (typeof value !== "number")
1415
+ continue;
1416
+ if (best === null || value > best)
1417
+ best = value;
1329
1418
  }
1330
- throw new ProjectIdentityError("git_identity_unavailable", directory, "Git identity unavailable; memory features paused until the retry cooldown expires");
1331
- }
1332
- return rememberedIdentityOrDirectoryFallback(canonical);
1333
- }
1334
- try {
1335
- return resolveProjectIdentityStrict(directory, allowHomeProject);
1419
+ return best;
1420
+ };
1421
+ return {
1422
+ newestV1Activity: (sessionId) => newest([v1Message, v1Session], sessionId),
1423
+ newestV2Activity: (sessionId) => newest([v2Message, v2Session], sessionId),
1424
+ close: () => store.close()
1425
+ };
1336
1426
  } catch (error) {
1337
- if (error instanceof ProjectIdentityError && shouldUseDirectoryFallback(error)) {
1338
- if (error.errorClass === "no_commits")
1339
- return directoryFallback(canonical);
1340
- if (!hasGitDir(canonical)) {
1341
- if (error.errorClass === "permission_denied")
1342
- throw error;
1343
- const resolved = rememberedIdentityOrDirectoryFallback(canonical);
1344
- directoryFallbackCache.set(canonical, resolved);
1345
- transientFailureCooldown.delete(canonical);
1346
- return resolved;
1347
- }
1348
- transientFailureCooldown.set(canonical, nowMs() + TRANSIENT_FAILURE_COOLDOWN_MS);
1349
- const cachedGitIdentity = reuseLastKnownGitIdentity(canonical);
1350
- if (error.errorClass === "dubious_ownership")
1351
- recordDubiousOwnershipFallback(canonical);
1352
- if (cachedGitIdentity !== undefined) {
1353
- return cachedGitIdentity;
1354
- }
1355
- throw error;
1356
- }
1427
+ store.close();
1357
1428
  throw error;
1358
1429
  }
1359
1430
  }
1360
- function resolveProjectIdentityOrFallback(directory, allowHomeProject = homeProjectPermission) {
1361
- try {
1362
- return resolveProjectIdentity(directory, allowHomeProject);
1363
- } catch (error) {
1364
- const canonical = path3.resolve(directory);
1365
- if (hasGitDir(canonical) || error instanceof ProjectIdentityError && error.errorClass === "home_project_disabled")
1366
- throw error;
1367
- const fallback = rememberedIdentityOrDirectoryFallback(canonical);
1368
- const message = error instanceof Error ? error.message : String(error);
1369
- log(`[magic-context] project identity resolution failed for ${canonical}; using directory fallback ${fallback}: ${message}`);
1370
- return fallback;
1371
- }
1372
- }
1373
- function hasGitDir(canonical) {
1374
- if (hasGitDirInAncestorChain(canonical)) {
1375
- return true;
1376
- }
1377
- try {
1378
- const realCanonical = realpathSync2.native(canonical);
1379
- return realCanonical !== canonical && hasGitDirInAncestorChain(realCanonical);
1380
- } catch {
1381
- return false;
1382
- }
1383
- }
1384
- function gitRootInAncestorChain(startDirectory) {
1385
- let current = startDirectory;
1386
- while (true) {
1387
- if (existsSync(path3.join(current, ".git"))) {
1388
- try {
1389
- return realpathSync2.native(current);
1390
- } catch {
1391
- return path3.resolve(current);
1392
- }
1393
- }
1394
- const parent = path3.dirname(current);
1395
- if (parent === current) {
1396
- return null;
1397
- }
1398
- current = parent;
1399
- }
1400
- }
1401
- function hasGitDirInAncestorChain(startDirectory) {
1402
- return gitRootInAncestorChain(startDirectory) !== null;
1403
- }
1404
- function gitRootDirectory(canonical) {
1405
- const direct = gitRootInAncestorChain(canonical);
1406
- if (direct)
1407
- return direct;
1408
- try {
1409
- const realCanonical = realpathSync2.native(canonical);
1410
- return realCanonical === canonical ? null : gitRootInAncestorChain(realCanonical);
1411
- } catch {
1431
+ function resolveHarnessFromEvidence(evidence, sessionId) {
1432
+ const v1 = evidence.newestV1Activity(sessionId);
1433
+ const v2 = evidence.newestV2Activity(sessionId);
1434
+ if (v1 === null && v2 === null)
1412
1435
  return null;
1413
- }
1414
- }
1415
- function directoryHasGitMetadata(directory) {
1416
- return gitRootDirectory(path3.resolve(directory)) !== null;
1436
+ if (v2 !== null && (v1 === null || v2 > v1))
1437
+ return "opencode2";
1438
+ if (v1 !== null && (v2 === null || v1 > v2))
1439
+ return "opencode";
1440
+ return null;
1417
1441
  }
1418
- function describeUnresolvedProjectIdentity(directory) {
1419
- const resolvedDirectory = path3.resolve(directory);
1420
- const canonicalHome = canonicalUserHomeDirectory();
1421
- const canonicalDirectory = (() => {
1422
- try {
1423
- return realpathSync2.native(resolvedDirectory);
1424
- } catch {
1425
- return resolvedDirectory;
1442
+ function prepareTwinResolver(db, table, keyColumns, incomingIsNewer) {
1443
+ const joinOn = keyColumns.map((column) => `incoming.${column} = existing.${column}`).join(" AND ");
1444
+ const dropIncoming = db.prepare(`DELETE FROM ${table} WHERE rowid IN (
1445
+ SELECT incoming.rowid FROM ${table} AS incoming
1446
+ JOIN ${table} AS existing ON ${joinOn} AND existing.harness = ?
1447
+ WHERE incoming.session_id = ? AND incoming.harness = ?
1448
+ AND NOT (${incomingIsNewer})
1449
+ )`);
1450
+ const dropExisting = db.prepare(`DELETE FROM ${table} WHERE rowid IN (
1451
+ SELECT existing.rowid FROM ${table} AS existing
1452
+ JOIN ${table} AS incoming ON ${joinOn} AND incoming.harness = ?
1453
+ WHERE existing.session_id = ? AND existing.harness = ?
1454
+ AND (${incomingIsNewer})
1455
+ )`);
1456
+ return {
1457
+ run(sessionId, target, source) {
1458
+ dropIncoming.run(target, sessionId, source);
1459
+ dropExisting.run(source, sessionId, target);
1426
1460
  }
1427
- })();
1428
- if (isUserHomeDirectory(directory) || gitRootDirectory(canonicalDirectory) === canonicalHome) {
1429
- return `this session runs in your home directory (${canonicalHome}), which Magic Context does not treat as a project. ` + "Start OpenCode inside a project folder, or set `allow_home_project: true` in the user-level " + "magic-context.jsonc (~/.config/cortexkit/) to give home sessions their own memory.";
1430
- }
1431
- if (hasGitDir(resolvedDirectory))
1432
- return `git identity resolution for ${resolvedDirectory} is temporarily unavailable. Memory features are paused; retry after git access recovers.`;
1433
- return `the session directory ${resolvedDirectory} could not be read as a project.`;
1461
+ };
1434
1462
  }
1435
- function isUsableProjectIdentity(identity) {
1436
- return typeof identity === "string" && identity.trim().length > 0;
1463
+ function writeUnresolvedState(db, reason, resolution, sessionIds) {
1464
+ if (!tableExists2(db, "schema_migrations_meta"))
1465
+ return;
1466
+ const value = {
1467
+ reason,
1468
+ lookedFor: getOpenCodeDbProbeDescriptions(resolution).join(", "),
1469
+ sessionIds: [...sessionIds],
1470
+ recordedAt: Date.now()
1471
+ };
1472
+ db.prepare(`INSERT INTO schema_migrations_meta (key, value) VALUES (?, ?)
1473
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(OPENCODE2_RELABEL_STATE_KEY, JSON.stringify(value));
1437
1474
  }
1438
- function resolveProjectIdentityForSession(directory, allowHomeProject = homeProjectPermission) {
1439
- const resolvedDirectory = path3.resolve(directory);
1440
- const cacheKey = `${allowHomeProject ? "1" : "0"}\x00${resolvedDirectory}`;
1441
- const cached = sessionIdentityCache.get(cacheKey);
1442
- if (cached && (cached.revalidateAt === null || nowMs() < cached.revalidateAt) && !(cached.identity?.startsWith("dir:") && hasGitDir(resolvedDirectory))) {
1443
- return cached.identity;
1475
+ function clearUnresolvedState(db) {
1476
+ if (!tableExists2(db, "schema_migrations_meta"))
1477
+ return;
1478
+ db.prepare("DELETE FROM schema_migrations_meta WHERE key = ?").run(OPENCODE2_RELABEL_STATE_KEY);
1479
+ }
1480
+ function repairOpenCode2HarnessLabels(db, options) {
1481
+ const shapes = listSessionScopedHarnessTables(db, options.tables);
1482
+ const candidateSessionIds = collectCandidateSessions(db, shapes);
1483
+ if (candidateSessionIds.length === 0) {
1484
+ clearUnresolvedState(db);
1485
+ return {
1486
+ status: "no_candidates",
1487
+ storePath: null,
1488
+ candidateSessionIds,
1489
+ relabelledSessions: []
1490
+ };
1444
1491
  }
1445
- sessionIdentityCache.delete(cacheKey);
1446
- filesystemProbeObserverForTests?.();
1447
- const canonicalDirectory = (() => {
1448
- try {
1449
- filesystemProbeObserverForTests?.();
1450
- return realpathSync2.native(resolvedDirectory);
1451
- } catch {
1452
- return resolvedDirectory;
1453
- }
1454
- })();
1455
- filesystemProbeObserverForTests?.();
1456
- const homeRoot = gitRootDirectory(canonicalDirectory);
1457
- const inheritsHomeRepository = homeRoot !== null && isUserHomeDirectory(homeRoot);
1458
- let identity;
1459
- if (!allowHomeProject && (isUserHomeDirectory(directory) || inheritsHomeRepository)) {
1460
- identity = undefined;
1492
+ const resolution = resolveOpenCodeDbPath("v1", { env: options.env });
1493
+ let evidence = null;
1494
+ let reason = null;
1495
+ if (!openCodeDbPathExists(resolution)) {
1496
+ reason = "store_not_found";
1461
1497
  } else {
1462
1498
  try {
1463
- identity = resolveProjectIdentityOrFallback(isUserHomeDirectory(directory) ? canonicalUserHomeDirectory() : directory, allowHomeProject);
1499
+ evidence = openEvidenceStore(resolution.path);
1500
+ if (!evidence)
1501
+ reason = "store_schema_unknown";
1464
1502
  } catch (error) {
1465
- if (!(error instanceof ProjectIdentityError))
1466
- throw error;
1467
- const reason = error.errorClass === "git_identity_unavailable" ? pausedIdentityReasons.get(resolvedDirectory) ?? error.errorClass : error.errorClass;
1468
- pausedIdentityReasons.set(resolvedDirectory, reason);
1469
- const logKey = `${resolvedDirectory}\x00${reason}`;
1470
- if (!pausedIdentityLogged.has(logKey)) {
1471
- pausedIdentityLogged.add(logKey);
1472
- log(`[magic-context] memory features paused for ${resolvedDirectory}: ${reason}`);
1473
- }
1474
- identity = undefined;
1503
+ reason = "store_unreadable";
1504
+ log(`[migration] OpenCode session database at ${resolution.path} could not be read: ${error instanceof Error ? error.message : String(error)}`);
1475
1505
  }
1476
1506
  }
1477
- if (identity !== undefined)
1478
- pausedIdentityReasons.delete(resolvedDirectory);
1479
- sessionIdentityCache.set(cacheKey, {
1480
- identity,
1481
- revalidateAt: identity?.startsWith("git:") === true ? null : nowMs() + TRANSIENT_FAILURE_COOLDOWN_MS
1482
- });
1483
- return identity;
1484
- }
1485
- function normalizeStoredProjectPath(rawOrStored) {
1486
- if (rawOrStored.startsWith("git:") || rawOrStored.startsWith("dir:")) {
1487
- return rawOrStored;
1507
+ if (!evidence || reason !== null) {
1508
+ evidence?.close();
1509
+ writeUnresolvedState(db, reason ?? "store_not_found", resolution, candidateSessionIds);
1510
+ log(`[migration] OpenCode harness labels left unverified for ${candidateSessionIds.length} session(s): ` + `no readable OpenCode session database (${reason ?? "store_not_found"}) at ${resolution.path}. ` + "Nothing was relabelled; `magic-context doctor` reports the affected sessions.");
1511
+ return {
1512
+ status: "unresolved",
1513
+ reason: reason ?? "store_not_found",
1514
+ storePath: null,
1515
+ candidateSessionIds,
1516
+ relabelledSessions: []
1517
+ };
1488
1518
  }
1519
+ const relabelledSessions = [];
1489
1520
  try {
1490
- return resolveProjectIdentity(rawOrStored);
1491
- } catch {
1492
- return rawOrStored;
1521
+ const updates = shapes.filter((shape) => shape.hasSessionId).map((shape) => db.prepare(`UPDATE ${shape.table} SET harness = ? WHERE session_id = ? AND harness = ?`));
1522
+ const presentTables = new Set(shapes.map((shape) => shape.table));
1523
+ const twinResolvers = V87_HARNESS_TWIN_RULES.filter((rule) => presentTables.has(rule.table)).map((rule) => prepareTwinResolver(db, rule.table, rule.keyColumns, rule.incomingIsNewer));
1524
+ for (const sessionId of candidateSessionIds) {
1525
+ const target = resolveHarnessFromEvidence(evidence, sessionId);
1526
+ if (target === null)
1527
+ continue;
1528
+ const source = target === "opencode" ? "opencode2" : "opencode";
1529
+ for (const resolver of twinResolvers)
1530
+ resolver.run(sessionId, target, source);
1531
+ let moved = 0;
1532
+ for (const update of updates) {
1533
+ moved += Number(update.run(target, sessionId, source).changes ?? 0);
1534
+ }
1535
+ if (moved > 0)
1536
+ relabelledSessions.push({ sessionId, harness: target });
1537
+ }
1538
+ } finally {
1539
+ evidence.close();
1540
+ }
1541
+ clearUnresolvedState(db);
1542
+ if (relabelledSessions.length > 0) {
1543
+ const toOpenCode2 = relabelledSessions.filter((entry) => entry.harness === "opencode2").length;
1544
+ log(`[migration] OpenCode harness labels repaired from ${resolution.path}: ` + `${toOpenCode2} session(s) restored to opencode2, ` + `${relabelledSessions.length - toOpenCode2} session(s) set to opencode.`);
1493
1545
  }
1546
+ return {
1547
+ status: "resolved",
1548
+ storePath: resolution.path,
1549
+ candidateSessionIds,
1550
+ relabelledSessions
1551
+ };
1494
1552
  }
1495
- function storedPathBelongsToIdentity(storedProjectPath, projectIdentity) {
1496
- return storedProjectPath === projectIdentity || normalizeStoredProjectPath(storedProjectPath) === projectIdentity;
1553
+
1554
+ // src/features/magic-context/storage-compartment-history-version.ts
1555
+ function definitions(db) {
1556
+ const hasPrivilegeTable = Boolean(db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='context_privilege_state'").get());
1557
+ const externalUpdate = hasPrivilegeTable ? "CASE WHEN COALESCE((SELECT enabled FROM context_privilege_state WHERE id=1),0)=0 THEN 1 ELSE 0 END" : "1";
1558
+ return [
1559
+ {
1560
+ type: "table",
1561
+ name: "compartment_history_versions",
1562
+ sql: `CREATE TABLE compartment_history_versions (
1563
+ session_id TEXT PRIMARY KEY NOT NULL,
1564
+ generation TEXT NOT NULL DEFAULT (lower(hex(randomblob(16)))),
1565
+ version INTEGER NOT NULL DEFAULT 0 CHECK (version >= 0),
1566
+ rewrite_version INTEGER NOT NULL DEFAULT 0 CHECK (rewrite_version >= 0),
1567
+ seeded INTEGER NOT NULL DEFAULT 0 CHECK (seeded IN (0, 1))
1568
+ )`
1569
+ },
1570
+ {
1571
+ type: "trigger",
1572
+ name: "compartment_history_ai",
1573
+ sql: `CREATE TRIGGER compartment_history_ai AFTER INSERT ON compartments BEGIN
1574
+ INSERT INTO compartment_history_versions(session_id, version) VALUES (NEW.session_id, 1)
1575
+ ON CONFLICT(session_id) DO UPDATE SET version = version + 1;
1576
+ END`
1577
+ },
1578
+ {
1579
+ type: "trigger",
1580
+ name: "compartment_history_ad",
1581
+ sql: `CREATE TRIGGER compartment_history_ad AFTER DELETE ON compartments BEGIN
1582
+ INSERT INTO compartment_history_versions(session_id, version) VALUES (OLD.session_id, 1)
1583
+ ON CONFLICT(session_id) DO UPDATE SET version = version + 1;
1584
+ END`
1585
+ },
1586
+ {
1587
+ type: "trigger",
1588
+ name: "compartment_history_au",
1589
+ sql: `CREATE TRIGGER compartment_history_au AFTER UPDATE ON compartments BEGIN
1590
+ INSERT INTO compartment_history_versions(session_id, version, rewrite_version) VALUES (OLD.session_id, 1, ${externalUpdate})
1591
+ ON CONFLICT(session_id) DO UPDATE SET version = version + 1, rewrite_version = rewrite_version + ${externalUpdate};
1592
+ INSERT INTO compartment_history_versions(session_id, version, rewrite_version)
1593
+ SELECT NEW.session_id, 1, ${externalUpdate} WHERE NEW.session_id != OLD.session_id
1594
+ ON CONFLICT(session_id) DO UPDATE SET version = version + 1, rewrite_version = rewrite_version + ${externalUpdate};
1595
+ END`
1596
+ }
1597
+ ];
1598
+ }
1599
+ function installedDefinitions(db) {
1600
+ const rows = db.prepare(`SELECT type, name, sql FROM sqlite_master
1601
+ WHERE name IN ('compartment_history_versions', 'compartment_history_ai', 'compartment_history_ad', 'compartment_history_au')`).all();
1602
+ return new Map(rows.map((row) => [`${row.type}:${row.name}`, row.sql]));
1603
+ }
1604
+ function matches(expected, installed) {
1605
+ return expected.every((definition) => installed.get(`${definition.type}:${definition.name}`) === definition.sql);
1606
+ }
1607
+ function installCompartmentHistoryVersions(db) {
1608
+ if (!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='compartments'").get())
1609
+ return;
1610
+ if (matches(definitions(db), installedDefinitions(db)))
1611
+ return;
1612
+ db.transaction(() => {
1613
+ const expected = definitions(db);
1614
+ const installed = installedDefinitions(db);
1615
+ if (matches(expected, installed))
1616
+ return;
1617
+ const table = expected[0];
1618
+ const existingTable = installed.get(`table:${table.name}`);
1619
+ if (existingTable !== undefined && existingTable !== table.sql) {
1620
+ throw new Error("compartment_history_versions schema differs from v93; refusing to replace revision data");
1621
+ }
1622
+ if (existingTable === undefined)
1623
+ db.exec(table.sql);
1624
+ db.exec(`INSERT OR IGNORE INTO compartment_history_versions(session_id, seeded)
1625
+ SELECT DISTINCT session_id, 1 FROM compartments`);
1626
+ for (const trigger of expected.slice(1)) {
1627
+ if (installed.get(`trigger:${trigger.name}`) === trigger.sql)
1628
+ continue;
1629
+ db.exec(`DROP TRIGGER IF EXISTS ${trigger.name}`);
1630
+ db.exec(trigger.sql);
1631
+ }
1632
+ }).immediate();
1497
1633
  }
1634
+
1498
1635
  // src/features/magic-context/workspaces.ts
1636
+ import { createHash as createHash4 } from "node:crypto";
1499
1637
  var VALID_SHARE_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
1500
1638
  var DEFAULT_WORKSPACE_SHARE_CATEGORIES = ["CONSTRAINTS"];
1501
1639
  function tableExists3(db, tableName) {
@@ -4105,6 +4243,13 @@ var MIGRATIONS = [
4105
4243
  splitLkgSlotPrefixes(db);
4106
4244
  splitReplayDecisions(db);
4107
4245
  }
4246
+ },
4247
+ {
4248
+ version: 95,
4249
+ description: "cover cleanup and retention reads, index git FTS rowids and store temporal replay decisions",
4250
+ up(db) {
4251
+ installV95PerfSchema(db, true);
4252
+ }
4108
4253
  }
4109
4254
  ];
4110
4255
  var LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
@@ -4132,6 +4277,9 @@ function hasPendingMigrations(db) {
4132
4277
  return MIGRATIONS.some((candidate) => candidate.version > currentVersion && !isMigrationApplied(db, candidate.version));
4133
4278
  }
4134
4279
  var mainThreadMigrationBodies = 0;
4280
+ function getMainThreadMigrationBodyCount() {
4281
+ return mainThreadMigrationBodies;
4282
+ }
4135
4283
  function isSiblingMigrationConflict(db, error, version) {
4136
4284
  if (!(error instanceof Error))
4137
4285
  return false;
@@ -4244,23 +4392,13 @@ async function runMigrationsWithRetry(db, options = {}) {
4244
4392
  }
4245
4393
  }
4246
4394
 
4247
- // src/features/magic-context/storage-db.ts
4248
- import {
4249
- chmodSync,
4250
- copyFileSync,
4251
- cpSync,
4252
- existsSync as existsSync3,
4253
- mkdirSync as mkdirSync2,
4254
- readdirSync,
4255
- readFileSync as readFileSync3,
4256
- rmdirSync,
4257
- statSync as statSync2,
4258
- unlinkSync
4259
- } from "node:fs";
4260
- import { basename, dirname, join as join2, resolve } from "node:path";
4261
-
4262
4395
  // src/plugin/boot-quiet.ts
4396
+ var BOOT_QUIET_MS = 120000;
4263
4397
  var bootQuietUntilMs = 0;
4398
+ function beginBootQuietPeriod(now = Date.now()) {
4399
+ if (bootQuietUntilMs === 0)
4400
+ bootQuietUntilMs = now + BOOT_QUIET_MS;
4401
+ }
4264
4402
  function bootQuietRemainingMs(now = Date.now()) {
4265
4403
  return Math.max(0, bootQuietUntilMs - now);
4266
4404
  }
@@ -4270,6 +4408,17 @@ function scheduleAfterBootQuiet(task, additionalDelayMs = 0) {
4270
4408
  return timer;
4271
4409
  }
4272
4410
 
4411
+ // src/features/magic-context/storage-db.ts
4412
+ import {
4413
+ existsSync as existsSync3,
4414
+ readdirSync,
4415
+ readFileSync as readFileSync3,
4416
+ rmdirSync,
4417
+ statSync as statSync2,
4418
+ unlinkSync
4419
+ } from "node:fs";
4420
+ import { basename, dirname, join as join2, resolve } from "node:path";
4421
+
4273
4422
  // src/shared/rpc-utils.ts
4274
4423
  import { execFile, execFileSync as execFileSync2 } from "node:child_process";
4275
4424
  import { createHash as createHash5 } from "node:crypto";
@@ -4995,8 +5144,20 @@ function inspectWindowsProcessesSync() {
4995
5144
  };
4996
5145
  }
4997
5146
 
5147
+ // src/shared/sqlite-context-pragmas.ts
5148
+ function configureContextDatabasePragmas(db, readonly = false) {
5149
+ const [row] = db.prepare(readonly ? "PRAGMA journal_mode" : "PRAGMA journal_mode=WAL").all();
5150
+ const mode = row?.journal_mode?.toLowerCase();
5151
+ if (mode === "memory" || readonly && mode !== "wal")
5152
+ return;
5153
+ if (mode !== "wal") {
5154
+ throw new Error(`context.db requires WAL before synchronous=NORMAL (got ${mode ?? "unknown"})`);
5155
+ }
5156
+ db.exec("PRAGMA synchronous=NORMAL");
5157
+ }
5158
+
4998
5159
  // src/features/magic-context/context-store-uuid.ts
4999
- import { randomUUID as randomUUID2 } from "node:crypto";
5160
+ import { randomUUID } from "node:crypto";
5000
5161
  function getContextStoreUuid(db) {
5001
5162
  const row = db.prepare("SELECT value FROM context_store_meta WHERE key = 'store_uuid'").get();
5002
5163
  return typeof row?.value === "string" && row.value.length > 0 ? row.value : null;
@@ -5005,7 +5166,7 @@ function ensureContextStoreUuid(db) {
5005
5166
  const existing = getContextStoreUuid(db);
5006
5167
  if (existing)
5007
5168
  return existing;
5008
- const minted = randomUUID2();
5169
+ const minted = randomUUID();
5009
5170
  withPrivilegedWriter(db, () => {
5010
5171
  db.transaction(() => {
5011
5172
  db.prepare("INSERT INTO context_store_meta(key, value) VALUES ('store_uuid', ?) ON CONFLICT(key) DO NOTHING").run(minted);
@@ -5014,210 +5175,6 @@ function ensureContextStoreUuid(db) {
5014
5175
  return getContextStoreUuid(db) ?? minted;
5015
5176
  }
5016
5177
 
5017
- // src/features/magic-context/fail-closed-block.ts
5018
- var FAIL_CLOSED_DOCTOR_COMMAND = "npx @cortexkit/magic-context@latest doctor";
5019
- function attachFailClosedBlockingProcessEvidence(process2, evidence) {
5020
- Object.defineProperties(process2, {
5021
- startTime: { configurable: true, value: evidence.startTime },
5022
- commandLine: { configurable: true, value: evidence.commandLine }
5023
- });
5024
- return process2;
5025
- }
5026
-
5027
- class FailClosedBlockingError extends Error {
5028
- code = "FAIL_CLOSED_BLOCKING";
5029
- reason;
5030
- constructor(message, reason, options) {
5031
- super(message, options);
5032
- this.name = "FailClosedBlockingError";
5033
- this.reason = reason;
5034
- }
5035
- }
5036
- var OPENCODE_INTERNAL_AGENT_NAMES = new Set(["title", "summary", "compaction"]);
5037
- function isMagicContextHiddenAgentName(agent) {
5038
- if (agent === "smart-note-compiler" || agent.startsWith("smart-note-")) {
5039
- return true;
5040
- }
5041
- if (agent === "historian" || agent.startsWith("historian-"))
5042
- return true;
5043
- if (agent === "dreamer" || agent.startsWith("dreamer-"))
5044
- return true;
5045
- return false;
5046
- }
5047
- var MAX_FORMATTED_BLOCKING_PROCESSES = 8;
5048
- var MAX_FORMATTED_COMMAND_LINE_LENGTH = 80;
5049
- function normalizeFailClosedProcessKind(process2) {
5050
- switch (process2.kind) {
5051
- case "OpenCode server":
5052
- case "OpenCode instance (TUI/CLI)":
5053
- case "Pi":
5054
- case "process":
5055
- return process2.kind;
5056
- }
5057
- switch (process2.harness?.trim().toLowerCase()) {
5058
- case "opencode server":
5059
- return "OpenCode server";
5060
- case "opencode instance (tui/cli)":
5061
- case "opencode instance":
5062
- return "OpenCode instance (TUI/CLI)";
5063
- case "pi":
5064
- case "pi harness":
5065
- case "omp":
5066
- return "Pi";
5067
- default:
5068
- return "process";
5069
- }
5070
- }
5071
- function formatFailClosedProcessStartTime(startTime) {
5072
- if (!Number.isFinite(startTime) || startTime <= 0)
5073
- return "unverified";
5074
- try {
5075
- const parts = new Intl.DateTimeFormat("en-US", {
5076
- day: "numeric",
5077
- hour: "2-digit",
5078
- minute: "2-digit",
5079
- month: "short",
5080
- hour12: false
5081
- }).formatToParts(new Date(startTime));
5082
- const values = Object.fromEntries(parts.map(({ type, value }) => [type, value]));
5083
- if (!values.month || !values.day || !values.hour || !values.minute)
5084
- return "unverified";
5085
- return `${values.month} ${values.day} ${values.hour}:${values.minute}`;
5086
- } catch {
5087
- return "unverified";
5088
- }
5089
- }
5090
- function formatFailClosedCommandLine(commandLine) {
5091
- if (typeof commandLine !== "string" || commandLine.trim().length === 0)
5092
- return "unverified";
5093
- try {
5094
- const normalized = commandLine.replaceAll("\x00", " ").replace(/\s+/g, " ").trim();
5095
- if (normalized.length === 0)
5096
- return "unverified";
5097
- const sanitized = sanitizeDiagnosticText(normalized).trim();
5098
- if (sanitized.length === 0)
5099
- return "unverified";
5100
- return sanitized.length > MAX_FORMATTED_COMMAND_LINE_LENGTH ? `${sanitized.slice(0, MAX_FORMATTED_COMMAND_LINE_LENGTH - 3)}...` : sanitized;
5101
- } catch {
5102
- return "unverified";
5103
- }
5104
- }
5105
- function formatFailClosedBlockingProcesses(processes) {
5106
- const uniqueProcesses = new Map;
5107
- for (const process2 of processes) {
5108
- if (!Number.isInteger(process2.pid) || process2.pid <= 0)
5109
- continue;
5110
- const candidate = {
5111
- kind: normalizeFailClosedProcessKind(process2),
5112
- pid: process2.pid,
5113
- startTime: process2.startTime,
5114
- commandLine: process2.commandLine
5115
- };
5116
- const previous = uniqueProcesses.get(process2.pid);
5117
- if (!previous || previous.kind === "process" && candidate.kind !== "process") {
5118
- uniqueProcesses.set(process2.pid, candidate);
5119
- } else {
5120
- uniqueProcesses.set(process2.pid, {
5121
- ...previous,
5122
- startTime: previous.startTime ?? candidate.startTime,
5123
- commandLine: previous.commandLine ?? candidate.commandLine
5124
- });
5125
- }
5126
- }
5127
- const entries = [...uniqueProcesses.values()];
5128
- const visible = entries.slice(0, MAX_FORMATTED_BLOCKING_PROCESSES);
5129
- const rendered = visible.map(({ kind, pid }) => `${kind} (PID ${pid})`);
5130
- const omitted = entries.length - visible.length;
5131
- if (omitted > 0)
5132
- rendered.push(`${omitted} more blocking process(es)`);
5133
- if (rendered.length === 0)
5134
- return "a live process";
5135
- const summary = rendered.length === 1 ? rendered[0] : `${rendered.slice(0, -1).join(", ")}, and ${rendered.at(-1)}`;
5136
- const evidence = entries.map(({ kind, pid, startTime, commandLine }) => `- PID ${pid}: ${kind}, started ${formatFailClosedProcessStartTime(startTime)}, cmd: ${formatFailClosedCommandLine(commandLine)}`).join(`
5137
- `);
5138
- return `${summary}
5139
- Blocking process evidence:
5140
- ${evidence}`;
5141
- }
5142
- function formatFailClosedBlockingMessage(reason) {
5143
- if (reason.kind === "migration_guard") {
5144
- if (reason.unreadableFile) {
5145
- const arm = reason.unreadableArm ?? "io";
5146
- const recovery = arm === "io" ? `If none of these processes are running, it is safe to delete ${reason.unreadableFile} and retry.` : `The file may be a recent incomplete write; retry after the file is older than the ten-minute grace window, or stop the relevant process before deleting it.`;
5147
- return [
5148
- `Magic Context cannot migrate the shared database because RPC discovery file ${reason.unreadableFile} is uncertain (${arm} arm), so the absence of a live process cannot be proven.`,
5149
- recovery,
5150
- `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
5151
- ].join(" ");
5152
- }
5153
- return [
5154
- `Magic Context cannot migrate the shared database (one database serves every project on this machine) because ${formatFailClosedBlockingProcesses(reason.blockingProcesses)} may be running an older Magic Context build that would fail against the migrated database.`,
5155
- `The database is at upstream migration v${reason.persistedVersion}; this build needs v${reason.supportedVersion}.`,
5156
- "Restart the blocking process — even one from a different project — and it will pick up the new build and migrate on start; or shut it down and retry.",
5157
- "If that host loads a pinned or cached older Magic Context build, a restart alone is not enough: stop it or update its Magic Context build, then start this host once so it migrates the database with nothing older attached.",
5158
- `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
5159
- ].join(" ");
5160
- }
5161
- if (reason.kind === "schema_fence") {
5162
- return [
5163
- `Magic Context cannot operate: this Magic Context build is older than the database; upgrade/restart this harness (upstream migration lane v${reason.persistedVersion}, build supports through v${reason.supportedVersion}).`,
5164
- `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
5165
- ].join(" ");
5166
- }
5167
- const cause = reason.cause.trim().length > 0 ? reason.cause.trim() : "unknown storage error";
5168
- return [
5169
- `Magic Context cannot operate: persistent storage failed (${cause}).`,
5170
- "The plugin will not silently degrade to native compaction while enabled.",
5171
- `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
5172
- ].join(" ");
5173
- }
5174
- function formatFailClosedBlockingSummary(reason) {
5175
- if (reason.kind === "migration_guard") {
5176
- if (reason.unreadableFile) {
5177
- return `Magic Context cannot migrate the shared database from v${reason.persistedVersion} to v${reason.supportedVersion}: RPC discovery file ${reason.unreadableFile} could not be read, so an older host may still be attached.`;
5178
- }
5179
- const blockers = reason.blockingProcesses.filter((process2) => Number.isInteger(process2.pid) && process2.pid > 0).map((process2) => `${normalizeFailClosedProcessKind(process2)} (PID ${process2.pid})`);
5180
- const named = blockers.length > 0 ? blockers.join(", ") : "a live process";
5181
- return `Magic Context cannot migrate the shared database from v${reason.persistedVersion} to v${reason.supportedVersion} while ${named} may still run an older Magic Context build. Stop or update that host, then start this one once.`;
5182
- }
5183
- if (reason.kind === "schema_fence") {
5184
- return `Magic Context cannot operate: the shared database is at v${reason.persistedVersion}, newer than this build supports (v${reason.supportedVersion}). Update and restart this host.`;
5185
- }
5186
- const cause = reason.cause.trim().length > 0 ? reason.cause.trim() : "unknown storage error";
5187
- return `Magic Context cannot operate: persistent storage failed (${cause}).`;
5188
- }
5189
- function createFailClosedBlockingError(reason, options) {
5190
- return new FailClosedBlockingError(formatFailClosedBlockingMessage(reason), reason, options);
5191
- }
5192
- function isFailClosedBlockingError(error) {
5193
- return error instanceof FailClosedBlockingError || typeof error === "object" && error !== null && error.name === "FailClosedBlockingError" && error.code === "FAIL_CLOSED_BLOCKING";
5194
- }
5195
- function shouldBypassFailClosedBlock(input) {
5196
- if (input.isPiSubagentEnv === true)
5197
- return true;
5198
- if (input.isInternalChildSession === true)
5199
- return true;
5200
- const agent = typeof input.agent === "string" ? input.agent.trim() : "";
5201
- if (agent.length === 0)
5202
- return false;
5203
- if (OPENCODE_INTERNAL_AGENT_NAMES.has(agent))
5204
- return true;
5205
- if (isMagicContextHiddenAgentName(agent))
5206
- return true;
5207
- return false;
5208
- }
5209
- function resolveAgentNameFromMessages(messages) {
5210
- for (let i = messages.length - 1;i >= 0; i -= 1) {
5211
- const info = messages[i]?.info;
5212
- if (!info || typeof info !== "object")
5213
- continue;
5214
- const agent = info.agent;
5215
- if (typeof agent === "string" && agent.length > 0)
5216
- return agent;
5217
- }
5218
- return;
5219
- }
5220
-
5221
5178
  // src/features/magic-context/message-fts-rowid-map.ts
5222
5179
  import { createHash as createHash6 } from "node:crypto";
5223
5180
  var MESSAGE_FTS_ROWID_MAP_BACKFILL_BATCH_SIZE = 100;
@@ -5452,6 +5409,9 @@ function startBootDeadline(timeoutMs, onDeferred) {
5452
5409
  }
5453
5410
 
5454
5411
  // src/features/magic-context/migration-worker-client.ts
5412
+ function workerStartError(detail) {
5413
+ return new Error(`the migration worker could not start (${detail}); reinstall or rebuild the plugin`);
5414
+ }
5455
5415
  function defaultWorkerEntry() {
5456
5416
  return new URL(new URL(import.meta.url).pathname.endsWith(".ts") ? "./migration-worker.ts" : "./migration-worker.js", import.meta.url);
5457
5417
  }
@@ -5462,8 +5422,7 @@ function runMigrationsOffThread(data) {
5462
5422
  try {
5463
5423
  worker = new Worker(workerEntryOverride ?? defaultWorkerEntry(), { workerData: data });
5464
5424
  } catch (error) {
5465
- log(`[migrations] could not start the migration worker (${error instanceof Error ? error.message : String(error)}); applying migrations on the main thread`);
5466
- resolve("worker_unavailable");
5425
+ reject(workerStartError(error instanceof Error ? error.message : String(error)));
5467
5426
  return;
5468
5427
  }
5469
5428
  log(`[migrations] applying pending migrations on a worker thread: ${data.dbPath}`);
@@ -5487,14 +5446,14 @@ function runMigrationsOffThread(data) {
5487
5446
  };
5488
5447
  const unavailable = (detail) => {
5489
5448
  settle(() => {
5490
- log(`[migrations] the migration worker did not start (${detail}); applying migrations on the main thread`);
5491
- resolve("worker_unavailable");
5449
+ reject(workerStartError(detail));
5492
5450
  });
5493
5451
  };
5494
5452
  worker.on("message", (message) => {
5495
5453
  switch (message.type) {
5496
5454
  case "ready":
5497
5455
  ready = true;
5456
+ log(`[migrations] migration worker ready: ${data.dbPath}`);
5498
5457
  return;
5499
5458
  case "log":
5500
5459
  writeForwardedLogLine(message.line);
@@ -5504,9 +5463,17 @@ function runMigrationsOffThread(data) {
5504
5463
  setBusy(!message.waiting);
5505
5464
  return;
5506
5465
  case "done":
5466
+ if (!ready) {
5467
+ unavailable("reported completion before ready");
5468
+ return;
5469
+ }
5507
5470
  settle(() => resolve("migrated"));
5508
5471
  return;
5509
5472
  case "failed":
5473
+ if (!ready) {
5474
+ unavailable(message.message);
5475
+ return;
5476
+ }
5510
5477
  settle(() => reject(new Error(message.message)));
5511
5478
  return;
5512
5479
  }
@@ -5808,38 +5775,14 @@ function getSchemaFenceRejection() {
5808
5775
  function getMigrationOnOpenRefusal() {
5809
5776
  return lastMigrationOnOpenRefusal;
5810
5777
  }
5811
- var LATEST_SUPPORTED_VERSION = 94;
5778
+ var LATEST_SUPPORTED_VERSION = 95;
5812
5779
  var BOOT_SQLITE_BUSY_TIMEOUT_MS = 5000;
5813
- var PERMISSIONS_ENFORCEABLE = process.platform !== "win32";
5814
- var defaultStoragePermissionFs = { chmodSync, mkdirSync: mkdirSync2 };
5815
- var storagePermissionFs = defaultStoragePermissionFs;
5816
5780
  function ensureSecureStorageDir(dir) {
5817
- if (!shouldEnforcePrivateStoragePermissions()) {
5818
- storagePermissionFs.mkdirSync(dir, { recursive: true });
5819
- return;
5820
- }
5821
- storagePermissionFs.mkdirSync(dir, { recursive: true, mode: 448 });
5822
- if (!PERMISSIONS_ENFORCEABLE)
5823
- return;
5824
- try {
5825
- storagePermissionFs.chmodSync(dir, 448);
5826
- } catch (error) {
5827
- log(`[magic-context] could not restrict storage dir permissions on ${dir}: ${getErrorMessage(error)}`);
5828
- }
5781
+ ensureStorageDirectorySync(dir);
5829
5782
  }
5830
- function restrictDatabaseFilePermissions(dbPath) {
5831
- if (!PERMISSIONS_ENFORCEABLE || !shouldEnforcePrivateStoragePermissions())
5832
- return;
5833
- for (const suffix of ["", "-wal", "-shm"]) {
5834
- const file = `${dbPath}${suffix}`;
5835
- if (!existsSync3(file))
5836
- continue;
5837
- try {
5838
- storagePermissionFs.chmodSync(file, 384);
5839
- } catch (error) {
5840
- log(`[magic-context] could not restrict DB file permissions on ${file}: ${getErrorMessage(error)}`);
5841
- }
5842
- }
5783
+ function tightenStorageTree(dir) {
5784
+ const { failures } = tightenStorageTreeSync(dir);
5785
+ log(`[magic-context] storage permission tightening failures=${failures}`);
5843
5786
  }
5844
5787
  function resolveBootBusyTimeoutMs(value) {
5845
5788
  if (value === undefined)
@@ -5875,6 +5818,7 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
5875
5818
  try {
5876
5819
  const legacyDb = new Database(legacyDbPath);
5877
5820
  try {
5821
+ configureContextDatabasePragmas(legacyDb, true);
5878
5822
  legacyDb.exec("PRAGMA wal_checkpoint(TRUNCATE)");
5879
5823
  } finally {
5880
5824
  closeQuietly(legacyDb);
@@ -5887,7 +5831,7 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
5887
5831
  const dst = join2(targetDbDir, `context.db${suffix}`);
5888
5832
  if (existsSync3(src)) {
5889
5833
  try {
5890
- copyFileSync(src, dst);
5834
+ copyStorageFileSync(src, dst);
5891
5835
  } catch (error) {
5892
5836
  log(`[magic-context] failed to copy ${src}:`, getErrorMessage(error));
5893
5837
  }
@@ -5897,7 +5841,7 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
5897
5841
  const targetModelsDir = join2(targetDbDir, "models");
5898
5842
  if (existsSync3(legacyModelsDir) && !existsSync3(targetModelsDir)) {
5899
5843
  try {
5900
- cpSync(legacyModelsDir, targetModelsDir, { recursive: true });
5844
+ copyStorageTreeSync(legacyModelsDir, targetModelsDir);
5901
5845
  } catch (error) {
5902
5846
  log("[magic-context] failed to copy embedding model cache:", getErrorMessage(error));
5903
5847
  }
@@ -6236,6 +6180,12 @@ function applySqliteTuningPragmas(db) {
6236
6180
  db.exec(`PRAGMA mmap_size=${Math.round(sqlitePragmaConfig.mmapSizeMb * 1024 * 1024)}`);
6237
6181
  db.exec("PRAGMA analysis_limit=400");
6238
6182
  }
6183
+ function runSqliteOptimize(db) {
6184
+ try {
6185
+ db.exec("PRAGMA analysis_limit=400");
6186
+ db.exec("PRAGMA optimize");
6187
+ } catch {}
6188
+ }
6239
6189
  function finishDatabaseOpen(db, dbPath, explicitDbPath, latestSupportedVersion) {
6240
6190
  if (!enforceSchemaFence(db, dbPath, latestSupportedVersion)) {
6241
6191
  closeQuietly(db);
@@ -6256,8 +6206,8 @@ function finishDatabaseOpen(db, dbPath, explicitDbPath, latestSupportedVersion)
6256
6206
  startMessageFtsRowidMapBackfill(db).then(async () => {
6257
6207
  if (!readsOpenCodeStore)
6258
6208
  return;
6259
- const { readRawSessionMessagePage, readRawSessionMessages } = await importPluginModule(() => import("./read-session-chunk-hjza38hr.js"));
6260
- const { startMessageTimeBackfill } = await importPluginModule(() => import("./message-time-backfill-0n93vrd7.js"));
6209
+ const { readRawSessionMessagePage, readRawSessionMessages } = await importPluginModule(() => import("./read-session-chunk-8vjp6581.js"));
6210
+ const { startMessageTimeBackfill } = await importPluginModule(() => import("./message-time-backfill-r6qkem1x.js"));
6261
6211
  await startMessageTimeBackfill(db, Object.assign(readRawSessionMessages, {
6262
6212
  readPage: readRawSessionMessagePage
6263
6213
  }));
@@ -6280,7 +6230,7 @@ function finishDatabaseOpen(db, dbPath, explicitDbPath, latestSupportedVersion)
6280
6230
  }
6281
6231
  setDatabase(db);
6282
6232
  loadToolDefinitionMeasurements(db);
6283
- restrictDatabaseFilePermissions(dbPath);
6233
+ tightenStorageTree(dirname(dbPath));
6284
6234
  databases.set(dbPath, db);
6285
6235
  pathByDatabase.set(db, dbPath);
6286
6236
  persistenceByDatabase.set(db, true);
@@ -6293,7 +6243,7 @@ function finishDatabaseOpen(db, dbPath, explicitDbPath, latestSupportedVersion)
6293
6243
  function initializeDatabase(db, busyTimeoutMs = BOOT_SQLITE_BUSY_TIMEOUT_MS) {
6294
6244
  db.exec(`PRAGMA busy_timeout=${resolveBootBusyTimeoutMs(busyTimeoutMs)}`);
6295
6245
  db.exec("PRAGMA foreign_keys=ON");
6296
- db.exec("PRAGMA journal_mode=WAL");
6246
+ configureContextDatabasePragmas(db);
6297
6247
  applySqliteTuningPragmas(db);
6298
6248
  db.exec(`
6299
6249
  CREATE TABLE IF NOT EXISTS tags (
@@ -6355,7 +6305,6 @@ function initializeDatabase(db, busyTimeoutMs = BOOT_SQLITE_BUSY_TIMEOUT_MS) {
6355
6305
  harness TEXT NOT NULL DEFAULT 'opencode',
6356
6306
  UNIQUE(session_id, sequence)
6357
6307
  );
6358
- CREATE INDEX IF NOT EXISTS idx_compartments_session ON compartments(session_id);
6359
6308
 
6360
6309
  CREATE TABLE IF NOT EXISTS compartment_chunk_embeddings (
6361
6310
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -6416,7 +6365,6 @@ function initializeDatabase(db, busyTimeoutMs = BOOT_SQLITE_BUSY_TIMEOUT_MS) {
6416
6365
  harness TEXT NOT NULL DEFAULT 'opencode',
6417
6366
  PRIMARY KEY(session_id, message_ordinal)
6418
6367
  );
6419
- CREATE INDEX IF NOT EXISTS idx_compression_depth_session ON compression_depth(session_id);
6420
6368
 
6421
6369
  CREATE TABLE IF NOT EXISTS session_facts (
6422
6370
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -6722,7 +6670,6 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
6722
6670
  stale_reason TEXT,
6723
6671
  PRIMARY KEY (project_path, path)
6724
6672
  );
6725
- CREATE INDEX IF NOT EXISTS idx_project_key_files_project ON project_key_files(project_path);
6726
6673
  CREATE INDEX IF NOT EXISTS idx_project_key_files_generated_at ON project_key_files(project_path, generated_at);
6727
6674
 
6728
6675
  CREATE TABLE IF NOT EXISTS project_key_files_version (
@@ -7107,10 +7054,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
7107
7054
  input_tokens INTEGER NOT NULL DEFAULT 0,
7108
7055
  PRIMARY KEY (session_id, harness, message_id)
7109
7056
  );
7110
- CREATE INDEX IF NOT EXISTS idx_transform_decisions_session_harness
7111
- ON transform_decisions(session_id, harness);
7112
7057
 
7113
- CREATE INDEX IF NOT EXISTS idx_tags_session_tag_number ON tags(session_id, tag_number);
7114
7058
  CREATE INDEX IF NOT EXISTS idx_tags_session_message_id ON tags(session_id, message_id);
7115
7059
 
7116
7060
  -- Clone/import paths can write tags before session bootstrap. Keep trigger-created
@@ -7156,9 +7100,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
7156
7100
  WHERE NEW.session_id != OLD.session_id
7157
7101
  ON CONFLICT(session_id) DO UPDATE SET tags_version = tags_version + 1;
7158
7102
  END;
7159
- CREATE INDEX IF NOT EXISTS idx_pending_ops_session ON pending_ops(session_id);
7160
7103
  CREATE INDEX IF NOT EXISTS idx_pending_ops_session_tag_id ON pending_ops(session_id, tag_id);
7161
- CREATE INDEX IF NOT EXISTS idx_source_contents_session ON source_contents(session_id);
7162
7104
 
7163
7105
  CREATE TABLE IF NOT EXISTS recomp_compartments (
7164
7106
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -7511,8 +7453,6 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
7511
7453
  input_tokens INTEGER NOT NULL DEFAULT 0,
7512
7454
  PRIMARY KEY (session_id, harness, message_id)
7513
7455
  );
7514
- CREATE INDEX IF NOT EXISTS idx_transform_decisions_session_harness
7515
- ON transform_decisions(session_id, harness);
7516
7456
  `);
7517
7457
  ensureColumn(db, "transform_decisions", "system_hash_prev", "TEXT");
7518
7458
  ensureColumn(db, "transform_decisions", "system_hash_new", "TEXT");
@@ -7534,12 +7474,26 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
7534
7474
  ON message_history_index(harness, session_id, updated_at);
7535
7475
  `);
7536
7476
  ensureColumn(db, "workspaces", "share_categories", `TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
7477
+ const version = getPersistedSchemaVersion(db);
7478
+ if (version === 0 || version >= 95)
7479
+ installV95PerfSchema(db, false, version === 0);
7537
7480
  }
7538
7481
  var CHANNEL2_CLAIM_TTL_MS = 10 * 60000;
7482
+ var channel2HealStatements = new WeakMap;
7539
7483
  function healWedgedChannel2Claims(db) {
7540
7484
  try {
7541
7485
  const staleBefore = Date.now() - CHANNEL2_CLAIM_TTL_MS;
7542
- db.prepare("UPDATE session_meta SET channel2_nudge_state = '', channel2_nudge_claimed_at = 0, channel2_nudge_claim_token = '' WHERE channel2_nudge_state = 'claimed' AND (channel2_nudge_claimed_at IS NULL OR channel2_nudge_claimed_at = 0 OR channel2_nudge_claimed_at <= ?)").run(staleBefore);
7486
+ let statements = channel2HealStatements.get(db);
7487
+ if (!statements) {
7488
+ const predicate = "channel2_nudge_state = 'claimed' AND (channel2_nudge_claimed_at IS NULL OR channel2_nudge_claimed_at = 0 OR channel2_nudge_claimed_at <= ?)";
7489
+ statements = {
7490
+ stale: db.prepare(`SELECT 1 FROM session_meta WHERE ${predicate} LIMIT 1`),
7491
+ heal: db.prepare(`UPDATE session_meta SET channel2_nudge_state = '', channel2_nudge_claimed_at = 0, channel2_nudge_claim_token = '' WHERE ${predicate}`)
7492
+ };
7493
+ channel2HealStatements.set(db, statements);
7494
+ }
7495
+ if (statements.stale.get(staleBefore))
7496
+ statements.heal.run(staleBefore);
7543
7497
  } catch {}
7544
7498
  }
7545
7499
  function openDatabase(dbPathOrOptions) {
@@ -7571,18 +7525,38 @@ function openDatabase(dbPathOrOptions) {
7571
7525
  migrateLegacyStorageIfNeeded(dbPath, dbDir);
7572
7526
  }
7573
7527
  ensureSecureStorageDir(dbDir);
7528
+ tightenStorageTree(dbDir);
7529
+ if (!existsSync3(dbPath) && shouldEnforcePrivateStoragePermissions()) {
7530
+ try {
7531
+ writeStorageFileSync(dbPath, new Uint8Array, { flag: "wx" });
7532
+ } catch (error) {
7533
+ if (error.code !== "EEXIST")
7534
+ throw error;
7535
+ }
7536
+ }
7574
7537
  db = new Database(dbPath);
7575
7538
  installBootBusyTimeout(db, dbPath, busyTimeoutMs, options?.onBootBusyTimeout);
7576
7539
  if (!enforceSchemaFence(db, dbPath, latestSupportedVersion)) {
7577
7540
  closeQuietly(db);
7578
7541
  return null;
7579
7542
  }
7543
+ if (options?.allowMigrations === false && hasPendingMigrations(db)) {
7544
+ log(`[magic-context] storage not ready: pending migrations at ${dbPath}; host callers must await the async opener`);
7545
+ closeQuietly(db);
7546
+ return null;
7547
+ }
7580
7548
  if (!enforceMigrationOnOpenGuard(db, dbPath, dbDir, latestSupportedVersion)) {
7581
7549
  closeQuietly(db);
7582
7550
  return null;
7583
7551
  }
7584
7552
  initializeDatabase(db, busyTimeoutMs);
7585
- runMigrations(db);
7553
+ if (options?.allowMigrations === false) {
7554
+ if (hasPendingMigrations(db)) {
7555
+ closeQuietly(db);
7556
+ return null;
7557
+ }
7558
+ } else
7559
+ runMigrations(db);
7586
7560
  ensureContextStoreUuid(db);
7587
7561
  return finishDatabaseOpen(db, dbPath, explicitDbPath, latestSupportedVersion);
7588
7562
  } catch (error) {
@@ -7593,6 +7567,9 @@ function openDatabase(dbPathOrOptions) {
7593
7567
  throw new Error(`[magic-context] storage unavailable: ${detail}. Magic Context is disabled for this run; check log for details.`);
7594
7568
  }
7595
7569
  }
7570
+ function openCurrentDatabase(options = {}) {
7571
+ return openDatabase({ ...options, allowMigrations: false });
7572
+ }
7596
7573
  async function openDatabaseAsync(dbPathOrOptions) {
7597
7574
  const options = typeof dbPathOrOptions === "string" ? { dbPath: dbPathOrOptions } : dbPathOrOptions;
7598
7575
  const explicitDbPath = options?.dbPath !== undefined;
@@ -7620,8 +7597,12 @@ async function openDatabaseAsync(dbPathOrOptions) {
7620
7597
  const pending = pendingAsyncOpens.get(dbPath);
7621
7598
  if (pending)
7622
7599
  return pending;
7600
+ if (!isFileBackedPath(dbPath)) {
7601
+ throw new Error("async migration requires a file-backed database; use the explicit synchronous opener for in-memory or URI test databases");
7602
+ }
7623
7603
  const opening = (async () => {
7624
7604
  let db;
7605
+ const mainThreadBodiesBefore = getMainThreadMigrationBodyCount();
7625
7606
  const openStartedAt = performance.now();
7626
7607
  let openMs = 0;
7627
7608
  let guardMs = 0;
@@ -7632,6 +7613,15 @@ async function openDatabaseAsync(dbPathOrOptions) {
7632
7613
  if (!explicitDbPath)
7633
7614
  migrateLegacyStorageIfNeeded(dbPath, dbDir);
7634
7615
  ensureSecureStorageDir(dbDir);
7616
+ tightenStorageTree(dbDir);
7617
+ if (!existsSync3(dbPath) && shouldEnforcePrivateStoragePermissions()) {
7618
+ try {
7619
+ writeStorageFileSync(dbPath, new Uint8Array, { flag: "wx" });
7620
+ } catch (error) {
7621
+ if (error.code !== "EEXIST")
7622
+ throw error;
7623
+ }
7624
+ }
7635
7625
  db = new Database(dbPath);
7636
7626
  installBootBusyTimeout(db, dbPath, busyTimeoutMs, options?.onBootBusyTimeout);
7637
7627
  openMs = performance.now() - openStartedAt;
@@ -7649,15 +7639,20 @@ async function openDatabaseAsync(dbPathOrOptions) {
7649
7639
  }
7650
7640
  guardMs = performance.now() - guardStartedAt;
7651
7641
  migrateStartedAt = performance.now();
7652
- if (isFileBackedPath(dbPath) && hasPendingMigrations(db)) {
7642
+ if (hasPendingMigrations(db)) {
7653
7643
  await runMigrationsOffThread({
7654
7644
  dbPath,
7655
7645
  busyTimeoutMs,
7656
7646
  sqlitePragmaConfig: { ...sqlitePragmaConfig }
7657
7647
  });
7658
7648
  }
7649
+ if (hasPendingMigrations(db)) {
7650
+ throw new Error("the migration worker did not complete the pending migration; reinstall or rebuild the plugin");
7651
+ }
7659
7652
  initializeDatabase(db, busyTimeoutMs);
7660
- await runMigrationsWithRetry(db);
7653
+ if (hasPendingMigrations(db)) {
7654
+ throw new Error("storage initialization left a pending migration; reinstall or rebuild the plugin");
7655
+ }
7661
7656
  ensureContextStoreUuid(db);
7662
7657
  const opened = finishDatabaseOpen(db, dbPath, explicitDbPath, latestSupportedVersion);
7663
7658
  migrateMs = performance.now() - migrateStartedAt;
@@ -7669,6 +7664,7 @@ async function openDatabaseAsync(dbPathOrOptions) {
7669
7664
  log(`[magic-context] storage fatal: failed to open ${dbPath}: ${detail}`);
7670
7665
  throw new Error(`[magic-context] storage unavailable: ${detail}. Magic Context is disabled for this run; check log for details.`);
7671
7666
  } finally {
7667
+ log(`[migrations] async open main-thread migration-body count: ${getMainThreadMigrationBodyCount() - mainThreadBodiesBefore} (total=${getMainThreadMigrationBodyCount()}) path=${dbPath}`);
7672
7668
  if (openMs === 0)
7673
7669
  openMs = performance.now() - openStartedAt;
7674
7670
  if (guardStartedAt !== null && guardMs === 0) {
@@ -7699,4 +7695,4 @@ function getDatabasePersistenceError(db) {
7699
7695
  return persistenceErrorByDatabase.get(db) ?? null;
7700
7696
  }
7701
7697
 
7702
- export { FAIL_CLOSED_DOCTOR_COMMAND, formatFailClosedBlockingMessage, formatFailClosedBlockingSummary, createFailClosedBlockingError, isFailClosedBlockingError, shouldBypassFailClosedBlock, resolveAgentNameFromMessages, canonicalProjectDirectory, setHomeProjectPermission, shouldSkipHomeProjectMemory, ProjectIdentityError, takeDubiousOwnershipProjectIdentityWarning, isUserHomeDirectory, resolveProjectIdentity, directoryHasGitMetadata, describeUnresolvedProjectIdentity, isUsableProjectIdentity, resolveProjectIdentityForSession, normalizeStoredProjectPath, storedPathBelongsToIdentity, recordMessageFtsRowid, messageFtsOrdinalRangeIsMapped, deleteUnmappedMessageFtsRows, registerOwnRpcServerInstance, rpcPortDir, rpcPortFilePath, isPidAlive, parseRpcPortFile, inspectProcessesAsync, V2_MEMORY_CATEGORIES, PROMOTABLE_CATEGORIES, CATEGORY_PRIORITY, getMemoryCategoryOrder, CATEGORY_DEFAULT_TTL, unescapeXml, parseCompartmentOutput, layoutLkgPrefix, resolveWorkspaceShareCategories, resolveWorkspaceIdentitySet, expandWorkspaceIdentitySetWithAliases, resolveStoredPathWorkspaceIdentity, storedPathBelongsToWorkspace, sourceNameForMemory, computeWorkspaceEpochFingerprint, managedAuthorityNoteRow, runMigrationsWithRetry, scheduleAfterBootQuiet, startBootDeadline, getUnconfirmedMigrationHolders, getSchemaFenceRejection, getMigrationOnOpenRefusal, LATEST_SUPPORTED_VERSION, BOOT_SQLITE_BUSY_TIMEOUT_MS, getPersistedSchemaVersion, setSqlitePragmaConfig, initializeDatabase, openDatabase, openDatabaseAsync, isDatabasePersisted, getDatabasePersistenceError };
7698
+ export { FAIL_CLOSED_DOCTOR_COMMAND, formatFailClosedBlockingMessage, formatFailClosedBlockingSummary, createFailClosedBlockingError, isFailClosedBlockingError, shouldBypassFailClosedBlock, resolveAgentNameFromMessages, canonicalProjectDirectory, setHomeProjectPermission, shouldSkipHomeProjectMemory, ProjectIdentityError, takeDubiousOwnershipProjectIdentityWarning, isUserHomeDirectory, resolveProjectIdentity, directoryHasGitMetadata, describeUnresolvedProjectIdentity, isUsableProjectIdentity, resolveProjectIdentityForSession, normalizeStoredProjectPath, storedPathBelongsToIdentity, recordMessageFtsRowid, messageFtsOrdinalRangeIsMapped, deleteUnmappedMessageFtsRows, registerOwnRpcServerInstance, rpcPortDir, rpcPortFilePath, isPidAlive, parseRpcPortFile, inspectProcessesAsync, V2_MEMORY_CATEGORIES, PROMOTABLE_CATEGORIES, CATEGORY_PRIORITY, getMemoryCategoryOrder, CATEGORY_DEFAULT_TTL, unescapeXml, parseCompartmentOutput, layoutLkgPrefix, assembleLkgPrefix, resolveWorkspaceShareCategories, resolveWorkspaceIdentitySet, expandWorkspaceIdentitySetWithAliases, resolveStoredPathWorkspaceIdentity, storedPathBelongsToWorkspace, sourceNameForMemory, computeWorkspaceEpochFingerprint, managedAuthorityNoteRow, runMigrationsWithRetry, beginBootQuietPeriod, scheduleAfterBootQuiet, startBootDeadline, getUnconfirmedMigrationHolders, getSchemaFenceRejection, getMigrationOnOpenRefusal, LATEST_SUPPORTED_VERSION, BOOT_SQLITE_BUSY_TIMEOUT_MS, getPersistedSchemaVersion, setSqlitePragmaConfig, runSqliteOptimize, initializeDatabase, openDatabase, openCurrentDatabase, openDatabaseAsync, isDatabasePersisted, getDatabasePersistenceError };