@cortexkit/opencode-magic-context 0.49.6 → 0.49.8

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 (255) hide show
  1. package/dist/auto-search-worker.js +10 -7
  2. package/dist/compaction-marker-candidates-worker.js +2 -2
  3. package/dist/{compaction-marker-candidates-cz72pm1t.js → compaction-marker-candidates-xxz35vwp.js} +3 -3
  4. package/dist/{conflict-warning-hook-1jq1rxx7.js → conflict-warning-hook-w3crzrar.js} +8 -8
  5. package/dist/{context-ae3gxdye.js → context-x0ccfv0f.js} +88 -53
  6. package/dist/{derive-budgets-j4ry8j8n.js → derive-budgets-3nbxsyss.js} +2 -2
  7. package/dist/embedding-backfill-3h2m5n0q.js +21 -0
  8. package/dist/embedding-worker-process.js +6 -2
  9. package/dist/embedding-worker.js +6 -5
  10. package/dist/features/magic-context/compaction-marker-candidates.d.ts.map +1 -1
  11. package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
  12. package/dist/features/magic-context/dreamer/cron.d.ts +2 -0
  13. package/dist/features/magic-context/dreamer/cron.d.ts.map +1 -1
  14. package/dist/features/magic-context/dreamer/hidden-single-shot.d.ts +6 -3
  15. package/dist/features/magic-context/dreamer/hidden-single-shot.d.ts.map +1 -1
  16. package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts +7 -1
  17. package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts.map +1 -1
  18. package/dist/features/magic-context/dreamer/map-memories.d.ts +2 -2
  19. package/dist/features/magic-context/dreamer/map-memories.d.ts.map +1 -1
  20. package/dist/features/magic-context/dreamer/primer-seed.d.ts +2 -0
  21. package/dist/features/magic-context/dreamer/primer-seed.d.ts.map +1 -1
  22. package/dist/features/magic-context/dreamer/provider-output-failure.d.ts +1 -0
  23. package/dist/features/magic-context/dreamer/provider-output-failure.d.ts.map +1 -1
  24. package/dist/features/magic-context/dreamer/refresh-primers.d.ts +2 -2
  25. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  26. package/dist/features/magic-context/memory/embedding-worker-client.d.ts.map +1 -1
  27. package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
  28. package/dist/features/magic-context/migration-worker-client.d.ts.map +1 -1
  29. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  30. package/dist/features/magic-context/smart-notes/wake-plane.d.ts.map +1 -1
  31. package/dist/features/magic-context/storage-clone.d.ts.map +1 -1
  32. package/dist/features/magic-context/storage-meta-persisted.d.ts +10 -3
  33. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  34. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  35. package/dist/features/magic-context/storage-replay-document.d.ts +4 -0
  36. package/dist/features/magic-context/storage-replay-document.d.ts.map +1 -1
  37. package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
  38. package/dist/history-owner-ty29fz0f.js +244 -0
  39. package/dist/hooks/auto-update-checker/checker.d.ts.map +1 -1
  40. package/dist/hooks/magic-context/apply-operations.d.ts +7 -0
  41. package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
  42. package/dist/hooks/magic-context/auto-search-worker-client.d.ts.map +1 -1
  43. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  44. package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
  45. package/dist/hooks/magic-context/edit-marker.d.ts +2 -0
  46. package/dist/hooks/magic-context/edit-marker.d.ts.map +1 -1
  47. package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
  48. package/dist/hooks/magic-context/history-boundary-repair.d.ts.map +1 -1
  49. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  50. package/dist/hooks/magic-context/lkg-persist.d.ts.map +1 -1
  51. package/dist/hooks/magic-context/module-transport.d.ts +1 -0
  52. package/dist/hooks/magic-context/module-transport.d.ts.map +1 -1
  53. package/dist/hooks/magic-context/note-nudger.d.ts +5 -3
  54. package/dist/hooks/magic-context/note-nudger.d.ts.map +1 -1
  55. package/dist/hooks/magic-context/postprocess-read-cache.d.ts.map +1 -1
  56. package/dist/hooks/magic-context/raw-ordinal-warmup.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/read-session-chunk.d.ts +15 -4
  58. package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
  59. package/dist/hooks/magic-context/replay-committed-drops.d.ts +9 -0
  60. package/dist/hooks/magic-context/replay-committed-drops.d.ts.map +1 -0
  61. package/dist/hooks/magic-context/rust-lkg-freeze-registry.d.ts +2 -2
  62. package/dist/hooks/magic-context/rust-lkg-freeze-registry.d.ts.map +1 -1
  63. package/dist/hooks/magic-context/rust-mode-transform.d.ts +4 -0
  64. package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
  65. package/dist/hooks/magic-context/rust-refusal-recovery.d.ts.map +1 -1
  66. package/dist/hooks/magic-context/rust-served-raw.d.ts +29 -0
  67. package/dist/hooks/magic-context/rust-served-raw.d.ts.map +1 -0
  68. package/dist/hooks/magic-context/strip-content.d.ts +23 -24
  69. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  70. package/dist/hooks/magic-context/tag-messages.d.ts +2 -0
  71. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  72. package/dist/hooks/magic-context/tool-drop-target.d.ts +2 -0
  73. package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
  74. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  75. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  76. package/dist/hooks/magic-context/unresolved-history-boundary.d.ts +5 -1
  77. package/dist/hooks/magic-context/unresolved-history-boundary.d.ts.map +1 -1
  78. package/dist/hooks/magic-context/wrapup-orchestrator.d.ts.map +1 -1
  79. package/dist/{index-g9exahnx.js → index-0jz47061.js} +6 -3
  80. package/dist/{index-4mjhqadz.js → index-150zz6fh.js} +1 -1
  81. package/dist/{index-ch08qwgd.js → index-2rw244rh.js} +88 -81
  82. package/dist/{index-6q5h70h2.js → index-4hb8nt93.js} +3 -3
  83. package/dist/{index-j55vn694.js → index-7bjgyaj1.js} +4 -4
  84. package/dist/{index-h2wc01qk.js → index-8kk7yh6m.js} +2 -2
  85. package/dist/{index-vztbgcdk.js → index-9g1c4bt9.js} +304 -92
  86. package/dist/{index-7q1m1qjk.js → index-a43xq1pg.js} +5 -5
  87. package/dist/{index-tqf7yg27.js → index-a4b0gbyg.js} +1 -1
  88. package/dist/{index-vnjhmkwk.js → index-angx1s0k.js} +4 -3
  89. package/dist/{index-1xaw2pq8.js → index-b4gcb3rk.js} +8 -8
  90. package/dist/{index-z1tjp257.js → index-b64n4d0h.js} +1 -1
  91. package/dist/{index-1bt88epp.js → index-bd9cjab3.js} +1 -1
  92. package/dist/{index-gbmqa9x9.js → index-bmwfzz28.js} +9 -6
  93. package/dist/{index-qx53vezw.js → index-gjvjh960.js} +1 -1
  94. package/dist/{index-zgzdm6dg.js → index-gzg9ekc1.js} +155 -103
  95. package/dist/{index-vh3e2mvr.js → index-hxrvx6cy.js} +121 -51
  96. package/dist/{index-w23dh319.js → index-jw4gbfkv.js} +1 -1
  97. package/dist/index-knvkbfvh.js +25 -0
  98. package/dist/{index-vkbqdnkj.js → index-kvw6xew3.js} +6 -3
  99. package/dist/{index-bg5qqene.js → index-mp2kghkv.js} +3 -27
  100. package/dist/{index-nsjyysj0.js → index-mvvephtj.js} +5 -1
  101. package/dist/{index-cm4hmxyk.js → index-n78avt2p.js} +1 -1
  102. package/dist/{index-hr64sth2.js → index-pgy3h49n.js} +4 -4
  103. package/dist/{index-a796ct3s.js → index-pjgzdezw.js} +13 -9
  104. package/dist/{index-9m7bn21d.js → index-q28hsbpg.js} +15 -3
  105. package/dist/{index-ab3bgm2m.js → index-q76br5zn.js} +3 -3
  106. package/dist/{index-xqyj5c85.js → index-qgp5vnt4.js} +5 -5
  107. package/dist/{index-9s58tq95.js → index-rg1cezmj.js} +22 -22
  108. package/dist/{index-f3dnvnw6.js → index-s178h9z8.js} +4 -3
  109. package/dist/{index-mj1fgfrk.js → index-w1ejb9qr.js} +939 -160
  110. package/dist/{index-k6xvbj2e.js → index-w28y7d0z.js} +2 -0
  111. package/dist/{index-0nnd4bry.js → index-wafzvyhg.js} +63 -19
  112. package/dist/{index-ck6y3td2.js → index-xf6k8szp.js} +4 -4
  113. package/dist/{index-1j8jcjpa.js → index-xpn05jxq.js} +4 -4
  114. package/dist/{index-s0gsmthp.js → index-ydjgz36f.js} +7 -7
  115. package/dist/{index-besxhcsh.js → index-zf9nydbd.js} +50 -39
  116. package/dist/index.js +53 -52
  117. package/dist/{logger-ewak5q1s.js → logger-wn328tfg.js} +1 -1
  118. package/dist/{message-index-bounded-630j53ps.js → message-index-bounded-hxjp56qk.js} +16 -16
  119. package/dist/{message-index-c84wm2g0.js → message-index-zs882h8e.js} +16 -16
  120. package/dist/{message-time-backfill-8kj57qe3.js → message-time-backfill-nb5hhnrt.js} +3 -3
  121. package/dist/migration-worker.js +11 -11
  122. package/dist/plugin/boot-quiet.d.ts.map +1 -1
  123. package/dist/plugin/dream-timer.d.ts.map +1 -1
  124. package/dist/plugin/messages-transform.d.ts.map +1 -1
  125. package/dist/plugin/rpc-handlers.d.ts +2 -0
  126. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  127. package/dist/{project-embedding-registry-vv59t7ah.js → project-embedding-registry-vy7sv9jt.js} +11 -11
  128. package/dist/raw-ordinal-warmup-worker.js +17 -15
  129. package/dist/{read-session-chunk-t3vjd0h0.js → read-session-chunk-t9r7r0sg.js} +13 -11
  130. package/dist/{read-session-raw-gf7eytnm.js → read-session-raw-gm6xj30v.js} +3 -3
  131. package/dist/recomp-orchestrator-gr36zvd2.js +60 -0
  132. package/dist/{safe-notification-target-q0cyrx3x.js → safe-notification-target-jdynm677.js} +2 -2
  133. package/dist/{search-kth2p6cc.js → search-qjen1pmz.js} +24 -24
  134. package/dist/{send-session-notification-3j6krd4w.js → send-session-notification-q8ngbap3.js} +6 -6
  135. package/dist/shared/history-owner.d.ts +12 -0
  136. package/dist/shared/history-owner.d.ts.map +1 -0
  137. package/dist/shared/off-thread-migration-clock.d.ts +8 -1
  138. package/dist/shared/off-thread-migration-clock.d.ts.map +1 -1
  139. package/dist/shared/processed-image-eligibility.d.ts +22 -0
  140. package/dist/shared/processed-image-eligibility.d.ts.map +1 -0
  141. package/dist/shared/recovery-reasoning-prefix.d.ts +9 -0
  142. package/dist/shared/recovery-reasoning-prefix.d.ts.map +1 -0
  143. package/dist/shared/redaction.d.ts.map +1 -1
  144. package/dist/shared/rpc-server.d.ts.map +1 -1
  145. package/dist/shared/sqlite.d.ts +3 -1
  146. package/dist/shared/sqlite.d.ts.map +1 -1
  147. package/dist/shared/tag-transcript.d.ts +9 -0
  148. package/dist/shared/tag-transcript.d.ts.map +1 -1
  149. package/dist/shared/user-facing-codes.d.ts +2 -2
  150. package/dist/{sqlite-d7edbk1s.js → sqlite-4pqyynrs.js} +3 -1
  151. package/dist/{storage-memory-af5zdz98.js → storage-memory-j3v3hc83.js} +2 -2
  152. package/dist/{storage-tags-dyj7mmbq.js → storage-tags-a513rkj3.js} +5 -5
  153. package/dist/tools/ctx-expand/search-archive.d.ts +4 -0
  154. package/dist/tools/ctx-expand/search-archive.d.ts.map +1 -0
  155. package/dist/tools/ctx-expand/tools.d.ts.map +1 -1
  156. package/dist/{transform-postprocess-phase-bv3kb5s1.js → transform-postprocess-phase-yv463b7y.js} +30 -30
  157. package/dist/v2/auto-search-worker.js +10 -7
  158. package/dist/v2/{compaction-marker-candidates-6gqp5wrc.js → compaction-marker-candidates-a15w3mqh.js} +3 -3
  159. package/dist/v2/compaction-marker-candidates-worker.js +2 -2
  160. package/dist/v2/{context-p8f2shp1.js → context-wd82v9z6.js} +1133 -278
  161. package/dist/v2/{derive-budgets-enckmg7y.js → derive-budgets-0rmpmxjw.js} +3 -3
  162. package/dist/v2/embedding-backfill-vr7wdtrf.js +20 -0
  163. package/dist/v2/embedding-worker-process.js +6 -2
  164. package/dist/v2/embedding-worker.js +6 -5
  165. package/dist/v2/hidden-completion.d.ts.map +1 -1
  166. package/dist/v2/history-owner-2cr9py37.js +243 -0
  167. package/dist/v2/hooks/context.d.ts.map +1 -1
  168. package/dist/v2/hooks/payload.d.ts.map +1 -1
  169. package/dist/v2/hooks/store.d.ts +1 -0
  170. package/dist/v2/hooks/store.d.ts.map +1 -1
  171. package/dist/v2/{logger-pj02s7sr.js → logger-te6e1j72.js} +1 -1
  172. package/dist/v2/{message-index-bounded-yzxmf6vp.js → message-index-bounded-madg2kfr.js} +16 -16
  173. package/dist/v2/{message-index-3ws17x1g.js → message-index-yd94vt9n.js} +16 -16
  174. package/dist/v2/{message-time-backfill-5nf10c65.js → message-time-backfill-rz1pnpaj.js} +3 -3
  175. package/dist/v2/migration-worker.js +11 -11
  176. package/dist/v2/{project-embedding-registry-rsnf2263.js → project-embedding-registry-ja23aygk.js} +11 -11
  177. package/dist/v2/raw-ordinal-warmup-worker.js +17 -15
  178. package/dist/v2/{read-session-chunk-vtb0w2kd.js → read-session-chunk-7qs791f3.js} +13 -11
  179. package/dist/v2/{read-session-raw-gpcddemt.js → read-session-raw-h4azh85b.js} +3 -3
  180. package/dist/v2/recomp-orchestrator-ee91r9s6.js +59 -0
  181. package/dist/v2/{safe-notification-target-y3p1hce9.js → safe-notification-target-936374xx.js} +2 -2
  182. package/dist/v2/search-pn15rr52.js +46 -0
  183. package/dist/v2/{send-session-notification-5aj2j7vf.js → send-session-notification-6n7chhyp.js} +6 -6
  184. package/dist/v2/{server-09g9pkvx.js → server-08k4zkfk.js} +50 -39
  185. package/dist/v2/{server-ras2sxnc.js → server-44ryn9rb.js} +9 -6
  186. package/dist/v2/{server-158ehmk2.js → server-4easnf78.js} +19 -19
  187. package/dist/v2/{server-ww9nr8pe.js → server-4gz0xkrc.js} +5 -5
  188. package/dist/v2/{server-d6g4whc2.js → server-4qr18rfg.js} +3 -3
  189. package/dist/v2/{server-f9py7s7a.js → server-4rs8sm06.js} +5 -5
  190. package/dist/v2/{server-yezz0cp2.js → server-63ha7f0f.js} +3 -27
  191. package/dist/v2/{server-3t0q2cqx.js → server-7dv9wt9r.js} +1 -1
  192. package/dist/v2/{server-cw6521wn.js → server-7e087rvk.js} +2 -0
  193. package/dist/v2/{server-hb6xhh9q.js → server-988cqjme.js} +6 -3
  194. package/dist/v2/{server-keswwkvv.js → server-9a6bwc12.js} +4 -4
  195. package/dist/v2/{server-t4k4qexw.js → server-9e5r8y60.js} +5 -1
  196. package/dist/v2/{server-7ekna9zs.js → server-adfte03x.js} +2 -2
  197. package/dist/v2/{server-9zqewm6g.js → server-ak7w74j9.js} +4 -4
  198. package/dist/v2/{server-387vkhjq.js → server-bx59ezg6.js} +4 -3
  199. package/dist/v2/{server-676xw7at.js → server-ckrb5rm8.js} +1 -1
  200. package/dist/v2/{server-wrem2nmz.js → server-cxsr0ng6.js} +15 -3
  201. package/dist/v2/{server-t528t8qm.js → server-hn9fb3v0.js} +2 -2
  202. package/dist/v2/{server-qn23p4h7.js → server-j3dm74ey.js} +9 -9
  203. package/dist/v2/{server-yd01snxq.js → server-jjsf29aq.js} +4 -4
  204. package/dist/v2/server-knvkbfvh.js +25 -0
  205. package/dist/v2/{server-cm4hmxyk.js → server-n78avt2p.js} +1 -1
  206. package/dist/v2/{server-btgnsv7w.js → server-njnx39q2.js} +6 -3
  207. package/dist/v2/{server-6a9mxnb2.js → server-npm52h2q.js} +4 -3
  208. package/dist/v2/{server-vb7pbd9p.js → server-nveratm5.js} +1 -1
  209. package/dist/v2/{server-7skbfb81.js → server-q360kdb8.js} +121 -51
  210. package/dist/v2/{server-kzns2hbz.js → server-s6jqh8bj.js} +1 -1
  211. package/dist/v2/{server-6byqfbqf.js → server-shme41gj.js} +1 -1
  212. package/dist/v2/{server-0nnd4bry.js → server-wafzvyhg.js} +63 -19
  213. package/dist/v2/{server-fby0cxbq.js → server-xj4mewze.js} +304 -92
  214. package/dist/v2/{server-aa9w15cs.js → server-y5g32dhq.js} +88 -81
  215. package/dist/v2/{server-x2r63c0x.js → server-yak3sxya.js} +7 -7
  216. package/dist/v2/{server-4tn95znb.js → server-zrwaj29a.js} +2 -2
  217. package/dist/v2/server.js +9 -8
  218. package/dist/v2/{sqlite-d7edbk1s.js → sqlite-4pqyynrs.js} +3 -1
  219. package/dist/v2/{storage-memory-af5zdz98.js → storage-memory-j3v3hc83.js} +2 -2
  220. package/dist/v2/{storage-tags-d47bpaqa.js → storage-tags-spa92sgj.js} +5 -5
  221. package/dist/v2/store-reader.d.ts.map +1 -1
  222. package/dist/v2/{wrapup-orchestrator-cahk37n7.js → wrapup-orchestrator-5wxy1ggv.js} +38 -35
  223. package/dist/wrapup-orchestrator-gv08c6f9.js +47 -0
  224. package/package.json +3 -3
  225. package/src/config/compaction-accessor-guard.test.ts +3 -0
  226. package/src/features/magic-context/dreamer/cron.ts +8 -0
  227. package/src/shared/history-owner.test.ts +60 -0
  228. package/src/shared/history-owner.ts +43 -0
  229. package/src/shared/host-stall-profiler-node.ts +3 -1
  230. package/src/shared/host-stall-profiler.ts +3 -1
  231. package/src/shared/off-thread-migration-clock.test.ts +80 -10
  232. package/src/shared/off-thread-migration-clock.ts +37 -13
  233. package/src/shared/processed-image-eligibility.test.ts +68 -0
  234. package/src/shared/processed-image-eligibility.ts +41 -0
  235. package/src/shared/processed-image-position-review.json +20 -0
  236. package/src/shared/processed-image-position-review.test.ts +19 -0
  237. package/src/shared/recovery-reasoning-prefix.test.ts +34 -0
  238. package/src/shared/recovery-reasoning-prefix.ts +35 -0
  239. package/src/shared/redaction.test.ts +16 -0
  240. package/src/shared/redaction.ts +8 -1
  241. package/src/shared/root-async-context.probe.mjs +36 -0
  242. package/src/shared/root-async-context.test.ts +49 -0
  243. package/src/shared/rpc-server.ts +27 -23
  244. package/src/shared/sqlite-busy-budget-review.test.ts +291 -0
  245. package/src/shared/sqlite-foreground-wait.test.ts +253 -1
  246. package/src/shared/sqlite-writer-diagnostics.test.ts +31 -0
  247. package/src/shared/sqlite.ts +112 -30
  248. package/src/shared/tag-transcript.ts +118 -0
  249. package/src/shared/user-facing-codes.ts +2 -2
  250. package/dist/embedding-backfill-dtjmyktb.js +0 -21
  251. package/dist/recomp-orchestrator-1d08g57a.js +0 -60
  252. package/dist/v2/embedding-backfill-cg4rv90f.js +0 -20
  253. package/dist/v2/recomp-orchestrator-v64r4s4r.js +0 -59
  254. package/dist/v2/search-bpa4fxq1.js +0 -46
  255. package/dist/wrapup-orchestrator-mqp0v195.js +0 -47
@@ -0,0 +1,47 @@
1
+ import {
2
+ runManagedWrapup2
3
+ } from "./index-pjgzdezw.js";
4
+ import"./index-xf6k8szp.js";
5
+ import"./index-hxrvx6cy.js";
6
+ import"./index-s178h9z8.js";
7
+ import"./index-ydjgz36f.js";
8
+ import"./index-b4gcb3rk.js";
9
+ import"./index-sd3egckz.js";
10
+ import"./index-a43xq1pg.js";
11
+ import"./index-n78avt2p.js";
12
+ import"./index-rg1cezmj.js";
13
+ import"./index-7bjgyaj1.js";
14
+ import"./index-4dhv7mqz.js";
15
+ import"./index-jw4gbfkv.js";
16
+ import"./index-pgy3h49n.js";
17
+ import"./index-qt1qqasc.js";
18
+ import"./index-x8a7jmkh.js";
19
+ import"./index-a4b0gbyg.js";
20
+ import"./index-bmwfzz28.js";
21
+ import"./index-kvw6xew3.js";
22
+ import"./index-zf9nydbd.js";
23
+ import"./index-qgp5vnt4.js";
24
+ import"./index-fb3zxn06.js";
25
+ import"./index-fr0wbhdn.js";
26
+ import"./index-0jz47061.js";
27
+ import"./index-mp2kghkv.js";
28
+ import"./index-q28hsbpg.js";
29
+ import"./index-9g1c4bt9.js";
30
+ import"./index-xpn05jxq.js";
31
+ import"./index-1zx87sdm.js";
32
+ import"./index-q76br5zn.js";
33
+ import"./index-angx1s0k.js";
34
+ import"./index-v0p37hm2.js";
35
+ import"./index-w28y7d0z.js";
36
+ import"./index-8kk7yh6m.js";
37
+ import"./index-bd9cjab3.js";
38
+ import"./index-58e3y90n.js";
39
+ import"./index-150zz6fh.js";
40
+ import"./index-gjvjh960.js";
41
+ import"./index-wafzvyhg.js";
42
+ import"./index-mvvephtj.js";
43
+ import"./index-0sahzkwr.js";
44
+ import"./index-pavzsdc6.js";
45
+ export {
46
+ runManagedWrapup2 as runManagedWrapup
47
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/opencode-magic-context",
3
- "version": "0.49.6",
3
+ "version": "0.49.8",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for Magic Context — cross-session memory and context management",
6
6
  "main": "dist/index.js",
@@ -43,8 +43,8 @@
43
43
  "check:tui-compiled": "git diff --exit-code -- src/tui-compiled && test -z \"$(git status --porcelain -- src/tui-compiled)\"",
44
44
  "check:transformers-node-wasm": "bun scripts/verify-transformers-node-wasm.ts",
45
45
  "typecheck": "tsc -p ../retina-local-fs/tsconfig.build.json && tsc --noEmit && tsc -p tsconfig.scripts.json",
46
- "test": "bun ../../scripts/check-bun.mjs && bun install --frozen-lockfile && BUN_JSC_useOMGJIT=0 bun test --parallel=4 --timeout 30000",
47
- "test:serial": "bun install --frozen-lockfile && BUN_JSC_useOMGJIT=0 bun test --timeout 30000",
46
+ "test": "bun ../../scripts/check-bun.mjs && bun install --frozen-lockfile && MC_REVIEW_ASSERT_LIVENESS=1 BUN_JSC_useOMGJIT=0 bun test --parallel=4 --timeout 30000",
47
+ "test:serial": "bun install --frozen-lockfile && MC_REVIEW_ASSERT_LIVENESS=1 BUN_JSC_useOMGJIT=0 bun test --timeout 30000",
48
48
  "test:parallel:probe": "bun scripts/verify-parallel-db-isolation.ts",
49
49
  "lint": "bun ../../scripts/check-bun.mjs && biome check .",
50
50
  "lint:fix": "biome check --write .",
@@ -32,6 +32,9 @@ const ALLOWED_READERS = new Set<string>([
32
32
  "packages/cli/src/lib/omp-helpers.ts",
33
33
  "packages/cli/src/commands/setup-omp.ts",
34
34
  "packages/cli/src/commands/doctor-omp.ts",
35
+ // The privacy projection recognizes the setting's name in diagnostic text;
36
+ // it never reads the parsed configuration value.
37
+ "packages/cli/src/lib/issue-privacy.ts",
35
38
  ]);
36
39
 
37
40
  function sourceFiles(directory: string): string[] {
@@ -61,6 +61,13 @@ const MINUTE_MS = 60_000;
61
61
  */
62
62
  const MAX_SEARCH_MS = 4 * 366 * 24 * 60 * MINUTE_MS;
63
63
 
64
+ let candidateVisitsForTests: (() => void) | undefined;
65
+
66
+ /** Count searched minutes in tests without affecting normal cron callers. */
67
+ export function __setCronCandidateVisitCounterForTests(counter?: () => void): void {
68
+ candidateVisitsForTests = counter;
69
+ }
70
+
64
71
  /** Parse one field into the set of matching values, or null on error. */
65
72
  function parseField(token: string, spec: FieldSpec): Set<number> | null {
66
73
  const values = new Set<number>();
@@ -226,6 +233,7 @@ export function nextOccurrence(
226
233
  const capMs = afterMs + Math.max(0, Math.min(MAX_SEARCH_MS, maxSearchMs));
227
234
 
228
235
  while (cursorMs <= capMs) {
236
+ candidateVisitsForTests?.();
229
237
  const candidate = new Date(cursorMs);
230
238
  if (matchesCron(cron, candidate)) {
231
239
  if (!excludeCivilMinute || civilMinuteKey(candidate) !== excludeCivilMinute) {
@@ -0,0 +1,60 @@
1
+ import { expect, test } from "bun:test";
2
+ import { handoffHistoryWork } from "./history-owner";
3
+ import type { MagicContextRpcClient } from "./rpc-client";
4
+
5
+ test("moved-session recomp is handed to the current directory before local work starts", async () => {
6
+ const calls: unknown[] = [];
7
+ const result = await handoffHistoryWork({
8
+ storageDir: "/isolated/mc",
9
+ directory: "/old",
10
+ sessionId: "session",
11
+ method: "recomp",
12
+ params: { sessionId: "session" },
13
+ readDirectory: async () => "/new",
14
+ findOwner: async () =>
15
+ ({
16
+ call: async (...args: unknown[]) => {
17
+ calls.push(args);
18
+ return { ok: true };
19
+ },
20
+ }) as unknown as MagicContextRpcClient,
21
+ });
22
+ expect(result).toEqual({ ok: true });
23
+ expect(calls).toEqual([
24
+ ["recomp", { sessionId: "session", directory: "/new", historyHandoff: true }],
25
+ ]);
26
+ });
27
+
28
+ test("moved-session recovery explains how to load a missing owner and stops handoff loops", async () => {
29
+ for (const historyHandoff of [false, true]) {
30
+ const result = await handoffHistoryWork({
31
+ storageDir: "/isolated/mc",
32
+ directory: "/old",
33
+ sessionId: "session",
34
+ method: "wrapup",
35
+ params: { sessionId: "session", historyHandoff },
36
+ readDirectory: async () => "/new",
37
+ findOwner: async () => null,
38
+ });
39
+ expect(result?.ok).toBe(false);
40
+ expect(result?.error).toContain(
41
+ "Reopen this session in its current project directory (/new)",
42
+ );
43
+ }
44
+ });
45
+
46
+ test("history recovery remains local when the host still reports this directory", async () => {
47
+ expect(
48
+ await handoffHistoryWork({
49
+ storageDir: "/isolated/mc",
50
+ directory: "/project",
51
+ sessionId: "session",
52
+ method: "recomp",
53
+ params: {},
54
+ readDirectory: async () => "/project",
55
+ findOwner: async () => {
56
+ throw new Error("must not discover");
57
+ },
58
+ }),
59
+ ).toBeUndefined();
60
+ });
@@ -0,0 +1,43 @@
1
+ import { sameProjectDirectory } from "./project-directory-key";
2
+ import { MagicContextRpcClient } from "./rpc-client";
3
+
4
+ /** History-compression children need the current project plugin's prompt and recognition of their run IDs. */
5
+ export async function handoffHistoryWork(args: {
6
+ storageDir: string;
7
+ directory: string;
8
+ sessionId: string;
9
+ method: string;
10
+ params: Record<string, unknown>;
11
+ readDirectory: () => Promise<string | null>;
12
+ findOwner?: typeof MagicContextRpcClient.findSessionOwner;
13
+ }): Promise<Record<string, unknown> | undefined> {
14
+ const current = await args.readDirectory();
15
+ if (current && sameProjectDirectory(current, args.directory)) return undefined;
16
+ const action = `Reopen this session in its current project directory${current ? ` (${current})` : ""}, then run /ctx-recomp.`;
17
+ if (!current || args.params.historyHandoff === true)
18
+ return {
19
+ ok: false,
20
+ error: `History recovery could not locate a stable owning instance. ${action}`,
21
+ };
22
+ const owner = await (args.findOwner ?? MagicContextRpcClient.findSessionOwner)(
23
+ args.storageDir,
24
+ args.sessionId,
25
+ );
26
+ if (!owner)
27
+ return {
28
+ ok: false,
29
+ error: `History recovery needs the instance for ${current}. ${action}`,
30
+ };
31
+ try {
32
+ return await owner.call(args.method, {
33
+ ...args.params,
34
+ directory: current,
35
+ historyHandoff: true,
36
+ });
37
+ } catch {
38
+ return {
39
+ ok: false,
40
+ error: `The owning instance could not accept history recovery. ${action}`,
41
+ };
42
+ }
43
+ }
@@ -224,7 +224,7 @@ export function createNodeHostStallProfiler(deps: NodeProfilerDeps): HostStallPr
224
224
  const api = await deps.createSession();
225
225
  if (stopped || !existsSync(enablePath)) return;
226
226
  session = api;
227
- api.connect();
227
+ inRootAsyncContext(() => api.connect());
228
228
  await post(api, "Profiler.enable");
229
229
  await post(api, "Profiler.setSamplingInterval", {
230
230
  interval: Math.round(options.sampleIntervalMs * 1000),
@@ -272,3 +272,5 @@ export function createNodeHostStallProfiler(deps: NodeProfilerDeps): HostStallPr
272
272
  isUnavailable: () => unavailable,
273
273
  };
274
274
  }
275
+
276
+ import { inRootAsyncContext } from "./sqlite";
@@ -29,7 +29,7 @@ export function startHostStallProfilerSwitch(): void {
29
29
  now: () => performance.now(),
30
30
  wallNow: () => Date.now(),
31
31
  setInterval: (callback, ms) => {
32
- const handle = setInterval(callback, ms);
32
+ const handle = inRootAsyncContext(() => setInterval(callback, ms));
33
33
  (handle as { unref?: () => void }).unref?.();
34
34
  return handle;
35
35
  },
@@ -55,3 +55,5 @@ export function startHostStallProfilerSwitch(): void {
55
55
  log("[magic-context] host stall profiler: could not start the enable-file switch", error);
56
56
  }
57
57
  }
58
+
59
+ import { inRootAsyncContext } from "./sqlite";
@@ -1,6 +1,7 @@
1
1
  import { afterEach, describe, expect, test } from "bun:test";
2
2
  import {
3
3
  __resetOffThreadMigrationClockForTests,
4
+ __setOffThreadMigrationClockForTests,
4
5
  beginOffThreadMigration,
5
6
  startBootDeadline,
6
7
  } from "./off-thread-migration-clock";
@@ -11,6 +12,49 @@ async function expiredWithin(promise: Promise<void>, ms: number): Promise<boolea
11
12
  return Promise.race([promise.then(() => true), sleep(ms).then(() => false)]);
12
13
  }
13
14
 
15
+ function createFakeClock() {
16
+ let now = 0;
17
+ let nextTimerId = 0;
18
+ const timers = new Map<number, { at: number; callback: () => void }>();
19
+ let timerCountWaiter: { count: number; resolve: () => void } | undefined;
20
+
21
+ return {
22
+ now: () => now,
23
+ setTimeout: (callback: () => void, delayMs: number) => {
24
+ const id = ++nextTimerId;
25
+ timers.set(id, { at: now + Math.max(0, delayMs), callback });
26
+ if (timerCountWaiter?.count === timers.size) {
27
+ timerCountWaiter.resolve();
28
+ timerCountWaiter = undefined;
29
+ }
30
+ return () => {
31
+ timers.delete(id);
32
+ };
33
+ },
34
+ advanceBy: (ms: number) => {
35
+ now += ms;
36
+ while (true) {
37
+ const next = [...timers.entries()].sort(
38
+ (left, right) => left[1].at - right[1].at,
39
+ )[0];
40
+ if (!next || next[1].at > now) return;
41
+ timers.delete(next[0]);
42
+ next[1].callback();
43
+ }
44
+ },
45
+ waitForTimerCount: (count: number) => {
46
+ if (timers.size === count) return Promise.resolve();
47
+ return new Promise<void>((resolve) => {
48
+ timerCountWaiter = { count, resolve };
49
+ });
50
+ },
51
+ };
52
+ }
53
+
54
+ async function isSettled(promise: Promise<void>): Promise<boolean> {
55
+ return Promise.race([promise.then(() => true), Promise.resolve(false)]);
56
+ }
57
+
14
58
  describe("boot deadline that excludes off-thread migration time", () => {
15
59
  afterEach(() => __resetOffThreadMigrationClockForTests());
16
60
 
@@ -19,32 +63,58 @@ describe("boot deadline that excludes off-thread migration time", () => {
19
63
  expect(await expiredWithin(deadline.expired, 500)).toBe(true);
20
64
  });
21
65
 
22
- test("does not expire while a migration runs, and resumes counting afterwards", async () => {
66
+ test("does not count migration time and resumes with only the pre-migration time spent", async () => {
67
+ const clock = createFakeClock();
68
+ __setOffThreadMigrationClockForTests(clock);
23
69
  let deferred = 0;
70
+ let signalDeferred!: () => void;
71
+ const wasDeferred = new Promise<void>((resolve) => {
72
+ signalDeferred = resolve;
73
+ });
24
74
  const deadline = startBootDeadline(40, () => {
25
75
  deferred += 1;
76
+ signalDeferred();
26
77
  });
78
+ clock.advanceBy(15);
27
79
  const end = beginOffThreadMigration();
28
- expect(await expiredWithin(deadline.expired, 150)).toBe(false);
80
+ clock.advanceBy(25);
81
+ await wasDeferred;
29
82
  expect(deferred).toBe(1);
30
83
 
31
- const endedAt = performance.now();
84
+ clock.advanceBy(1_000_000);
85
+ expect(await isSettled(deadline.expired)).toBe(false);
86
+
87
+ const nextTimer = clock.waitForTimerCount(1).then(() => "scheduled" as const);
32
88
  end();
89
+ expect(
90
+ await Promise.race([nextTimer, deadline.expired.then(() => "expired" as const)]),
91
+ ).toBe("scheduled");
92
+
93
+ clock.advanceBy(24);
94
+ expect(await isSettled(deadline.expired)).toBe(false);
95
+ clock.advanceBy(1);
33
96
  await deadline.expired;
34
- // Only the few milliseconds before the migration began were counted, so
35
- // nearly the whole 40ms budget is still left once it ends.
36
- expect(performance.now() - endedAt).toBeGreaterThanOrEqual(25);
37
97
  });
38
98
 
39
99
  test("time between migrations still counts", async () => {
100
+ const clock = createFakeClock();
101
+ __setOffThreadMigrationClockForTests(clock);
40
102
  const deadline = startBootDeadline(60);
41
- await sleep(40);
103
+ clock.advanceBy(40);
42
104
  const end = beginOffThreadMigration();
43
- await sleep(80);
105
+ clock.advanceBy(80);
106
+ expect(await isSettled(deadline.expired)).toBe(false);
107
+
108
+ const nextTimer = clock.waitForTimerCount(1).then(() => "scheduled" as const);
44
109
  end();
45
- const endedAt = performance.now();
110
+ expect(
111
+ await Promise.race([nextTimer, deadline.expired.then(() => "expired" as const)]),
112
+ ).toBe("scheduled");
113
+
114
+ clock.advanceBy(19);
115
+ expect(await isSettled(deadline.expired)).toBe(false);
116
+ clock.advanceBy(1);
46
117
  await deadline.expired;
47
- expect(performance.now() - endedAt).toBeLessThan(45);
48
118
  });
49
119
 
50
120
  test("a cancelled deadline never expires", async () => {
@@ -19,9 +19,26 @@ let activeSince = 0;
19
19
  let accumulatedMs = 0;
20
20
  let idleWaiters: Array<() => void> = [];
21
21
 
22
+ interface TestClock {
23
+ now(): number;
24
+ setTimeout(callback: () => void, delayMs: number): () => void;
25
+ }
26
+
27
+ let testClock: TestClock | undefined;
28
+
29
+ function now(): number {
30
+ return testClock?.now() ?? performance.now();
31
+ }
32
+
33
+ function scheduleTimeout(callback: () => void, delayMs: number): () => void {
34
+ if (testClock) return testClock.setTimeout(callback, delayMs);
35
+ const timer = setTimeout(callback, delayMs);
36
+ return () => clearTimeout(timer);
37
+ }
38
+
22
39
  /** Total milliseconds any off-thread migration has been running, up to `now`. */
23
- export function offThreadMigrationMs(now = performance.now()): number {
24
- return accumulatedMs + (activeMigrations > 0 ? Math.max(0, now - activeSince) : 0);
40
+ export function offThreadMigrationMs(at = now()): number {
41
+ return accumulatedMs + (activeMigrations > 0 ? Math.max(0, at - activeSince) : 0);
25
42
  }
26
43
 
27
44
  export function isOffThreadMigrationRunning(): boolean {
@@ -33,7 +50,7 @@ export function isOffThreadMigrationRunning(): boolean {
33
50
  * end; calling it more than once has no further effect.
34
51
  */
35
52
  export function beginOffThreadMigration(): () => void {
36
- if (activeMigrations === 0) activeSince = performance.now();
53
+ if (activeMigrations === 0) activeSince = now();
37
54
  activeMigrations += 1;
38
55
  let ended = false;
39
56
  return () => {
@@ -41,7 +58,7 @@ export function beginOffThreadMigration(): () => void {
41
58
  ended = true;
42
59
  activeMigrations -= 1;
43
60
  if (activeMigrations > 0) return;
44
- accumulatedMs += Math.max(0, performance.now() - activeSince);
61
+ accumulatedMs += Math.max(0, now() - activeSince);
45
62
  const waiters = idleWaiters;
46
63
  idleWaiters = [];
47
64
  for (const wake of waiters) wake();
@@ -66,10 +83,10 @@ export interface BootDeadlineTimer {
66
83
  * still running.
67
84
  */
68
85
  export function startBootDeadline(timeoutMs: number, onDeferred?: () => void): BootDeadlineTimer {
69
- const startedAt = performance.now();
86
+ const startedAt = now();
70
87
  const migratedAtStart = offThreadMigrationMs(startedAt);
71
88
  const budgetMs = Math.max(0, timeoutMs);
72
- let timer: ReturnType<typeof setTimeout> | undefined;
89
+ let cancelTimer: (() => void) | undefined;
73
90
  let cancelled = false;
74
91
  let deferredReported = false;
75
92
  let resolveExpired!: () => void;
@@ -78,13 +95,14 @@ export function startBootDeadline(timeoutMs: number, onDeferred?: () => void): B
78
95
  });
79
96
 
80
97
  const remainingMs = (): number => {
81
- const now = performance.now();
82
- const countedMs = now - startedAt - (offThreadMigrationMs(now) - migratedAtStart);
98
+ const currentTime = now();
99
+ const countedMs =
100
+ currentTime - startedAt - (offThreadMigrationMs(currentTime) - migratedAtStart);
83
101
  return budgetMs - countedMs;
84
102
  };
85
103
 
86
104
  const check = (): void => {
87
- timer = undefined;
105
+ cancelTimer = undefined;
88
106
  if (cancelled) return;
89
107
  if (isOffThreadMigrationRunning()) {
90
108
  if (!deferredReported) {
@@ -96,28 +114,34 @@ export function startBootDeadline(timeoutMs: number, onDeferred?: () => void): B
96
114
  }
97
115
  const left = remainingMs();
98
116
  if (left > 0) {
99
- timer = setTimeout(check, left);
117
+ cancelTimer = scheduleTimeout(check, left);
100
118
  return;
101
119
  }
102
120
  resolveExpired();
103
121
  };
104
122
 
105
- timer = setTimeout(check, budgetMs);
123
+ cancelTimer = scheduleTimeout(check, budgetMs);
106
124
  return {
107
125
  expired,
108
126
  cancel: () => {
109
127
  cancelled = true;
110
- if (timer) clearTimeout(timer);
111
- timer = undefined;
128
+ cancelTimer?.();
129
+ cancelTimer = undefined;
112
130
  },
113
131
  };
114
132
  }
115
133
 
134
+ /** Inject deterministic time and timers in tests; production uses host APIs. */
135
+ export function __setOffThreadMigrationClockForTests(clock?: TestClock): void {
136
+ testClock = clock;
137
+ }
138
+
116
139
  /** Test seam: forget accumulated migration time between isolated tests. */
117
140
  export function __resetOffThreadMigrationClockForTests(): void {
118
141
  activeMigrations = 0;
119
142
  activeSince = 0;
120
143
  accumulatedMs = 0;
144
+ testClock = undefined;
121
145
  const waiters = idleWaiters;
122
146
  idleWaiters = [];
123
147
  for (const wake of waiters) wake();
@@ -0,0 +1,68 @@
1
+ import { expect, test } from "bun:test";
2
+ import { createProcessedImageEligibility } from "./processed-image-eligibility";
3
+
4
+ const messages = ["old", "image", "answer", "tail"];
5
+
6
+ test("image position age inherits its next positive tag, never zero or an earlier tag", () => {
7
+ const policy = createProcessedImageEligibility(
8
+ messages,
9
+ new Map([
10
+ ["old", 1],
11
+ ["answer", 6],
12
+ ]),
13
+ { watermark: 1 },
14
+ );
15
+ expect(policy.ageTags.get("image")).toBe(6);
16
+ expect(policy.mayStrip("image")).toBe(false);
17
+ expect(policy.ageTags.has("tail")).toBe(false);
18
+ expect(policy.mayStrip("tail")).toBe(false);
19
+ const own = createProcessedImageEligibility(
20
+ messages,
21
+ new Map([
22
+ ["image", 3],
23
+ ["answer", 6],
24
+ ]),
25
+ { watermark: 4 },
26
+ );
27
+ expect(own.ageTags.get("image")).toBe(3);
28
+ expect(own.mayStrip("image")).toBe(true);
29
+ });
30
+
31
+ test("answered image-only positions outside the protected window are eligible", () => {
32
+ const policy = createProcessedImageEligibility(
33
+ messages,
34
+ new Map([
35
+ ["answer", 3],
36
+ ["tail", 5],
37
+ ]),
38
+ { watermark: 4, protectedCutoff: 5, contextPercentage: 94.99 },
39
+ );
40
+ expect(policy.ageTags.get("image")).toBe(3);
41
+ expect(policy.mayStrip("image")).toBe(true);
42
+ });
43
+
44
+ test("image positions inside the protected window stay below the 95 percent emergency", () => {
45
+ const policy = createProcessedImageEligibility(messages, new Map([["answer", 6]]), {
46
+ watermark: 10,
47
+ protectedCutoff: 5,
48
+ contextPercentage: 94.99,
49
+ });
50
+ expect(policy.mayStrip("image")).toBe(false);
51
+ });
52
+
53
+ test("image positions follow emergency drops when the newest window yields at 95 percent", () => {
54
+ for (const contextPercentage of [95, 99]) {
55
+ const policy = createProcessedImageEligibility(messages, new Map([["answer", 6]]), {
56
+ watermark: 10,
57
+ protectedCutoff: 5,
58
+ contextPercentage,
59
+ });
60
+ expect(policy.mayStrip("image")).toBe(true);
61
+ const notDroppedYet = createProcessedImageEligibility(messages, new Map([["answer", 11]]), {
62
+ watermark: 10,
63
+ protectedCutoff: 5,
64
+ contextPercentage,
65
+ });
66
+ expect(notDroppedYet.mayStrip("image")).toBe(false);
67
+ }
68
+ });
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Shared processed-image age and recency policy for both OpenCode adapters.
3
+ * Use the owning message's positive maximum tag; an untagged image-only message
4
+ * inherits the next positive tagged message after it, never age zero or a tag
5
+ * from an earlier message. Without either tag, retain the image at the live tail.
6
+ * A positive dropped-tag watermark must cover that position. Below 95% usage,
7
+ * retain every position at or beyond the protected newest-window cutoff. At
8
+ * 95% and above only that window yields, exactly as for emergency tool drops;
9
+ * independent per-tool protections still apply. Content before retained provider-signed
10
+ * thinking must stay unchanged unless an existing whole-turn rewrite removes that
11
+ * thinking first. Callers enforce these protections, later-assistant/stable-id checks, durable
12
+ * persistence and rewrite-only discovery. Frozen replay bypasses eligibility.
13
+ */
14
+ export function createProcessedImageEligibility<T>(
15
+ messages: readonly T[],
16
+ messageTagNumbers: ReadonlyMap<T, number>,
17
+ options: { watermark: number; protectedCutoff?: number | null; contextPercentage?: number },
18
+ ): { ageTags: ReadonlyMap<T, number>; mayStrip: (message: T) => boolean } {
19
+ const ageTags = new Map<T, number>();
20
+ let nextTag: number | undefined;
21
+ for (let index = messages.length - 1; index >= 0; index--) {
22
+ const message = messages[index];
23
+ const ownTag = messageTagNumbers.get(message);
24
+ if (ownTag !== undefined && Number.isFinite(ownTag) && ownTag > 0) nextTag = ownTag;
25
+ if (nextTag !== undefined) ageTags.set(message, nextTag);
26
+ }
27
+ return {
28
+ ageTags,
29
+ mayStrip: (message) => {
30
+ const age = ageTags.get(message);
31
+ return (
32
+ options.watermark > 0 &&
33
+ age !== undefined &&
34
+ age <= options.watermark &&
35
+ ((options.contextPercentage ?? 0) >= 95 ||
36
+ options.protectedCutoff == null ||
37
+ age < options.protectedCutoff)
38
+ );
39
+ },
40
+ };
41
+ }
@@ -0,0 +1,20 @@
1
+ [
2
+ { "name": "own maximum wins over a later smaller tag", "tags": [[2, 10, 4], [3]], "target": 0, "watermark": 8, "cutoff": null, "percentage": 60, "age": 10, "eligible": false },
3
+ { "name": "own maximum at watermark", "tags": [[2, 10, 4], [30]], "target": 0, "watermark": 10, "cutoff": null, "percentage": 60, "age": 10, "eligible": true },
4
+ { "name": "first later tag not largest later tag", "tags": [[], [3], [20]], "target": 0, "watermark": 3, "cutoff": null, "percentage": 60, "age": 3, "eligible": true },
5
+ { "name": "first later message contributes its own maximum", "tags": [[], [2, 9], [3]], "target": 0, "watermark": 5, "cutoff": null, "percentage": 60, "age": 9, "eligible": false },
6
+ { "name": "skip empty and zero tags when looking forward", "tags": [[1], [], [0], [4]], "target": 1, "watermark": 4, "cutoff": null, "percentage": 60, "age": 4, "eligible": true },
7
+ { "name": "zero own tag inherits later positive", "tags": [[0], [7]], "target": 0, "watermark": 6, "cutoff": null, "percentage": 60, "age": 7, "eligible": false },
8
+ { "name": "preceding tag cannot age live tail", "tags": [[1], [], []], "target": 1, "watermark": 100, "cutoff": null, "percentage": 60, "age": null, "eligible": false },
9
+ { "name": "all untagged is ineligible", "tags": [[], []], "target": 0, "watermark": 100, "cutoff": null, "percentage": 60, "age": null, "eligible": false },
10
+ { "name": "watermark zero even with emergency", "tags": [[], [1]], "target": 0, "watermark": 0, "cutoff": 1, "percentage": 95, "age": 1, "eligible": false },
11
+ { "name": "age above watermark even with emergency", "tags": [[], [7]], "target": 0, "watermark": 6, "cutoff": 1, "percentage": 100, "age": 7, "eligible": false },
12
+ { "name": "cutoff inclusive at 94.99 percent", "tags": [[5], [8]], "target": 0, "watermark": 9, "cutoff": 5, "percentage": 94.99, "age": 5, "eligible": false },
13
+ { "name": "inside cutoff is a range not set membership", "tags": [[], [6]], "target": 0, "watermark": 9, "cutoff": 5, "percentage": 60, "age": 6, "eligible": false },
14
+ { "name": "below cutoff is eligible", "tags": [[4], [8]], "target": 0, "watermark": 9, "cutoff": 5, "percentage": 94.99, "age": 4, "eligible": true },
15
+ { "name": "exactly 95 waives cutoff", "tags": [[], [6]], "target": 0, "watermark": 9, "cutoff": 5, "percentage": 95, "age": 6, "eligible": true },
16
+ { "name": "above 95 waives cutoff", "tags": [[5], [8]], "target": 0, "watermark": 9, "cutoff": 5, "percentage": 95.01, "age": 5, "eligible": true },
17
+ { "name": "emergency cannot invent tail age", "tags": [[1], []], "target": 1, "watermark": 100, "cutoff": 1, "percentage": 100, "age": null, "eligible": false },
18
+ { "name": "own positive tag overrides later greater tag", "tags": [[2], [20]], "target": 0, "watermark": 2, "cutoff": 3, "percentage": 60, "age": 2, "eligible": true },
19
+ { "name": "last message with own tag is aged", "tags": [[1], [4]], "target": 1, "watermark": 4, "cutoff": null, "percentage": 60, "age": 4, "eligible": true }
20
+ ]
@@ -0,0 +1,19 @@
1
+ import { expect, test } from "bun:test";
2
+ import { createProcessedImageEligibility } from "./processed-image-eligibility";
3
+ import cases from "./processed-image-position-review.json";
4
+
5
+ // OpenCode, Rust and Pi can reuse these cases: each row supplies per-message
6
+ // tag numbers rather than provider-specific message objects or image encodings.
7
+ for (const row of cases) {
8
+ test(`independent image position table: ${row.name}`, () => {
9
+ const messages = row.tags.map((_, i) => `message-${i}`);
10
+ const tags = new Map(messages.map((message, i) => [message, Math.max(0, ...row.tags[i])]));
11
+ const policy = createProcessedImageEligibility(messages, tags, {
12
+ watermark: row.watermark,
13
+ protectedCutoff: row.cutoff,
14
+ contextPercentage: row.percentage,
15
+ });
16
+ expect(policy.ageTags.get(messages[row.target]) ?? null).toBe(row.age);
17
+ expect(policy.mayStrip(messages[row.target])).toBe(row.eligible);
18
+ });
19
+ }