@cortexkit/opencode-magic-context 0.49.35 → 0.49.37

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 (202) hide show
  1. package/README.md +1 -1
  2. package/dist/auto-search-worker.js +163 -160
  3. package/dist/chunk-embedding-write-worker.js +58 -56
  4. package/dist/{compaction-marker-candidates-g0p67ced.js → compaction-marker-candidates-5p1qkve9.js} +3 -1
  5. package/dist/compaction-marker-candidates-worker.js +24 -23
  6. package/dist/{compartment-chunk-embedding-y4136are.js → compartment-chunk-embedding-ypktm0tk.js} +5 -4
  7. package/dist/{conflict-warning-hook-hhw8z5ak.js → conflict-warning-hook-b75jn458.js} +6 -5
  8. package/dist/{context-fk8e3h9p.js → context-ngk2jz60.js} +501 -97
  9. package/dist/{embedding-backfill-q6n72sy0.js → embedding-backfill-rma0v2ct.js} +9 -8
  10. package/dist/embedding-worker.js +2 -2
  11. package/dist/features/magic-context/compaction-marker-candidates.d.ts.map +1 -1
  12. package/dist/features/magic-context/compaction-marker.d.ts +5 -3
  13. package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
  14. package/dist/features/magic-context/dreamer/task-gates.d.ts.map +1 -1
  15. package/dist/features/magic-context/dreamer/verify-gate.d.ts +2 -0
  16. package/dist/features/magic-context/dreamer/verify-gate.d.ts.map +1 -1
  17. package/dist/features/magic-context/dreamer/verify-state.d.ts +9 -0
  18. package/dist/features/magic-context/dreamer/verify-state.d.ts.map +1 -0
  19. package/dist/features/magic-context/dreamer/verify.d.ts +2 -1
  20. package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
  21. package/dist/features/magic-context/migration-worker-client.d.ts +3 -0
  22. package/dist/features/magic-context/migration-worker-client.d.ts.map +1 -1
  23. package/dist/features/magic-context/migration-worker.d.ts +15 -0
  24. package/dist/features/magic-context/migration-worker.d.ts.map +1 -1
  25. package/dist/features/magic-context/overflow-detection.d.ts +2 -2
  26. package/dist/features/magic-context/overflow-detection.d.ts.map +1 -1
  27. package/dist/features/magic-context/search-rerank.d.ts.map +1 -1
  28. package/dist/features/magic-context/storage-meta-persisted.d.ts +1 -0
  29. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  30. package/dist/features/magic-context/storage-tags.d.ts +4 -0
  31. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  32. package/dist/features/magic-context/work-metrics-carry-storage.d.ts +10 -0
  33. package/dist/features/magic-context/work-metrics-carry-storage.d.ts.map +1 -0
  34. package/dist/hooks/magic-context/auto-search-worker-client.d.ts +5 -1
  35. package/dist/hooks/magic-context/auto-search-worker-client.d.ts.map +1 -1
  36. package/dist/hooks/magic-context/compaction-marker-publication.d.ts.map +1 -1
  37. package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
  38. package/dist/hooks/magic-context/issue-423-fixture.d.ts +1 -1
  39. package/dist/hooks/magic-context/issue-423-fixture.d.ts.map +1 -1
  40. package/dist/hooks/magic-context/issue-424-fixture.d.ts +1 -1
  41. package/dist/hooks/magic-context/issue-424-fixture.d.ts.map +1 -1
  42. package/dist/hooks/magic-context/project-structural-sentinels.d.ts +25 -0
  43. package/dist/hooks/magic-context/project-structural-sentinels.d.ts.map +1 -0
  44. package/dist/hooks/magic-context/protected-tail-boundary.d.ts +8 -1
  45. package/dist/hooks/magic-context/protected-tail-boundary.d.ts.map +1 -1
  46. package/dist/hooks/magic-context/raw-ordinal-warmup.d.ts.map +1 -1
  47. package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts +2 -0
  48. package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
  49. package/dist/hooks/magic-context/release-recovery-prefix.d.ts +9 -0
  50. package/dist/hooks/magic-context/release-recovery-prefix.d.ts.map +1 -0
  51. package/dist/hooks/magic-context/strip-structural-noise.d.ts +6 -3
  52. package/dist/hooks/magic-context/strip-structural-noise.d.ts.map +1 -1
  53. package/dist/hooks/magic-context/transform-context-state.d.ts +3 -0
  54. package/dist/hooks/magic-context/transform-context-state.d.ts.map +1 -1
  55. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +3 -0
  56. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  58. package/dist/{index-cpjk5jd2.js → index-1d042x6w.js} +16 -14
  59. package/dist/{index-jn0ddrhp.js → index-21fgrfrf.js} +8 -6
  60. package/dist/{index-pq0dpwrc.js → index-2rs7912w.js} +2 -2
  61. package/dist/{index-ezv6dz3y.js → index-4ks89h5a.js} +4 -4
  62. package/dist/{index-vzhhr1ht.js → index-4y35msh5.js} +1 -2
  63. package/dist/{index-f297z1n8.js → index-72czy45z.js} +5 -5
  64. package/dist/index-7v05d9b7.js +58 -0
  65. package/dist/{index-f9b3vtt1.js → index-850vthn3.js} +8 -2
  66. package/dist/{index-ksv0a31x.js → index-8jjjmhab.js} +12 -1
  67. package/dist/index-96ztjvkn.js +204 -0
  68. package/dist/{index-gyaa159j.js → index-9fgmkfaf.js} +6 -3
  69. package/dist/{index-4bgk5ztz.js → index-9tdvbdv1.js} +187 -90
  70. package/dist/{index-2v6nvy3k.js → index-ae1ar4v1.js} +1 -1
  71. package/dist/{index-faxb7mck.js → index-b1ydbp95.js} +1 -1
  72. package/dist/{index-nxrsns5x.js → index-c5dzns43.js} +75 -75
  73. package/dist/{index-zhqwda72.js → index-dcjkw41m.js} +1 -1
  74. package/dist/{index-x2mrq5xv.js → index-dfdg2cgg.js} +179 -49
  75. package/dist/{index-87ctnzz9.js → index-dz48h1wx.js} +2 -2
  76. package/dist/{index-b6xz3qbe.js → index-gnmhbyyc.js} +366 -83
  77. package/dist/{index-qx8j6kds.js → index-j105mf16.js} +2 -2
  78. package/dist/{index-p56dszfp.js → index-jggt5gf2.js} +1 -1
  79. package/dist/{index-xwwsbsd3.js → index-pp8vdn01.js} +5 -5
  80. package/dist/{index-dy7w6wm4.js → index-prw8apnc.js} +5 -5
  81. package/dist/{index-qncw9hey.js → index-r2epb000.js} +33 -3
  82. package/dist/{index-brg2w6qc.js → index-tn9drm1a.js} +16 -14
  83. package/dist/{index-pswsyf1w.js → index-ty4vhe4n.js} +3 -3
  84. package/dist/{index-pztqab6k.js → index-vd2ywmfe.js} +1 -1
  85. package/dist/{index-3j9t1v4n.js → index-wcwhhtdh.js} +10 -9
  86. package/dist/{index-1rhgxqs8.js → index-xd2p95m7.js} +1 -1
  87. package/dist/{index-0c7p8mq5.js → index-xnm0wq1r.js} +1 -1
  88. package/dist/{index-3d3872c6.js → index-ys7j6679.js} +4 -4
  89. package/dist/index.js +38 -37
  90. package/dist/{message-fts-rowid-map-xg7hr0mk.js → message-fts-rowid-map-t6qsyg9s.js} +4 -3
  91. package/dist/{message-index-bounded-narw7bnz.js → message-index-bounded-wfk6aqxk.js} +9 -8
  92. package/dist/migration-worker.js +45 -40
  93. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  94. package/dist/{project-embedding-registry-tdz7985n.js → project-embedding-registry-3yyegjma.js} +8 -7
  95. package/dist/raw-ordinal-warmup-worker.js +114 -118
  96. package/dist/{read-session-chunk-vmg1zjg0.js → read-session-chunk-26yrrhyz.js} +7 -6
  97. package/dist/{read-session-raw-mncmzbm9.js → read-session-raw-j7c9w57v.js} +3 -2
  98. package/dist/{recomp-orchestrator-qyyhnc9b.js → recomp-orchestrator-wk509qkp.js} +20 -19
  99. package/dist/{render-c5p0k846.js → render-h1qarr8a.js} +13 -12
  100. package/dist/{search-f6hwvcxk.js → search-5h9fydyq.js} +16 -15
  101. package/dist/{search-archive-0ny7p3bw.js → search-archive-q79hyqxs.js} +9 -8
  102. package/dist/{send-session-notification-0gwcz3fw.js → send-session-notification-14p3yje4.js} +5 -4
  103. package/dist/shared/model-suggestion-retry.d.ts +7 -0
  104. package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
  105. package/dist/shared/prompt-async-transport.d.ts.map +1 -1
  106. package/dist/shared/reusable-worker-entry.d.ts +4 -0
  107. package/dist/shared/reusable-worker-entry.d.ts.map +1 -0
  108. package/dist/shared/reusable-worker.d.ts +6 -0
  109. package/dist/shared/reusable-worker.d.ts.map +1 -0
  110. package/dist/shared/reusable-worker.fixture.d.ts +2 -0
  111. package/dist/shared/reusable-worker.fixture.d.ts.map +1 -0
  112. package/dist/shared/worker-shutdown.d.ts +1 -1
  113. package/dist/shared/worker-shutdown.d.ts.map +1 -1
  114. package/dist/{storage-tags-zx1023km.js → storage-tags-vj0yj7ed.js} +1 -1
  115. package/dist/{store-620rjzt9.js → store-s3eykdjt.js} +1 -1
  116. package/dist/{transform-postprocess-phase-807ff6t2.js → transform-postprocess-phase-4gj0mtcg.js} +21 -20
  117. package/dist/v2/auto-search-worker.js +163 -160
  118. package/dist/v2/chunk-embedding-write-worker.js +59 -57
  119. package/dist/v2/compaction-marker-candidates-worker.js +24 -23
  120. package/dist/v2/{compaction-marker-candidates-t1aq6gsj.js → compaction-marker-candidates-yeb2ky46.js} +4 -2
  121. package/dist/v2/{compartment-chunk-embedding-ncsk0r9e.js → compartment-chunk-embedding-qfk791bd.js} +6 -5
  122. package/dist/v2/{context-znqfkap4.js → context-05x7kr7f.js} +1027 -215
  123. package/dist/v2/{embedding-backfill-11j425e0.js → embedding-backfill-00tw59rx.js} +10 -9
  124. package/dist/v2/embedding-worker.js +2 -2
  125. package/dist/v2/fold/code-mode-catalog.d.ts +3 -0
  126. package/dist/v2/fold/code-mode-catalog.d.ts.map +1 -0
  127. package/dist/v2/fold/host-media.d.ts +7 -3
  128. package/dist/v2/fold/host-media.d.ts.map +1 -1
  129. package/dist/v2/fold/idle.d.ts +1 -0
  130. package/dist/v2/fold/idle.d.ts.map +1 -1
  131. package/dist/v2/fold/manual-baseline.d.ts +8 -0
  132. package/dist/v2/fold/manual-baseline.d.ts.map +1 -0
  133. package/dist/v2/fold/native-replay-v3.d.ts +7 -0
  134. package/dist/v2/fold/native-replay-v3.d.ts.map +1 -1
  135. package/dist/v2/fold/native-replay.d.ts +24 -7
  136. package/dist/v2/fold/native-replay.d.ts.map +1 -1
  137. package/dist/v2/fold/restore.d.ts.map +1 -1
  138. package/dist/v2/fold/summary.d.ts +4 -0
  139. package/dist/v2/fold/summary.d.ts.map +1 -0
  140. package/dist/v2/hidden-completion.d.ts.map +1 -1
  141. package/dist/v2/hooks/channel2.d.ts +6 -0
  142. package/dist/v2/hooks/channel2.d.ts.map +1 -1
  143. package/dist/v2/hooks/context.d.ts.map +1 -1
  144. package/dist/v2/hooks/restore-rows.d.ts +12 -2
  145. package/dist/v2/hooks/restore-rows.d.ts.map +1 -1
  146. package/dist/v2/hooks/synthetic-admission.d.ts +7 -0
  147. package/dist/v2/hooks/synthetic-admission.d.ts.map +1 -0
  148. package/dist/v2/hooks/usage-persist.d.ts.map +1 -1
  149. package/dist/v2/{message-fts-rowid-map-rmk571mb.js → message-fts-rowid-map-z86avy0n.js} +5 -4
  150. package/dist/v2/{message-index-bounded-620bppw7.js → message-index-bounded-w14496s9.js} +9 -8
  151. package/dist/v2/migration-worker.js +46 -41
  152. package/dist/v2/{project-embedding-registry-bm5fe1a8.js → project-embedding-registry-jqq2qeqb.js} +9 -8
  153. package/dist/v2/raw-ordinal-warmup-worker.js +114 -118
  154. package/dist/v2/{read-session-chunk-qfwv1k71.js → read-session-chunk-tqnh1ret.js} +7 -6
  155. package/dist/v2/{read-session-raw-q294t8xs.js → read-session-raw-qpp6x9kg.js} +4 -3
  156. package/dist/v2/{recomp-orchestrator-fcnjref7.js → recomp-orchestrator-9cjk6aej.js} +19 -18
  157. package/dist/v2/{render-98a1qv0b.js → render-5dz1knf2.js} +13 -12
  158. package/dist/v2/{search-z1zqzsmh.js → search-8yfet25y.js} +15 -14
  159. package/dist/v2/{search-archive-r0y1bafr.js → search-archive-z6z9jqwf.js} +9 -8
  160. package/dist/v2/{send-session-notification-fst6hkg7.js → send-session-notification-kxp47tsw.js} +4 -3
  161. package/dist/v2/{server-cpjk5jd2.js → server-1d042x6w.js} +16 -14
  162. package/dist/v2/{server-fqj6ddpr.js → server-5by3vdzs.js} +186 -89
  163. package/dist/v2/{server-5jp38p98.js → server-6emy0v9a.js} +1 -2
  164. package/dist/v2/{server-rktnzs89.js → server-6vzh6wej.js} +1 -1
  165. package/dist/v2/{server-qa74bj91.js → server-7094pk3w.js} +9 -8
  166. package/dist/v2/{server-bxpz2dcn.js → server-7acq7815.js} +8 -6
  167. package/dist/v2/server-7v05d9b7.js +58 -0
  168. package/dist/v2/{server-24g8r2d0.js → server-7zacmty5.js} +75 -75
  169. package/dist/v2/server-96ztjvkn.js +204 -0
  170. package/dist/v2/{server-hnknwaz8.js → server-a9w5f0yw.js} +3 -3
  171. package/dist/v2/{server-9k0mjef7.js → server-avdveykk.js} +1 -1
  172. package/dist/v2/{server-hsf0qsn5.js → server-cp1a03vc.js} +1 -1
  173. package/dist/v2/{server-r3chnxs5.js → server-dfq5vb8j.js} +2 -2
  174. package/dist/v2/{server-x4m6xcn4.js → server-e51xmr4d.js} +6 -6
  175. package/dist/v2/{server-7ccxt4nq.js → server-g3gqq4ac.js} +6 -3
  176. package/dist/v2/{server-kkxzwa6w.js → server-gkse1wyz.js} +3 -3
  177. package/dist/v2/{server-1xesqm5h.js → server-jd7vzwn4.js} +15 -13
  178. package/dist/v2/{server-ws5exe6q.js → server-nmyganc8.js} +1 -1
  179. package/dist/v2/{server-q0z2q2dx.js → server-nq3cb7be.js} +1 -1
  180. package/dist/v2/{server-55v5hpth.js → server-qjyp27vf.js} +2 -2
  181. package/dist/v2/{server-xbtw0p9t.js → server-r4430x9m.js} +9 -3
  182. package/dist/v2/{server-qf2kzsh2.js → server-t8y64xj7.js} +4 -4
  183. package/dist/v2/{server-s9xgyyj9.js → server-tnzbk64q.js} +34 -4
  184. package/dist/v2/{server-51779zs2.js → server-v0p37hm2.js} +4 -1
  185. package/dist/v2/{server-bpvsvtph.js → server-xfjjbrkt.js} +12 -1
  186. package/dist/v2/server.js +4 -4
  187. package/dist/v2/{storage-tags-19s0ebf0.js → storage-tags-cw07ky67.js} +1 -1
  188. package/dist/v2/{store-vdm8v3jd.js → store-z3qe67cg.js} +1 -1
  189. package/dist/v2/{v2-archive-reader-t2c0eypn.js → v2-archive-reader-z7wr42ry.js} +3 -3
  190. package/dist/v2/{wrapup-orchestrator-aqpqsveg.js → wrapup-orchestrator-j1v62bkj.js} +19 -18
  191. package/dist/{v2-archive-reader-tm2eayp2.js → v2-archive-reader-028z6kck.js} +2 -2
  192. package/dist/{wrapup-orchestrator-k5yp19a6.js → wrapup-orchestrator-xg9avt2p.js} +21 -20
  193. package/package.json +1 -1
  194. package/src/shared/model-suggestion-retry.ts +1 -1
  195. package/src/shared/prompt-async-transport.test.ts +64 -3
  196. package/src/shared/prompt-async-transport.ts +61 -16
  197. package/src/shared/reusable-worker-entry.ts +58 -0
  198. package/src/shared/reusable-worker-reset.test.ts +108 -0
  199. package/src/shared/reusable-worker.fixture.ts +35 -0
  200. package/src/shared/reusable-worker.test.ts +253 -0
  201. package/src/shared/reusable-worker.ts +240 -0
  202. package/src/shared/worker-shutdown.ts +21 -17
@@ -2554,7 +2554,9 @@ function getAllStatusTagTokenTotalsFlat2(db, sessionId, floor = 0, calibration =
2554
2554
  FROM tags
2555
2555
  WHERE session_id = ?`).all(sessionId);
2556
2556
  const totals = new Map;
2557
+ const summaryRemovedTotals = new Map;
2557
2558
  const nullMessageIds = new Set;
2559
+ const nullSummaryRemovedMessageIds = new Set;
2558
2560
  for (const row of rows) {
2559
2561
  if (row.type === "tool" && row.tool_owner_message_id === null)
2560
2562
  continue;
@@ -2564,6 +2566,7 @@ function getAllStatusTagTokenTotalsFlat2(db, sessionId, floor = 0, calibration =
2564
2566
  if (row.token_count === null) {
2565
2567
  nullMessageIds.add(owner);
2566
2568
  totals.delete(owner);
2569
+ summaryRemovedTotals.delete(owner);
2567
2570
  continue;
2568
2571
  }
2569
2572
  if (nullMessageIds.has(owner))
@@ -2571,8 +2574,16 @@ function getAllStatusTagTokenTotalsFlat2(db, sessionId, floor = 0, calibration =
2571
2574
  const ratio = row.type === "tool" ? calibration.toolsRatio : calibration.proseRatio;
2572
2575
  const weight = ((row.token_count ?? 0) + (row.input_token_count ?? 0)) * ratio + (row.reasoning_token_count ?? 0) * calibration.proseRatio;
2573
2576
  totals.set(owner, (totals.get(owner) ?? 0) + weight);
2577
+ if (row.type === "tool" && row.input_token_count === null) {
2578
+ nullSummaryRemovedMessageIds.add(owner);
2579
+ summaryRemovedTotals.delete(owner);
2580
+ }
2581
+ if (!nullSummaryRemovedMessageIds.has(owner)) {
2582
+ const removedWeight = row.type === "tool" ? (row.input_token_count ?? 0) * calibration.toolsRatio : row.type === "message" ? row.token_count * calibration.proseRatio : 0;
2583
+ summaryRemovedTotals.set(owner, (summaryRemovedTotals.get(owner) ?? 0) + removedWeight);
2584
+ }
2574
2585
  }
2575
- return { totals, nullMessageIds };
2586
+ return { totals, summaryRemovedTotals, nullMessageIds };
2576
2587
  }
2577
2588
  function getReasoningTokenEstimatesByMessage2(db, sessionId, proseRatio) {
2578
2589
  const summary = readTagOwnerSummary(db, sessionId, proseRatio);
package/dist/v2/server.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  loadPluginConfigDetailed
3
- } from "./server-9k0mjef7.js";
3
+ } from "./server-avdveykk.js";
4
4
  import {
5
5
  setHarness2
6
6
  } from "./server-pavzsdc6.js";
@@ -22,7 +22,7 @@ import {
22
22
  union,
23
23
  tuple,
24
24
  record
25
- } from "./server-24g8r2d0.js";
25
+ } from "./server-7zacmty5.js";
26
26
  import {
27
27
  pushNotification2
28
28
  } from "./server-x8a7jmkh.js";
@@ -7875,7 +7875,7 @@ var require_src2 = __commonJS(function(exports, module) {
7875
7875
  // package.json
7876
7876
  var package_default = {
7877
7877
  name: "@cortexkit/opencode-magic-context",
7878
- version: "0.49.35",
7878
+ version: "0.49.37",
7879
7879
  type: "module",
7880
7880
  description: "OpenCode plugin for Magic Context — cross-session memory and context management",
7881
7881
  main: "dist/index.js",
@@ -8196,7 +8196,7 @@ async function setup(context) {
8196
8196
  if (claimStorageBackupWarningCheck() && isMagicContextStorageOutsideEngram(storageDir)) {
8197
8197
  log2(`[magic-context] Magic Context storage is at ${storageDir}, outside the folder Engram backs up (~/.local/share/cortexkit/magic-context). It is not included in CortexKit backups.`);
8198
8198
  }
8199
- const { registerContext } = await import("./context-znqfkap4.js");
8199
+ const { registerContext } = await import("./context-05x7kr7f.js");
8200
8200
  setHarness2("opencode2");
8201
8201
  bindStaleBuildNotice({}, import.meta.url, "opencode2");
8202
8202
  const duties = await registerContext(context);
@@ -63,7 +63,7 @@ import {
63
63
  adoptNullOwnerToolTag2,
64
64
  getCandidateToolOwners2,
65
65
  pickNearestPriorOwner2
66
- } from "./server-bpvsvtph.js";
66
+ } from "./server-xfjjbrkt.js";
67
67
  import"./server-1zx87sdm.js";
68
68
  import"./server-tpt7bwq8.js";
69
69
  import"./server-969cdpmg.js";
@@ -5,7 +5,7 @@ import {
5
5
  readV2RawMessagePagesForConversion2,
6
6
  createV2RawMessageReader2,
7
7
  createV2RawMessageProvider2
8
- } from "./server-5jp38p98.js";
8
+ } from "./server-6emy0v9a.js";
9
9
  import"./server-ef1mskas.js";
10
10
  import"./server-468mya5v.js";
11
11
  import"./server-2ra6pwf3.js";
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  readV2ArchiveOffThread2,
3
3
  serveV2ArchiveReads2
4
- } from "./server-x4m6xcn4.js";
5
- import"./server-51779zs2.js";
4
+ } from "./server-e51xmr4d.js";
5
+ import"./server-v0p37hm2.js";
6
6
  import"./server-gzy5w8ya.js";
7
- import"./server-cpjk5jd2.js";
7
+ import"./server-1d042x6w.js";
8
8
  export {
9
9
  readV2ArchiveOffThread2 as readV2ArchiveOffThread,
10
10
  serveV2ArchiveReads2 as serveV2ArchiveReads
@@ -11,19 +11,20 @@ import {
11
11
  releaseCompartmentLeaseBestEffort,
12
12
  getCompartments,
13
13
  getLastCompartmentEndMessage
14
- } from "./server-qf2kzsh2.js";
14
+ } from "./server-t8y64xj7.js";
15
15
  import"./server-2amn2s73.js";
16
16
  import"./server-969cdpmg.js";
17
17
  import"./server-zzgtgqmd.js";
18
18
  import {
19
19
  inRootAsyncContext2
20
20
  } from "./server-gzy5w8ya.js";
21
- import"./server-51779zs2.js";
22
- import"./server-cpjk5jd2.js";
23
- import"./server-s9xgyyj9.js";
24
- import"./server-ws5exe6q.js";
25
- import"./server-hsf0qsn5.js";
26
- import"./server-r3chnxs5.js";
21
+ import"./server-v0p37hm2.js";
22
+ import"./server-96ztjvkn.js";
23
+ import"./server-1d042x6w.js";
24
+ import"./server-tnzbk64q.js";
25
+ import"./server-nmyganc8.js";
26
+ import"./server-cp1a03vc.js";
27
+ import"./server-dfq5vb8j.js";
27
28
  import {
28
29
  getWrapupInProgressState,
29
30
  acquireWrapupInProgress,
@@ -32,12 +33,12 @@ import {
32
33
  getHistorianFailureState,
33
34
  clearEmergencyRecovery,
34
35
  prepareRawSessionOrdinals2
35
- } from "./server-1xesqm5h.js";
36
+ } from "./server-jd7vzwn4.js";
36
37
  import"./server-tpt7bwq8.js";
37
- import"./server-7ccxt4nq.js";
38
+ import"./server-g3gqq4ac.js";
38
39
  import"./server-1zx87sdm.js";
39
- import"./server-bpvsvtph.js";
40
- import"./server-24g8r2d0.js";
40
+ import"./server-xfjjbrkt.js";
41
+ import"./server-7zacmty5.js";
41
42
  import"./server-2ra6pwf3.js";
42
43
  import"./server-mfn1yf9d.js";
43
44
  import"./server-xy7w7hrd.js";
@@ -51,7 +52,7 @@ import"./server-ypf1ap0v.js";
51
52
  import {
52
53
  pushNotification2
53
54
  } from "./server-x8a7jmkh.js";
54
- import"./server-xbtw0p9t.js";
55
+ import"./server-r4430x9m.js";
55
56
  import {
56
57
  describeBoundaryDiagnostics,
57
58
  resolveWrapupProtectedTailBoundary,
@@ -62,16 +63,16 @@ import {
62
63
  registerActiveCompartmentRun,
63
64
  setRecompStarting2,
64
65
  setRecompTerminal2
65
- } from "./server-fqj6ddpr.js";
66
+ } from "./server-5by3vdzs.js";
66
67
  import"./server-qt1qqasc.js";
67
- import"./server-bxpz2dcn.js";
68
+ import"./server-7acq7815.js";
68
69
  import"./server-dcws5831.js";
69
70
  import"./server-4dhv7mqz.js";
70
- import"./server-rktnzs89.js";
71
- import"./server-kkxzwa6w.js";
71
+ import"./server-6vzh6wej.js";
72
+ import"./server-gkse1wyz.js";
72
73
  import"./server-jjvbk92s.js";
73
- import"./server-q0z2q2dx.js";
74
- import"./server-9k0mjef7.js";
74
+ import"./server-nq3cb7be.js";
75
+ import"./server-avdveykk.js";
75
76
  import"./server-qxzbmtqt.js";
76
77
  import"./server-sd8hvzte.js";
77
78
  import"./server-f0j864ma.js";
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  readV2ArchiveOffThread2,
3
3
  serveV2ArchiveReads2
4
- } from "./index-dy7w6wm4.js";
4
+ } from "./index-prw8apnc.js";
5
5
  import"./index-v0p37hm2.js";
6
6
  import"./index-gzy5w8ya.js";
7
- import"./index-cpjk5jd2.js";
7
+ import"./index-1d042x6w.js";
8
8
  export {
9
9
  readV2ArchiveOffThread2 as readV2ArchiveOffThread,
10
10
  serveV2ArchiveReads2 as serveV2ArchiveReads
@@ -1,45 +1,46 @@
1
1
  import {
2
2
  runManagedWrapup2
3
- } from "./index-xwwsbsd3.js";
4
- import"./index-3d3872c6.js";
5
- import"./index-4bgk5ztz.js";
6
- import"./index-f9b3vtt1.js";
3
+ } from "./index-pp8vdn01.js";
4
+ import"./index-ys7j6679.js";
5
+ import"./index-9tdvbdv1.js";
6
+ import"./index-850vthn3.js";
7
7
  import"./index-sd3egckz.js";
8
- import"./index-0c7p8mq5.js";
9
- import"./index-pswsyf1w.js";
8
+ import"./index-xnm0wq1r.js";
9
+ import"./index-ty4vhe4n.js";
10
10
  import"./index-jjvbk92s.js";
11
- import"./index-jn0ddrhp.js";
11
+ import"./index-21fgrfrf.js";
12
12
  import"./index-4dhv7mqz.js";
13
- import"./index-f297z1n8.js";
13
+ import"./index-72czy45z.js";
14
14
  import"./index-2qbtqg1w.js";
15
- import"./index-pq0dpwrc.js";
15
+ import"./index-2rs7912w.js";
16
16
  import"./index-2amn2s73.js";
17
17
  import"./index-66a50f3e.js";
18
18
  import"./index-dnqqqc5f.js";
19
- import"./index-2v6nvy3k.js";
19
+ import"./index-ae1ar4v1.js";
20
20
  import"./index-qt1qqasc.js";
21
- import"./index-brg2w6qc.js";
22
- import"./index-ksv0a31x.js";
21
+ import"./index-tn9drm1a.js";
22
+ import"./index-8jjjmhab.js";
23
23
  import"./index-1zx87sdm.js";
24
- import"./index-pztqab6k.js";
24
+ import"./index-vd2ywmfe.js";
25
25
  import"./index-x8a7jmkh.js";
26
26
  import"./index-fw42ms3m.js";
27
- import"./index-1rhgxqs8.js";
28
- import"./index-nxrsns5x.js";
27
+ import"./index-xd2p95m7.js";
28
+ import"./index-c5dzns43.js";
29
29
  import"./index-jz5bk7xd.js";
30
- import"./index-zhqwda72.js";
30
+ import"./index-dcjkw41m.js";
31
31
  import"./index-g4t7850m.js";
32
- import"./index-gyaa159j.js";
32
+ import"./index-9fgmkfaf.js";
33
33
  import"./index-fb3zxn06.js";
34
34
  import"./index-nj360hcs.js";
35
35
  import"./index-fr0wbhdn.js";
36
36
  import"./index-txs8tce6.js";
37
- import"./index-faxb7mck.js";
37
+ import"./index-b1ydbp95.js";
38
38
  import"./index-wh9fxds5.js";
39
39
  import"./index-2p37txes.js";
40
40
  import"./index-jkanybgq.js";
41
41
  import"./index-9wnbnx8f.js";
42
- import"./index-qncw9hey.js";
42
+ import"./index-r2epb000.js";
43
+ import"./index-96ztjvkn.js";
43
44
  import"./index-v0p37hm2.js";
44
45
  import"./index-58e3y90n.js";
45
46
  import"./index-fxbk7gk3.js";
@@ -49,7 +50,7 @@ import"./index-jrfre9ah.js";
49
50
  import"./index-g2q4htwp.js";
50
51
  import"./index-pavzsdc6.js";
51
52
  import"./index-z4p0ry94.js";
52
- import"./index-cpjk5jd2.js";
53
+ import"./index-1d042x6w.js";
53
54
  export {
54
55
  runManagedWrapup2 as runManagedWrapup
55
56
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/opencode-magic-context",
3
- "version": "0.49.35",
3
+ "version": "0.49.37",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for Magic Context — cross-session memory and context management",
6
6
  "main": "dist/index.js",
@@ -429,7 +429,7 @@ async function promptWithTimeout(
429
429
  * instance-scoped fiber that `POST /session/{id}/abort` interrupts. Best-effort —
430
430
  * a failure here must not mask the original timeout/abort error.
431
431
  */
432
- async function abortChildRun(client: Client, sessionId: string): Promise<void> {
432
+ export async function abortChildRun(client: Client, sessionId: string): Promise<void> {
433
433
  if (!client) return;
434
434
  try {
435
435
  // Bound the abort call: it's best-effort cleanup, and if the abort
@@ -17,7 +17,7 @@ type Message = {
17
17
  finish?: string;
18
18
  error?: unknown;
19
19
  };
20
- parts: Array<{ type: string; text?: string }>;
20
+ parts: Array<{ type: string; text?: string; error?: unknown }>;
21
21
  };
22
22
 
23
23
  /**
@@ -165,6 +165,42 @@ describe("promptAsyncAndWaitForIdle", () => {
165
165
  ).rejects.toThrow("did not start a run");
166
166
  });
167
167
 
168
+ test("ignores provider errors and retry parts from before the current send", async () => {
169
+ const host = asyncHost({
170
+ plan: ["busy", "settle"],
171
+ initialMessages: [
172
+ {
173
+ info: {
174
+ id: "old-error",
175
+ role: "assistant",
176
+ time: { created: 0 },
177
+ error: { name: "APIError" },
178
+ },
179
+ parts: [{ type: "retry", error: { name: "APIError" } }],
180
+ },
181
+ ],
182
+ });
183
+ await promptAsyncAndWaitForIdle(host.client, request(), { pollIntervalMs: 1 });
184
+ expect(host.polls()).toBe(2);
185
+ expect(host.abort).not.toHaveBeenCalled();
186
+ });
187
+
188
+ test("accepts a successfully settled assistant that retains an earlier retry part", async () => {
189
+ const host = asyncHost({ plan: ["settle"] });
190
+ const read = host.list.getMockImplementation()!;
191
+ host.list.mockImplementation(async () => {
192
+ const result = await read();
193
+ const last = result.data.at(-1);
194
+ if (last?.info.finish === "stop") {
195
+ last.parts.push({ type: "retry", error: { name: "APIError" } });
196
+ }
197
+ return result;
198
+ });
199
+ await promptAsyncAndWaitForIdle(host.client, request(), { pollIntervalMs: 1 });
200
+ expect(host.polls()).toBe(1);
201
+ expect(host.abort).not.toHaveBeenCalled();
202
+ });
203
+
168
204
  test("surfaces a rejected prompt_async request", async () => {
169
205
  const host = asyncHost({ plan: ["settle"] });
170
206
  host.promptAsync.mockImplementation(async () => ({
@@ -653,9 +689,11 @@ describe("prompt_async transport and the first-output deadline", () => {
653
689
  * busy forever; "partial" streams a text part and then stays busy; "answer"
654
690
  * settles with a final assistant message on the next poll.
655
691
  */
656
- function modelHost(behaviours: Record<string, "dead" | "partial" | "answer">) {
692
+ function modelHost(
693
+ behaviours: Record<string, "dead" | "partial" | "answer" | "error" | "retry">,
694
+ ) {
657
695
  const messages: Message[] = [];
658
- let current: "dead" | "partial" | "answer" | null = null;
696
+ let current: "dead" | "partial" | "answer" | "error" | "retry" | null = null;
659
697
  const sent: string[] = [];
660
698
  const promptAsync = mock(
661
699
  async (request: { body: { model?: { providerID: string; modelID: string } } }) => {
@@ -684,6 +722,16 @@ describe("prompt_async transport and the first-output deadline", () => {
684
722
  return { data: true };
685
723
  });
686
724
  const status = mock(async () => {
725
+ if (current === "error" || current === "retry") {
726
+ const last = messages.at(-1)!;
727
+ const error = {
728
+ name: "APIError",
729
+ data: { statusCode: 429, message: "Free usage limit reached" },
730
+ };
731
+ if (current === "error") last.info.error = error;
732
+ else last.parts = [{ type: "retry", error }];
733
+ return { data: { "ses-child": { type: current === "retry" ? "retry" : "busy" } } };
734
+ }
687
735
  if (current === "dead" || current === "partial")
688
736
  return { data: { "ses-child": { type: "busy" } } };
689
737
  if (current === "answer") {
@@ -751,4 +799,17 @@ describe("prompt_async transport and the first-output deadline", () => {
751
799
  await expect(runChain(host, 200, 40)).rejects.toThrow("prompt timed out after 200ms");
752
800
  expect(host.sent).toEqual(["opencode/free-model"]);
753
801
  });
802
+
803
+ for (const failure of ["error", "retry"] as const) {
804
+ test(`a recorded provider ${failure} stops the host retry loop and immediately tries the fallback`, async () => {
805
+ const host = modelHost({ "opencode/free-model": failure });
806
+ const startedAt = Date.now();
807
+ const result = await runChain(host, 5_000, 5_000);
808
+ expect(result.attempt.label).toBe("anthropic/claude-sonnet-5-5");
809
+ expect(host.sent).toEqual(["opencode/free-model", "anthropic/claude-sonnet-5-5"]);
810
+ expect(host.abort).toHaveBeenCalledTimes(1);
811
+ expect(host.abort).toHaveBeenCalledWith({ path: { id: "ses-child" } });
812
+ expect(Date.now() - startedAt).toBeLessThan(2_000);
813
+ });
814
+ }
754
815
  });
@@ -14,7 +14,11 @@ import {
14
14
  import { describeAssistantError } from "./assistant-message-extractor";
15
15
  import { sessionLog } from "./logger";
16
16
  import type { PromptArgs, PromptTransport } from "./model-suggestion-retry";
17
- import { noteModelActivityMonitored, noteModelOutputReceived } from "./model-suggestion-retry";
17
+ import {
18
+ abortChildRun,
19
+ noteModelActivityMonitored,
20
+ noteModelOutputReceived,
21
+ } from "./model-suggestion-retry";
18
22
 
19
23
  type Client = ReturnType<typeof createOpencodeClient> | undefined;
20
24
 
@@ -196,6 +200,28 @@ function messageId(message: unknown): string | null {
196
200
  return typeof id === "string" ? id : null;
197
201
  }
198
202
 
203
+ function recordedAssistantError(message: unknown): unknown {
204
+ const info = infoOf(message);
205
+ if (info.error != null) return info.error;
206
+ // A completed step can retain retry parts from a provider attempt that
207
+ // already recovered before this poll. Its successful settlement wins.
208
+ if (isSettledAssistant(message)) return undefined;
209
+ const parts = (message as { parts?: unknown[] } | null)?.parts;
210
+ if (!Array.isArray(parts)) return undefined;
211
+ // OpenCode records a failed provider attempt as a retry part before it
212
+ // settles the assistant row or emits session.error.
213
+ const retry = parts
214
+ .slice()
215
+ .reverse()
216
+ .find(
217
+ (part) =>
218
+ part !== null &&
219
+ typeof part === "object" &&
220
+ (part as { type?: unknown }).type === "retry",
221
+ ) as { error?: unknown } | undefined;
222
+ return retry?.error ?? undefined;
223
+ }
224
+
199
225
  /** An assistant message the host has finished writing, successfully or not. */
200
226
  function isSettledAssistant(message: unknown): boolean {
201
227
  const info = infoOf(message);
@@ -355,6 +381,26 @@ export async function promptAsyncAndWaitForIdle(
355
381
  // Dispatch can fail before an assistant row exists. The plugin event hook
356
382
  // retains that provider/model error while this child is actively awaited.
357
383
  activeSessionErrors.set(sessionId, undefined);
384
+ const failForHostError = async (hostError: unknown): Promise<never> => {
385
+ // Stop the host's retry loop before handing the attempt to our model
386
+ // chain; otherwise the failed model keeps making requests in parallel.
387
+ await abortChildRun(client, sessionId);
388
+ const error = new Error(
389
+ `Host recorded session error: ${describeAssistantError(hostError)}`,
390
+ );
391
+ Object.assign(error, {
392
+ name: "DreamerProviderOutputFailureError",
393
+ transient: true,
394
+ [HOST_SESSION_ERROR]: true,
395
+ hostError,
396
+ });
397
+ annotateSubagentInvocationError(error, {
398
+ attemptedModel: requestModelRef(request),
399
+ modelConfigKey: options.modelConfigKey,
400
+ hostError,
401
+ });
402
+ throw error;
403
+ };
358
404
  try {
359
405
  let sent: unknown;
360
406
  try {
@@ -395,24 +441,23 @@ export async function promptAsyncAndWaitForIdle(
395
441
  await sleep(pollIntervalMs, signal);
396
442
  const hostError = activeSessionErrors.get(sessionId);
397
443
  if (hostError !== undefined) {
398
- const error = new Error(
399
- `Host recorded session error: ${describeAssistantError(hostError)}`,
400
- );
401
- Object.assign(error, {
402
- name: "DreamerProviderOutputFailureError",
403
- transient: true,
404
- [HOST_SESSION_ERROR]: true,
405
- hostError,
406
- });
407
- annotateSubagentInvocationError(error, {
408
- attemptedModel: requestModelRef(request),
409
- modelConfigKey: options.modelConfigKey,
410
- hostError,
411
- });
412
- throw error;
444
+ await failForHostError(hostError);
413
445
  }
414
446
  const status = await readStatus(session, sessionId, dir, signal);
415
447
  const messages = await readMessages(session, sessionId, dir, signal);
448
+ const latestAssistant = messages
449
+ .slice()
450
+ .reverse()
451
+ .find((message) => {
452
+ const id = messageId(message);
453
+ return (
454
+ id !== null &&
455
+ !completionBaseline.has(id) &&
456
+ infoOf(message).role === "assistant"
457
+ );
458
+ });
459
+ const assistantError = recordedAssistantError(latestAssistant);
460
+ if (assistantError !== undefined) await failForHostError(assistantError);
416
461
  if (
417
462
  !sawOutput &&
418
463
  messages.some((message) => {
@@ -0,0 +1,58 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { type MessagePort, parentPort, workerData } from "node:worker_threads";
3
+
4
+ /** Dispatch each job with new per-job state and a port whose messages are matched by job id. */
5
+ export function serveWorkerJobs<T>(
6
+ run: (data: T, port: MessagePort) => Promise<void> | void,
7
+ autoClose = true,
8
+ ): void {
9
+ if (!parentPort) throw new Error("worker requires a parent port");
10
+ const parent = parentPort;
11
+ if (!workerData?.reusableWorker) {
12
+ void Promise.resolve(run(workerData, parent)).catch(rethrow);
13
+ return;
14
+ }
15
+ let active: { id: number; port: EventEmitter } | undefined;
16
+ parent.on("message", (message: { jobId: number; start?: T; value?: unknown }) => {
17
+ if (!("start" in message)) {
18
+ if (active?.id === message.jobId) active.port.emit("message", message.value);
19
+ return;
20
+ }
21
+ if (active) throw new Error("reusable worker received overlapping jobs");
22
+ const id = message.jobId;
23
+ let closed = false;
24
+ const port = Object.assign(new EventEmitter(), {
25
+ postMessage(value: unknown, transferList?: readonly object[]) {
26
+ if (!closed) parent.postMessage({ jobId: id, value }, transferList as never);
27
+ },
28
+ close() {
29
+ if (closed) return;
30
+ closed = true;
31
+ port.removeAllListeners();
32
+ active = undefined;
33
+ parent.postMessage({
34
+ jobId: id,
35
+ finished: true,
36
+ code: Number(process.exitCode) || 0,
37
+ });
38
+ },
39
+ });
40
+ active = { id, port };
41
+ try {
42
+ void Promise.resolve(run(message.start as T, port as unknown as MessagePort))
43
+ .then(() => {
44
+ if (autoClose) port.close();
45
+ })
46
+ .catch(rethrow);
47
+ } catch (error) {
48
+ rethrow(error);
49
+ }
50
+ });
51
+ }
52
+
53
+ function rethrow(error: unknown): void {
54
+ // Bun 1.3 aborts the host for a microtask throw; a timer reports a Worker error.
55
+ setTimeout(() => {
56
+ throw error;
57
+ });
58
+ }
@@ -0,0 +1,108 @@
1
+ import { expect, spyOn, test } from "bun:test";
2
+ import { spawnSync } from "node:child_process";
3
+ import { once } from "node:events";
4
+ import { writeFileSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import * as timers from "node:timers";
7
+ import { fileURLToPath } from "node:url";
8
+ import { resetReusableWorkersForTests, reuseWorker } from "./reusable-worker";
9
+ import { withTestTempDir } from "./test-temp-dir";
10
+
11
+ test("a temporary caller clock cannot replace the pool's idle timer", async () => {
12
+ const clock = spyOn(globalThis, "setTimeout").mockReturnValue(
13
+ 0 as unknown as ReturnType<typeof setTimeout>,
14
+ );
15
+ try {
16
+ const worker = reuseWorker(new URL("./reusable-worker.fixture.ts", import.meta.url), {
17
+ workerData: { path: "caller-clock" },
18
+ });
19
+ const message = once(worker, "message");
20
+ const exit = once(worker, "exit");
21
+ await message;
22
+ await exit;
23
+ expect(clock).not.toHaveBeenCalled();
24
+ } finally {
25
+ clock.mockRestore();
26
+ await resetReusableWorkersForTests();
27
+ }
28
+ });
29
+
30
+ test("reset clears idle timers and waits for active and queued leases to exit", async () => {
31
+ const entry = new URL("./reusable-worker.fixture.ts", import.meta.url);
32
+ const schedules = spyOn(timers, "setTimeout");
33
+ const cancellations = spyOn(timers, "clearTimeout");
34
+ const idleMs = 12_345;
35
+ async function runIdle() {
36
+ const worker = reuseWorker(entry, { workerData: { path: "reset-idle" } }, idleMs);
37
+ const message = once(worker, "message");
38
+ const exit = once(worker, "exit");
39
+ const [reply] = await message;
40
+ await exit;
41
+ return reply.threadId as number;
42
+ }
43
+ try {
44
+ const previousThread = await runIdle();
45
+ const timerIndex = schedules.mock.calls.findIndex(([, delay]) => delay === idleMs);
46
+ expect(timerIndex).toBeGreaterThanOrEqual(0);
47
+ const idleTimer = schedules.mock.results[timerIndex]?.value;
48
+ const active = reuseWorker(entry, { workerData: { path: "reset-active", hang: true } });
49
+ const queued = reuseWorker(entry, {
50
+ workerData: { path: "reset-active", value: "queued" },
51
+ });
52
+ const replies: unknown[] = [];
53
+ queued.on("message", (message) => replies.push(message));
54
+ const activeExit = once(active, "exit");
55
+ const queuedExit = once(queued, "exit");
56
+ await Promise.resolve();
57
+ await Promise.all([resetReusableWorkersForTests(), activeExit, queuedExit]);
58
+ expect(cancellations).toHaveBeenCalledWith(idleTimer);
59
+ expect(replies).toEqual([]);
60
+ expect(await runIdle()).not.toBe(previousThread);
61
+ } finally {
62
+ schedules.mockRestore();
63
+ cancellations.mockRestore();
64
+ await resetReusableWorkersForTests();
65
+ }
66
+ });
67
+
68
+ test("the shared preload stops pooled workers between test cases", () => {
69
+ withTestTempDir("reusable-worker-boundaries-", (dir) => {
70
+ writeFileSync(
71
+ join(dir, "boundaries.test.ts"),
72
+ `import { expect, test } from "bun:test";
73
+ import { once } from "node:events";
74
+ import { reuseWorker } from ${JSON.stringify(new URL("./reusable-worker.ts", import.meta.url).href)};
75
+ const entry = new URL(${JSON.stringify(new URL("./reusable-worker.fixture.ts", import.meta.url).href)});
76
+ let previousThread;
77
+ async function run() {
78
+ const worker = reuseWorker(entry, { workerData: { path: "test-boundaries" } });
79
+ const message = once(worker, "message");
80
+ const exit = once(worker, "exit");
81
+ const [reply] = await message;
82
+ await exit;
83
+ return reply.threadId;
84
+ }
85
+ test("finishes with an idle pooled worker", async () => {
86
+ previousThread = await run();
87
+ expect(previousThread).toBeGreaterThan(0);
88
+ });
89
+ test("cannot inherit the previous test's pooled worker", async () => {
90
+ expect(await run()).not.toBe(previousThread);
91
+ });`,
92
+ );
93
+ const child = spawnSync(
94
+ process.execPath,
95
+ [
96
+ "test",
97
+ "--preload",
98
+ fileURLToPath(new URL("../../test-preload.ts", import.meta.url)),
99
+ join(dir, "boundaries.test.ts"),
100
+ ],
101
+ { cwd: dir, encoding: "utf8", timeout: 5_000, windowsHide: true },
102
+ );
103
+ const output = child.stdout + child.stderr;
104
+ expect(output).toContain("2 pass");
105
+ expect(output).toContain("0 fail");
106
+ expect(child.status).toBe(0);
107
+ });
108
+ }, 10_000);