@cortexkit/opencode-magic-context 0.49.7 → 0.49.9

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 (252) hide show
  1. package/dist/agents/hidden-agent-registrations.d.ts +5 -3
  2. package/dist/agents/hidden-agent-registrations.d.ts.map +1 -1
  3. package/dist/auto-search-worker.js +6 -6
  4. package/dist/compaction-marker-candidates-worker.js +2 -2
  5. package/dist/{compaction-marker-candidates-a0edbqrr.js → compaction-marker-candidates-xxz35vwp.js} +3 -3
  6. package/dist/config/schema/agent-overrides.d.ts +13 -13
  7. package/dist/config/schema/magic-context.d.ts +500 -500
  8. package/dist/{conflict-warning-hook-myk5hbt9.js → conflict-warning-hook-w3crzrar.js} +8 -8
  9. package/dist/{context-ffr7e0g7.js → context-ey3ap3cc.js} +81 -53
  10. package/dist/{derive-budgets-j4ry8j8n.js → derive-budgets-3nbxsyss.js} +2 -2
  11. package/dist/embedding-backfill-3h2m5n0q.js +21 -0
  12. package/dist/embedding-worker-process.js +6 -2
  13. package/dist/embedding-worker.js +6 -5
  14. package/dist/features/magic-context/compaction-marker-candidates.d.ts.map +1 -1
  15. package/dist/features/magic-context/dreamer/cron.d.ts +2 -0
  16. package/dist/features/magic-context/dreamer/cron.d.ts.map +1 -1
  17. package/dist/features/magic-context/dreamer/provider-output-failure.d.ts +1 -0
  18. package/dist/features/magic-context/dreamer/provider-output-failure.d.ts.map +1 -1
  19. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  20. package/dist/features/magic-context/memory/embedding-worker-client.d.ts.map +1 -1
  21. package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
  22. package/dist/features/magic-context/migration-worker-client.d.ts.map +1 -1
  23. package/dist/features/magic-context/primer-clustering.d.ts.map +1 -1
  24. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  25. package/dist/features/magic-context/smart-notes/compiler-prompt.d.ts +1 -1
  26. package/dist/features/magic-context/smart-notes/compiler-prompt.d.ts.map +1 -1
  27. package/dist/features/magic-context/smart-notes/compiler.d.ts.map +1 -1
  28. package/dist/features/magic-context/smart-notes/wake-plane.d.ts.map +1 -1
  29. package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
  30. package/dist/history-owner-ty29fz0f.js +244 -0
  31. package/dist/hooks/auto-update-checker/checker.d.ts.map +1 -1
  32. package/dist/hooks/magic-context/__fixtures__/tail-hygiene-refresh.d.ts +34 -0
  33. package/dist/hooks/magic-context/__fixtures__/tail-hygiene-refresh.d.ts.map +1 -0
  34. package/dist/hooks/magic-context/auto-search-worker-client.d.ts.map +1 -1
  35. package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
  36. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  37. package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
  38. package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts +1 -0
  39. package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts.map +1 -1
  40. package/dist/hooks/magic-context/dropped-input-guard.d.ts.map +1 -1
  41. package/dist/hooks/magic-context/edit-marker.d.ts +2 -0
  42. package/dist/hooks/magic-context/edit-marker.d.ts.map +1 -1
  43. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +2 -0
  44. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
  45. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  46. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  47. package/dist/hooks/magic-context/module-transport.d.ts +1 -0
  48. package/dist/hooks/magic-context/module-transport.d.ts.map +1 -1
  49. package/dist/hooks/magic-context/provider-visible-parts.d.ts +10 -4
  50. package/dist/hooks/magic-context/provider-visible-parts.d.ts.map +1 -1
  51. package/dist/hooks/magic-context/raw-ordinal-warmup.d.ts.map +1 -1
  52. package/dist/hooks/magic-context/read-session-chunk.d.ts +8 -4
  53. package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
  54. package/dist/hooks/magic-context/rust-mode-transform.d.ts +1 -0
  55. package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
  56. package/dist/hooks/magic-context/rust-refusal-recovery.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/supersession-reclaim.d.ts +5 -3
  58. package/dist/hooks/magic-context/supersession-reclaim.d.ts.map +1 -1
  59. package/dist/hooks/magic-context/tail-hygiene-output-count.d.ts +18 -0
  60. package/dist/hooks/magic-context/tail-hygiene-output-count.d.ts.map +1 -0
  61. package/dist/hooks/magic-context/tail-hygiene-walk.d.ts +5 -1
  62. package/dist/hooks/magic-context/tail-hygiene-walk.d.ts.map +1 -1
  63. package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
  64. package/dist/hooks/magic-context/tool-reclaim.d.ts +5 -2
  65. package/dist/hooks/magic-context/tool-reclaim.d.ts.map +1 -1
  66. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +1 -0
  67. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  68. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  69. package/dist/hooks/magic-context/unresolved-history-boundary.d.ts +5 -1
  70. package/dist/hooks/magic-context/unresolved-history-boundary.d.ts.map +1 -1
  71. package/dist/hooks/magic-context/wrapup-orchestrator.d.ts.map +1 -1
  72. package/dist/{index-g8qbdb03.js → index-0jz47061.js} +6 -3
  73. package/dist/{index-wq16ahsn.js → index-0x9cn9w1.js} +8 -8
  74. package/dist/{index-4mjhqadz.js → index-150zz6fh.js} +1 -1
  75. package/dist/{index-n7kwfw2r.js → index-16vwdyy5.js} +7 -7
  76. package/dist/{index-rwmpkss5.js → index-21919gcq.js} +4 -4
  77. package/dist/{index-5tbvdff4.js → index-3403gbzj.js} +200 -135
  78. package/dist/{index-0gyq3djm.js → index-4hb8nt93.js} +3 -3
  79. package/dist/{index-ebnngykq.js → index-6k86es2p.js} +142 -27
  80. package/dist/{index-ax2jhztn.js → index-7bjgyaj1.js} +4 -4
  81. package/dist/{index-h2wc01qk.js → index-9jvab89d.js} +2 -2
  82. package/dist/{index-w6bd03dc.js → index-a43xq1pg.js} +5 -5
  83. package/dist/{index-tqf7yg27.js → index-a4b0gbyg.js} +1 -1
  84. package/dist/{index-j5hce1ab.js → index-a865mj02.js} +5 -5
  85. package/dist/{index-37q80veb.js → index-angx1s0k.js} +4 -3
  86. package/dist/{index-z1tjp257.js → index-b64n4d0h.js} +1 -1
  87. package/dist/{index-gbmqa9x9.js → index-bmwfzz28.js} +9 -6
  88. package/dist/{index-tmtty2x5.js → index-f8cn4xqa.js} +290 -61
  89. package/dist/{index-j58gqrn2.js → index-ffa8x2as.js} +31 -24
  90. package/dist/{index-1bt88epp.js → index-g7jkrz0x.js} +36 -16
  91. package/dist/{index-qx53vezw.js → index-gjvjh960.js} +1 -1
  92. package/dist/{index-w23dh319.js → index-jw4gbfkv.js} +1 -1
  93. package/dist/index-knvkbfvh.js +25 -0
  94. package/dist/{index-vkbqdnkj.js → index-kvw6xew3.js} +6 -3
  95. package/dist/{index-rzmjkdd6.js → index-mp2kghkv.js} +3 -27
  96. package/dist/{index-nsjyysj0.js → index-mvvephtj.js} +5 -1
  97. package/dist/{index-9yyqq3mm.js → index-n78avt2p.js} +1 -1
  98. package/dist/{index-w6vr754t.js → index-nenz4gpb.js} +13 -9
  99. package/dist/{index-vt4sf4ep.js → index-pgy3h49n.js} +4 -4
  100. package/dist/{v2/server-1rfgbjfn.js → index-q28hsbpg.js} +15 -3
  101. package/dist/{index-a8acx4gc.js → index-q76br5zn.js} +3 -3
  102. package/dist/{index-sf94at64.js → index-rg1cezmj.js} +22 -22
  103. package/dist/{index-sh91t19a.js → index-s178h9z8.js} +4 -3
  104. package/dist/{index-4btknbqh.js → index-swgpe402.js} +48 -38
  105. package/dist/{index-k6xvbj2e.js → index-w28y7d0z.js} +2 -0
  106. package/dist/{index-j8epsc0g.js → index-wafzvyhg.js} +33 -10
  107. package/dist/{index-n5a170gs.js → index-wpwpn7nj.js} +166 -63
  108. package/dist/{index-tagss15p.js → index-x57ep4fp.js} +124 -63
  109. package/dist/index.d.ts.map +1 -1
  110. package/dist/index.js +84 -79
  111. package/dist/{logger-ewak5q1s.js → logger-wn328tfg.js} +1 -1
  112. package/dist/{message-index-rqtse28y.js → message-index-6r1x9cqh.js} +16 -16
  113. package/dist/{message-index-bounded-04rycpx4.js → message-index-bounded-fskdyc81.js} +16 -16
  114. package/dist/{message-time-backfill-a5ytnp53.js → message-time-backfill-nb5hhnrt.js} +3 -3
  115. package/dist/migration-worker.js +11 -11
  116. package/dist/plugin/boot-quiet.d.ts.map +1 -1
  117. package/dist/plugin/dream-timer.d.ts.map +1 -1
  118. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  119. package/dist/plugin/rpc-handlers.d.ts +2 -0
  120. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  121. package/dist/{project-embedding-registry-k0sd9k7v.js → project-embedding-registry-vy7sv9jt.js} +11 -11
  122. package/dist/raw-ordinal-warmup-worker.js +6 -6
  123. package/dist/{read-session-chunk-h1btx938.js → read-session-chunk-zgpvs423.js} +11 -11
  124. package/dist/{read-session-raw-vdf4wh78.js → read-session-raw-gm6xj30v.js} +3 -3
  125. package/dist/recomp-orchestrator-77xc8wpe.js +60 -0
  126. package/dist/{safe-notification-target-q0cyrx3x.js → safe-notification-target-jdynm677.js} +2 -2
  127. package/dist/{search-3rbdx6hw.js → search-72fkjwk2.js} +24 -24
  128. package/dist/{send-session-notification-2tp44tfa.js → send-session-notification-q8ngbap3.js} +6 -6
  129. package/dist/shared/hidden-zen-read.d.ts +24 -0
  130. package/dist/shared/hidden-zen-read.d.ts.map +1 -0
  131. package/dist/shared/history-owner.d.ts +12 -0
  132. package/dist/shared/history-owner.d.ts.map +1 -0
  133. package/dist/shared/off-thread-migration-clock.d.ts +8 -1
  134. package/dist/shared/off-thread-migration-clock.d.ts.map +1 -1
  135. package/dist/shared/reclaim-distance.d.ts +33 -0
  136. package/dist/shared/reclaim-distance.d.ts.map +1 -0
  137. package/dist/shared/redaction.d.ts.map +1 -1
  138. package/dist/shared/rpc-server.d.ts.map +1 -1
  139. package/dist/shared/sqlite.d.ts +2 -0
  140. package/dist/shared/sqlite.d.ts.map +1 -1
  141. package/dist/shared/stable-json.d.ts +7 -0
  142. package/dist/shared/stable-json.d.ts.map +1 -1
  143. package/dist/shared/tail-hygiene-json.d.ts +22 -0
  144. package/dist/shared/tail-hygiene-json.d.ts.map +1 -0
  145. package/dist/shared/transcript.d.ts +5 -0
  146. package/dist/shared/transcript.d.ts.map +1 -1
  147. package/dist/shared/user-facing-codes.d.ts +2 -2
  148. package/dist/{sqlite-j3tvg8r0.js → sqlite-4pqyynrs.js} +3 -1
  149. package/dist/{storage-memory-8168bgef.js → storage-memory-j3v3hc83.js} +2 -2
  150. package/dist/{storage-tags-61z5j2f9.js → storage-tags-35m4gms2.js} +5 -5
  151. package/dist/{transform-postprocess-phase-7qts7h54.js → transform-postprocess-phase-r8d406j0.js} +30 -30
  152. package/dist/v2/auto-search-worker.js +6 -6
  153. package/dist/v2/{compaction-marker-candidates-6nypjkpf.js → compaction-marker-candidates-a15w3mqh.js} +3 -3
  154. package/dist/v2/compaction-marker-candidates-worker.js +2 -2
  155. package/dist/v2/{context-xq4rtywr.js → context-z2yagwy4.js} +293 -91
  156. package/dist/v2/{derive-budgets-enckmg7y.js → derive-budgets-0rmpmxjw.js} +3 -3
  157. package/dist/v2/embedding-backfill-vr7wdtrf.js +20 -0
  158. package/dist/v2/embedding-worker-process.js +6 -2
  159. package/dist/v2/embedding-worker.js +6 -5
  160. package/dist/v2/hidden-completion.d.ts.map +1 -1
  161. package/dist/v2/history-owner-2cr9py37.js +243 -0
  162. package/dist/v2/hooks/context.d.ts.map +1 -1
  163. package/dist/v2/hooks/hidden-child.d.ts +2 -2
  164. package/dist/v2/hooks/hidden-child.d.ts.map +1 -1
  165. package/dist/v2/hooks/store.d.ts +1 -0
  166. package/dist/v2/hooks/store.d.ts.map +1 -1
  167. package/dist/v2/{logger-pj02s7sr.js → logger-te6e1j72.js} +1 -1
  168. package/dist/v2/{message-index-8e5z2xx8.js → message-index-9ezq6wze.js} +16 -16
  169. package/dist/v2/{message-index-bounded-5vh1h2g7.js → message-index-bounded-rqc603f2.js} +16 -16
  170. package/dist/v2/{message-time-backfill-2z365239.js → message-time-backfill-rz1pnpaj.js} +3 -3
  171. package/dist/v2/migration-worker.js +11 -11
  172. package/dist/v2/{project-embedding-registry-b77awytr.js → project-embedding-registry-ja23aygk.js} +11 -11
  173. package/dist/v2/raw-ordinal-warmup-worker.js +6 -6
  174. package/dist/v2/{read-session-chunk-4sat83cx.js → read-session-chunk-7f87tg2q.js} +11 -11
  175. package/dist/v2/{read-session-raw-a3hw9jsp.js → read-session-raw-h4azh85b.js} +3 -3
  176. package/dist/v2/recomp-orchestrator-s5tdpsbs.js +59 -0
  177. package/dist/v2/{safe-notification-target-y3p1hce9.js → safe-notification-target-936374xx.js} +2 -2
  178. package/dist/v2/search-2td1b1wy.js +46 -0
  179. package/dist/v2/{send-session-notification-ppgc8qdz.js → send-session-notification-6n7chhyp.js} +6 -6
  180. package/dist/v2/{server-v4fwha43.js → server-23znwjqk.js} +48 -38
  181. package/dist/v2/{server-nfan6zb6.js → server-2hxypdx5.js} +31 -24
  182. package/dist/v2/{server-ras2sxnc.js → server-44ryn9rb.js} +9 -6
  183. package/dist/v2/{server-fw31krgq.js → server-4easnf78.js} +19 -19
  184. package/dist/v2/{server-d6g4whc2.js → server-4qr18rfg.js} +3 -3
  185. package/dist/v2/{server-sy4b1sra.js → server-4rs8sm06.js} +5 -5
  186. package/dist/v2/{server-jdwd03ep.js → server-4zwx20yg.js} +7 -7
  187. package/dist/v2/{server-brhj8qtd.js → server-63ha7f0f.js} +3 -27
  188. package/dist/v2/{server-3t0q2cqx.js → server-7dv9wt9r.js} +1 -1
  189. package/dist/v2/{server-cw6521wn.js → server-7e087rvk.js} +2 -0
  190. package/dist/v2/{server-6byqfbqf.js → server-89fcbe37.js} +36 -16
  191. package/dist/v2/{server-hb6xhh9q.js → server-988cqjme.js} +6 -3
  192. package/dist/v2/{server-fpqe39as.js → server-9a6bwc12.js} +4 -4
  193. package/dist/v2/{server-t4k4qexw.js → server-9e5r8y60.js} +5 -1
  194. package/dist/v2/{server-qcw43339.js → server-ak7w74j9.js} +4 -4
  195. package/dist/v2/{server-dd0nja67.js → server-bx59ezg6.js} +4 -3
  196. package/dist/v2/{server-mv1w7t6z.js → server-c9w52qss.js} +99 -43
  197. package/dist/v2/{server-676xw7at.js → server-ckrb5rm8.js} +1 -1
  198. package/dist/{index-w4mw92w3.js → v2/server-cxsr0ng6.js} +15 -3
  199. package/dist/v2/{server-hcvcxzx7.js → server-f27he3j1.js} +5 -5
  200. package/dist/v2/{server-gtv9wbhq.js → server-gc8x2xdb.js} +144 -63
  201. package/dist/v2/{server-t528t8qm.js → server-hn9fb3v0.js} +2 -2
  202. package/dist/v2/{server-w3zeywp1.js → server-kmapq3v0.js} +289 -60
  203. package/dist/v2/server-knvkbfvh.js +25 -0
  204. package/dist/v2/{server-9yyqq3mm.js → server-n78avt2p.js} +1 -1
  205. package/dist/v2/{server-kdrezdy0.js → server-njnx39q2.js} +6 -3
  206. package/dist/v2/{server-x08d1xf3.js → server-npm52h2q.js} +4 -3
  207. package/dist/v2/{server-vb7pbd9p.js → server-nveratm5.js} +1 -1
  208. package/dist/v2/{server-kzns2hbz.js → server-s6jqh8bj.js} +1 -1
  209. package/dist/v2/{server-j8epsc0g.js → server-wafzvyhg.js} +33 -10
  210. package/dist/v2/{server-7ekna9zs.js → server-xqf2d2mg.js} +2 -2
  211. package/dist/v2/{server-7srtwsa4.js → server-z0rjemg1.js} +9 -9
  212. package/dist/v2/{server-wkdgjjch.js → server-zrwaj29a.js} +2 -2
  213. package/dist/v2/server.js +7 -6
  214. package/dist/v2/{sqlite-j3tvg8r0.js → sqlite-4pqyynrs.js} +3 -1
  215. package/dist/v2/{storage-memory-8168bgef.js → storage-memory-j3v3hc83.js} +2 -2
  216. package/dist/v2/{storage-tags-j2fzq3sw.js → storage-tags-c4zfcxv4.js} +5 -5
  217. package/dist/v2/store-reader.d.ts.map +1 -1
  218. package/dist/v2/{wrapup-orchestrator-s92ebfzv.js → wrapup-orchestrator-wg8dmmm9.js} +38 -35
  219. package/dist/wrapup-orchestrator-b1z99yg5.js +47 -0
  220. package/package.json +1 -1
  221. package/src/agents/hidden-agent-registrations.ts +22 -4
  222. package/src/config/compaction-accessor-guard.test.ts +3 -0
  223. package/src/features/magic-context/dreamer/cron.ts +8 -0
  224. package/src/shared/hidden-zen-read.test.ts +99 -0
  225. package/src/shared/hidden-zen-read.ts +88 -0
  226. package/src/shared/history-owner.test.ts +60 -0
  227. package/src/shared/history-owner.ts +43 -0
  228. package/src/shared/host-stall-profiler-node.ts +3 -1
  229. package/src/shared/host-stall-profiler.ts +3 -1
  230. package/src/shared/off-thread-migration-clock.test.ts +80 -10
  231. package/src/shared/off-thread-migration-clock.ts +37 -13
  232. package/src/shared/reclaim-distance.test.ts +111 -0
  233. package/src/shared/reclaim-distance.ts +91 -0
  234. package/src/shared/redaction.test.ts +16 -0
  235. package/src/shared/redaction.ts +8 -1
  236. package/src/shared/root-async-context.probe.mjs +36 -0
  237. package/src/shared/root-async-context.test.ts +49 -0
  238. package/src/shared/rpc-server.ts +27 -23
  239. package/src/shared/sqlite-busy-budget-review.test.ts +291 -0
  240. package/src/shared/sqlite-foreground-wait.test.ts +253 -1
  241. package/src/shared/sqlite.ts +55 -14
  242. package/src/shared/stable-json.ts +56 -14
  243. package/src/shared/tail-hygiene-json.test.ts +117 -0
  244. package/src/shared/tail-hygiene-json.ts +217 -0
  245. package/src/shared/transcript.ts +7 -0
  246. package/src/shared/user-facing-codes.ts +2 -2
  247. package/dist/embedding-backfill-re16xncd.js +0 -21
  248. package/dist/recomp-orchestrator-trcgmm23.js +0 -60
  249. package/dist/v2/embedding-backfill-xd9ygzgd.js +0 -20
  250. package/dist/v2/recomp-orchestrator-n11zv1f1.js +0 -59
  251. package/dist/v2/search-mvgqtkkz.js +0 -46
  252. package/dist/wrapup-orchestrator-df61jmz9.js +0 -47
@@ -0,0 +1,99 @@
1
+ import { expect, test } from "bun:test";
2
+ import {
3
+ needsBlockedRead,
4
+ rememberV1HiddenRun,
5
+ shapeV1HiddenMessage,
6
+ withBlockedReadInstruction,
7
+ } from "./hidden-zen-read";
8
+
9
+ for (const [agent, task, sentence] of [
10
+ [
11
+ "historian",
12
+ "initial",
13
+ "Summarize only the supplied conversation and memory context. Do not call tools.",
14
+ ],
15
+ [
16
+ "historian-recomp",
17
+ "repair",
18
+ "Summarize only the supplied conversation and memory context. Do not call tools.",
19
+ ],
20
+ [
21
+ "historian-editor",
22
+ "fallback",
23
+ "Summarize only the supplied conversation and memory context. Do not call tools.",
24
+ ],
25
+ [
26
+ "dreamer-classifier",
27
+ "classify-memories",
28
+ "Classify only the supplied memory context. Do not call tools.",
29
+ ],
30
+ [
31
+ "dreamer-classifier",
32
+ "compress-cues",
33
+ "Compress only the supplied memory cues. Do not call tools.",
34
+ ],
35
+ [
36
+ "smart-note-compiler",
37
+ "compile",
38
+ "Compile only the supplied conditions and memory context. Do not call tools.",
39
+ ],
40
+ [
41
+ "dreamer-reviewer",
42
+ "review-user-memories",
43
+ "Review only the supplied observations and memory context. Do not call tools.",
44
+ ],
45
+ ]) {
46
+ test(`Zen compatibility is provider-scoped and prompt-guided for ${agent}/${task}`, () => {
47
+ expect(needsBlockedRead(agent!, "opencode")).toBe(true);
48
+ for (const provider of ["anthropic", "openai", "opencode-zen", undefined])
49
+ expect(needsBlockedRead(agent!, provider)).toBe(false);
50
+ expect(
51
+ withBlockedReadInstruction("original prompt\n", { agent: agent!, metadata: { task } }),
52
+ ).toBe(`original prompt\n\n\n${sentence}`);
53
+ expect(withBlockedReadInstruction(sentence!, { agent: agent!, metadata: { task } })).toBe(
54
+ sentence!,
55
+ );
56
+ });
57
+ }
58
+
59
+ test("genuine-tool agents and user agents never receive the compatibility read", () => {
60
+ for (const agent of [
61
+ "dreamer",
62
+ "dreamer-docs",
63
+ "dreamer-memory-mapper",
64
+ "dreamer-primer-investigator",
65
+ "dreamer-retrospective",
66
+ "build",
67
+ "custom",
68
+ ])
69
+ expect(needsBlockedRead(agent, "opencode")).toBe(false);
70
+ });
71
+
72
+ test("v1 uses the host-resolved default model only on a registered hidden session", () => {
73
+ const message = { agent: "historian", model: { providerID: "opencode" }, system: "original" };
74
+ shapeV1HiddenMessage("user", message);
75
+ expect(message).toEqual({
76
+ agent: "historian",
77
+ model: { providerID: "opencode" },
78
+ system: "original",
79
+ });
80
+ rememberV1HiddenRun("child", {
81
+ agent: "historian",
82
+ kind: "historian",
83
+ system: "original",
84
+ title: "test",
85
+ directory: "/project",
86
+ timeoutMs: 1000,
87
+ });
88
+ try {
89
+ shapeV1HiddenMessage("child", message);
90
+ expect(message.agent).toBe("historian-zen-blocked-read");
91
+ expect(message.system).toBe(
92
+ "original\n\nSummarize only the supplied conversation and memory context. Do not call tools.",
93
+ );
94
+ shapeV1HiddenMessage("child", message);
95
+ expect(message.system.match(/Do not call tools\./g)).toHaveLength(1);
96
+ } finally {
97
+ rememberV1HiddenRun("child", undefined);
98
+ }
99
+ });
@@ -0,0 +1,88 @@
1
+ import type { HiddenRunIdentity } from "../hooks/magic-context/compartment-runner-types";
2
+
3
+ /** These agents transform supplied text; agents that inspect code or memories keep their existing tools. */
4
+ export function isToolFreeHiddenAgent(agent: string): boolean {
5
+ return [
6
+ "historian",
7
+ "historian-recomp",
8
+ "historian-editor",
9
+ "dreamer-classifier",
10
+ "dreamer-reviewer",
11
+ "smart-note-compiler",
12
+ ].includes(agent);
13
+ }
14
+
15
+ export const TOOL_USE_DISABLED = "tool use is disabled for this task";
16
+ export const ZEN_READ_AGENT_SUFFIX = "-zen-blocked-read";
17
+
18
+ export function needsBlockedRead(agent: string, providerID: string | undefined): boolean {
19
+ return providerID === "opencode" && isToolFreeHiddenAgent(agent);
20
+ }
21
+
22
+ export function blockedReadInstruction(run: Pick<HiddenRunIdentity, "agent" | "metadata">): string {
23
+ if (run.agent.startsWith("historian"))
24
+ return "Summarize only the supplied conversation and memory context. Do not call tools.";
25
+ if (run.metadata?.task === "compress-cues")
26
+ return "Compress only the supplied memory cues. Do not call tools.";
27
+ if (run.agent === "smart-note-compiler")
28
+ return "Compile only the supplied conditions and memory context. Do not call tools.";
29
+ if (run.agent === "dreamer-reviewer")
30
+ return "Review only the supplied observations and memory context. Do not call tools.";
31
+ return "Classify only the supplied memory context. Do not call tools.";
32
+ }
33
+
34
+ export function withBlockedReadInstruction(
35
+ system: string,
36
+ run: Parameters<typeof blockedReadInstruction>[0],
37
+ ): string {
38
+ const instruction = blockedReadInstruction(run);
39
+ return system.endsWith(instruction) ? system : `${system}${system ? "\n\n" : ""}${instruction}`;
40
+ }
41
+
42
+ // Only sessions opened by Magic Context's hidden executor can have tool calls
43
+ // blocked in OpenCode 1. Agent names and user permissions never select user sessions.
44
+ const blockedSessions = new Set<string>();
45
+ const v1Runs = new Map<string, HiddenRunIdentity>();
46
+
47
+ export function rememberV1HiddenRun(sessionID: string, run: HiddenRunIdentity | undefined): void {
48
+ if (run) v1Runs.set(sessionID, run);
49
+ else {
50
+ v1Runs.delete(sessionID);
51
+ setBlockedReadSession(sessionID, false);
52
+ }
53
+ }
54
+
55
+ /**
56
+ * OpenCode 1 calls chat.message after selecting the request's actual model.
57
+ * Select the read-bearing carrier there so agent defaults and fallback models
58
+ * cannot accidentally apply Zen's compatibility surface to another provider.
59
+ */
60
+ export function shapeV1HiddenMessage(
61
+ sessionID: string,
62
+ message: {
63
+ agent?: string;
64
+ model?: { providerID: string };
65
+ system?: string;
66
+ },
67
+ ): void {
68
+ const run = v1Runs.get(sessionID);
69
+ if (!run) return;
70
+ const agent = message.agent ?? run.agent;
71
+ const base = agent.endsWith(ZEN_READ_AGENT_SUFFIX)
72
+ ? agent.slice(0, -ZEN_READ_AGENT_SUFFIX.length)
73
+ : agent;
74
+ const enabled = needsBlockedRead(base, message.model?.providerID);
75
+ setBlockedReadSession(sessionID, enabled);
76
+ if (!enabled) return;
77
+ message.agent = `${base}${ZEN_READ_AGENT_SUFFIX}`;
78
+ message.system = withBlockedReadInstruction(message.system ?? "", run);
79
+ }
80
+
81
+ export function setBlockedReadSession(sessionID: string, enabled: boolean): void {
82
+ if (enabled) blockedSessions.add(sessionID);
83
+ else blockedSessions.delete(sessionID);
84
+ }
85
+
86
+ export function blockedReadSessionRefusal(sessionID: string): string | undefined {
87
+ return blockedSessions.has(sessionID) ? TOOL_USE_DISABLED : undefined;
88
+ }
@@ -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,111 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import {
3
+ farBehindToolBookmark,
4
+ isReclaimUserTurn,
5
+ type ReclaimDistanceEntry,
6
+ } from "./reclaim-distance";
7
+
8
+ const output = (tag: number, tokens = 1000): ReclaimDistanceEntry => ({
9
+ toolOutputTag: tag,
10
+ tokens,
11
+ });
12
+ const turn = (): ReclaimDistanceEntry => ({ userTurn: true, tokens: 10 });
13
+
14
+ describe("reclaim distance", () => {
15
+ it("repeated rebuilds cannot age the current task", () => {
16
+ const entries = [turn(), ...Array.from({ length: 28 }, (_, i) => output(i + 1))];
17
+ for (const count of [2, 3, 5]) {
18
+ let bookmark = 0;
19
+ for (let pass = 0; pass < count + 1; pass++) {
20
+ expect(bookmark).toBe(0);
21
+ bookmark = Math.max(bookmark, farBehindToolBookmark(entries, 200_000));
22
+ }
23
+ }
24
+ });
25
+
26
+ it("three newer user turns establish a bookmark, not two", () => {
27
+ const entries = [turn(), output(1), turn(), output(2), turn()];
28
+ expect(farBehindToolBookmark(entries, 200_000)).toBe(0);
29
+ expect(farBehindToolBookmark([...entries, turn()], 200_000)).toBe(1);
30
+ });
31
+
32
+ it("single-turn tool loop ages at exactly a quarter of the usable window", () => {
33
+ const entries = [turn(), output(1), output(2, 49_999)];
34
+ expect(farBehindToolBookmark(entries, 200_000)).toBe(0);
35
+ expect(farBehindToolBookmark([...entries, { tokens: 1 }], 200_000)).toBe(1);
36
+ expect(farBehindToolBookmark([turn(), output(1, 100_000)], 200_000)).toBe(0);
37
+ });
38
+
39
+ it("multipart outputs never age from their own trailing chunks", () => {
40
+ expect(farBehindToolBookmark([output(1), output(1, 60_000)], 200_000)).toBe(0);
41
+ });
42
+
43
+ it("unknown windows disable only token distance", () => {
44
+ expect(farBehindToolBookmark([output(1), output(2, 99_999)], 0)).toBe(0);
45
+ expect(farBehindToolBookmark([output(1), turn(), turn(), turn()], 0)).toBe(1);
46
+ });
47
+
48
+ it("excludes synthetic host wakes, MC reminders, heads and result carriers", () => {
49
+ expect(
50
+ isReclaimUserTurn({
51
+ role: "user",
52
+ parts: [
53
+ {
54
+ kind: "text",
55
+ synthetic: true,
56
+ text: "<system-reminder>Background task finished.</system-reminder>",
57
+ },
58
+ ],
59
+ }),
60
+ ).toBe(false);
61
+ expect(
62
+ isReclaimUserTurn({
63
+ role: "user",
64
+ parts: [
65
+ {
66
+ kind: "text",
67
+ text: "<system-reminder>Your next step: call ctx_reduce on the outputs you've already used (5).</system-reminder>",
68
+ },
69
+ ],
70
+ }),
71
+ ).toBe(false);
72
+ expect(
73
+ isReclaimUserTurn({
74
+ role: "user",
75
+ syntheticHead: true,
76
+ parts: [{ kind: "text", text: "history" }],
77
+ }),
78
+ ).toBe(false);
79
+ expect(
80
+ isReclaimUserTurn({ role: "user", parts: [{ kind: "tool_result", text: "output" }] }),
81
+ ).toBe(false);
82
+ expect(
83
+ isReclaimUserTurn({
84
+ role: "user",
85
+ parts: [
86
+ { kind: "tool_result", text: "output" },
87
+ { kind: "text", text: "continue" },
88
+ ],
89
+ }),
90
+ ).toBe(true);
91
+ });
92
+
93
+ it("counts an authored part even after synthetic or ignored parts", () => {
94
+ for (const provenance of [{ synthetic: true }, { ignored: true }]) {
95
+ expect(
96
+ isReclaimUserTurn({ role: "user", parts: [{ kind: "image", ...provenance }] }),
97
+ ).toBe(false);
98
+ expect(
99
+ isReclaimUserTurn({
100
+ role: "user",
101
+ parts: [{ kind: "text", text: "notice", ...provenance }, { kind: "image" }],
102
+ }),
103
+ ).toBe(true);
104
+ }
105
+ });
106
+
107
+ it("a young lower-tag result holds back distant higher tags", () => {
108
+ expect(farBehindToolBookmark([output(2), output(3, 50_000), output(1)], 200_000)).toBe(0);
109
+ expect(farBehindToolBookmark([output(1), output(3, 50_000), output(2)], 200_000)).toBe(1);
110
+ });
111
+ });