@cortexkit/opencode-magic-context 0.44.1 → 0.44.3

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 (216) hide show
  1. package/README.md +6 -0
  2. package/dist/agents/dreamer.d.ts +4 -6
  3. package/dist/agents/dreamer.d.ts.map +1 -1
  4. package/dist/config/live-child-output-cap.d.ts.map +1 -1
  5. package/dist/config/schema/magic-context.d.ts +63 -10
  6. package/dist/config/schema/magic-context.d.ts.map +1 -1
  7. package/dist/{conflict-warning-hook-d14ah73v.js → conflict-warning-hook-hmpzxep6.js} +3 -3
  8. package/dist/{derive-budgets-gd2270s0.js → derive-budgets-vjmvh6pz.js} +3 -1
  9. package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
  10. package/dist/features/magic-context/compartment-chunk-embedding.d.ts +11 -0
  11. package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -1
  12. package/dist/features/magic-context/compartment-storage.d.ts.map +1 -1
  13. package/dist/features/magic-context/dreamer/classify.d.ts.map +1 -1
  14. package/dist/features/magic-context/dreamer/docs-proposals.d.ts +2 -0
  15. package/dist/features/magic-context/dreamer/docs-proposals.d.ts.map +1 -1
  16. package/dist/features/magic-context/dreamer/hidden-single-shot.d.ts.map +1 -1
  17. package/dist/features/magic-context/dreamer/lease.d.ts +1 -1
  18. package/dist/features/magic-context/dreamer/lease.d.ts.map +1 -1
  19. package/dist/features/magic-context/dreamer/manual-summary.d.ts.map +1 -1
  20. package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts +1 -1
  21. package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts.map +1 -1
  22. package/dist/features/magic-context/dreamer/map-memories.d.ts +5 -0
  23. package/dist/features/magic-context/dreamer/map-memories.d.ts.map +1 -1
  24. package/dist/features/magic-context/dreamer/refresh-primers.d.ts +5 -0
  25. package/dist/features/magic-context/dreamer/refresh-primers.d.ts.map +1 -1
  26. package/dist/features/magic-context/dreamer/retrospective-raw-provider.d.ts +2 -2
  27. package/dist/features/magic-context/dreamer/retrospective-raw-provider.d.ts.map +1 -1
  28. package/dist/features/magic-context/dreamer/storage-dream-runs.d.ts +8 -1
  29. package/dist/features/magic-context/dreamer/storage-dream-runs.d.ts.map +1 -1
  30. package/dist/features/magic-context/dreamer/task-config.d.ts.map +1 -1
  31. package/dist/features/magic-context/dreamer/task-executor.d.ts +5 -0
  32. package/dist/features/magic-context/dreamer/task-executor.d.ts.map +1 -1
  33. package/dist/features/magic-context/dreamer/task-gates.d.ts.map +1 -1
  34. package/dist/features/magic-context/dreamer/task-prompts.d.ts +18 -2
  35. package/dist/features/magic-context/dreamer/task-prompts.d.ts.map +1 -1
  36. package/dist/features/magic-context/dreamer/task-registry.d.ts +12 -0
  37. package/dist/features/magic-context/dreamer/task-registry.d.ts.map +1 -1
  38. package/dist/features/magic-context/dreamer/task-scheduler.d.ts +3 -1
  39. package/dist/features/magic-context/dreamer/task-scheduler.d.ts.map +1 -1
  40. package/dist/features/magic-context/dreamer/token-budget.d.ts +33 -0
  41. package/dist/features/magic-context/dreamer/token-budget.d.ts.map +1 -0
  42. package/dist/features/magic-context/dreamer/verify-diff.d.ts +15 -0
  43. package/dist/features/magic-context/dreamer/verify-diff.d.ts.map +1 -0
  44. package/dist/features/magic-context/dreamer/verify-gate.d.ts.map +1 -1
  45. package/dist/features/magic-context/dreamer/verify-prompt.d.ts +5 -2
  46. package/dist/features/magic-context/dreamer/verify-prompt.d.ts.map +1 -1
  47. package/dist/features/magic-context/dreamer/verify-read-cap.d.ts +4 -0
  48. package/dist/features/magic-context/dreamer/verify-read-cap.d.ts.map +1 -0
  49. package/dist/features/magic-context/dreamer/verify.d.ts +5 -0
  50. package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
  51. package/dist/features/magic-context/git-commits/git-log-reader.d.ts.map +1 -1
  52. package/dist/features/magic-context/memory/project-identity-cache.d.ts +6 -0
  53. package/dist/features/magic-context/memory/project-identity-cache.d.ts.map +1 -0
  54. package/dist/features/magic-context/memory/project-identity.d.ts +11 -22
  55. package/dist/features/magic-context/memory/project-identity.d.ts.map +1 -1
  56. package/dist/features/magic-context/memory/storage-memory-fts.d.ts +1 -0
  57. package/dist/features/magic-context/memory/storage-memory-fts.d.ts.map +1 -1
  58. package/dist/features/magic-context/memory/verification-paths.d.ts +1 -0
  59. package/dist/features/magic-context/memory/verification-paths.d.ts.map +1 -1
  60. package/dist/features/magic-context/message-index-async.d.ts +1 -1
  61. package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
  62. package/dist/features/magic-context/project-embedding-registry.d.ts +5 -0
  63. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  64. package/dist/features/magic-context/search.d.ts.map +1 -1
  65. package/dist/features/magic-context/session-activity.d.ts +10 -0
  66. package/dist/features/magic-context/session-activity.d.ts.map +1 -0
  67. package/dist/features/magic-context/session-project-backfill.d.ts +4 -1
  68. package/dist/features/magic-context/session-project-backfill.d.ts.map +1 -1
  69. package/dist/features/magic-context/session-project-storage.d.ts +5 -5
  70. package/dist/features/magic-context/session-project-storage.d.ts.map +1 -1
  71. package/dist/features/magic-context/smart-notes/compiler.d.ts +5 -0
  72. package/dist/features/magic-context/smart-notes/compiler.d.ts.map +1 -1
  73. package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts +1 -0
  74. package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts.map +1 -1
  75. package/dist/features/magic-context/smart-notes/storage.d.ts +1 -1
  76. package/dist/features/magic-context/smart-notes/storage.d.ts.map +1 -1
  77. package/dist/features/magic-context/smart-notes/types.d.ts +2 -0
  78. package/dist/features/magic-context/smart-notes/types.d.ts.map +1 -1
  79. package/dist/features/magic-context/storage-db.d.ts +2 -1
  80. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  81. package/dist/features/magic-context/storage-embedding-measurements.d.ts.map +1 -1
  82. package/dist/features/magic-context/storage-identity-merge.d.ts +4 -1
  83. package/dist/features/magic-context/storage-identity-merge.d.ts.map +1 -1
  84. package/dist/features/magic-context/storage-session-tables.d.ts.map +1 -1
  85. package/dist/features/magic-context/v22-deferred-backfill.d.ts.map +1 -1
  86. package/dist/hooks/auto-update-checker/checker.d.ts.map +1 -1
  87. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  88. package/dist/hooks/magic-context/compartment-runner-historian.d.ts +2 -1
  89. package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
  90. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  91. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  92. package/dist/hooks/magic-context/compartment-runner-types.d.ts +1 -0
  93. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  94. package/dist/hooks/magic-context/compartment-runner.d.ts +1 -0
  95. package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
  96. package/dist/hooks/magic-context/derive-budgets.d.ts +4 -0
  97. package/dist/hooks/magic-context/derive-budgets.d.ts.map +1 -1
  98. package/dist/hooks/magic-context/dropped-input-guard.d.ts.map +1 -1
  99. package/dist/hooks/magic-context/embed-session-state.d.ts +3 -0
  100. package/dist/hooks/magic-context/embed-session-state.d.ts.map +1 -1
  101. package/dist/hooks/magic-context/event-handler.d.ts +1 -0
  102. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  103. package/dist/hooks/magic-context/event-payloads.d.ts +1 -0
  104. package/dist/hooks/magic-context/event-payloads.d.ts.map +1 -1
  105. package/dist/hooks/magic-context/historian-host-runner.d.ts.map +1 -1
  106. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  107. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  108. package/dist/hooks/magic-context/lkg-persist.d.ts +1 -4
  109. package/dist/hooks/magic-context/lkg-persist.d.ts.map +1 -1
  110. package/dist/hooks/magic-context/lkg-replay.d.ts +6 -4
  111. package/dist/hooks/magic-context/lkg-replay.d.ts.map +1 -1
  112. package/dist/hooks/magic-context/module-state-sync.d.ts.map +1 -1
  113. package/dist/hooks/magic-context/module-wire.d.ts.map +1 -1
  114. package/dist/hooks/magic-context/producer-window-guard.d.ts +4 -1
  115. package/dist/hooks/magic-context/producer-window-guard.d.ts.map +1 -1
  116. package/dist/hooks/magic-context/rust-mode-transform.d.ts +1 -0
  117. package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
  118. package/dist/hooks/magic-context/storage-busy-refusal.d.ts +13 -0
  119. package/dist/hooks/magic-context/storage-busy-refusal.d.ts.map +1 -0
  120. package/dist/hooks/magic-context/strip-content.d.ts +1 -0
  121. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  122. package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
  123. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  124. package/dist/{index-a9vbtdzv.js → index-33q7s9y2.js} +1 -1
  125. package/dist/{index-cz88kw0n.js → index-4an2p8qa.js} +183 -39
  126. package/dist/{index-d107az47.js → index-638hf49n.js} +1 -1
  127. package/dist/{index-stb5fg12.js → index-9y5p0d50.js} +31 -1
  128. package/dist/{index-s6b7h12g.js → index-dgg5y7er.js} +73 -2
  129. package/dist/{index-6w9p73jz.js → index-jqcbs2k1.js} +10 -0
  130. package/dist/{index-0rp6pj54.js → index-nf9sk8sh.js} +7 -5
  131. package/dist/index-q5p1aqvv.js +7260 -0
  132. package/dist/index-rdsaxnf2.js +125 -0
  133. package/dist/{index-jtdrvmcn.js → index-yhm60r53.js} +37544 -37072
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +8402 -14429
  136. package/dist/plugin/dream-timer.d.ts.map +1 -1
  137. package/dist/plugin/embedding-bootstrap.d.ts.map +1 -1
  138. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  139. package/dist/plugin/messages-transform.d.ts +10 -9
  140. package/dist/plugin/messages-transform.d.ts.map +1 -1
  141. package/dist/plugin/rpc-handlers.d.ts +1 -0
  142. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  143. package/dist/{read-session-chunk-fkdv2g5b.js → read-session-chunk-n072mcq4.js} +2 -2
  144. package/dist/{recomp-orchestrator-k4h5n0p3.js → recomp-orchestrator-19tr6zya.js} +6 -5
  145. package/dist/{send-session-notification-g2k03zyn.js → send-session-notification-04hvd54c.js} +2 -2
  146. package/dist/shared/model-suggestion-retry.d.ts +1 -1
  147. package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
  148. package/dist/shared/prompt-async-transport.d.ts +7 -7
  149. package/dist/shared/prompt-async-transport.d.ts.map +1 -1
  150. package/dist/shared/rpc-server.d.ts.map +1 -1
  151. package/dist/shared/rpc-utils.d.ts +21 -0
  152. package/dist/shared/rpc-utils.d.ts.map +1 -1
  153. package/dist/shared/run-token-log.d.ts +13 -0
  154. package/dist/shared/run-token-log.d.ts.map +1 -0
  155. package/dist/shared/sqlite-write-locker-test-support.d.ts.map +1 -1
  156. package/dist/shared/sqlite.d.ts +19 -0
  157. package/dist/shared/sqlite.d.ts.map +1 -1
  158. package/dist/shared/subagent-runner.d.ts +3 -1
  159. package/dist/shared/subagent-runner.d.ts.map +1 -1
  160. package/dist/shared/user-facing-codes.d.ts +5 -0
  161. package/dist/shared/user-facing-codes.d.ts.map +1 -1
  162. package/dist/{task-config-mgf5nsh3.js → task-config-2bm68628.js} +1 -1
  163. package/dist/transform-postprocess-phase-z2mfc5w1.js +36 -0
  164. package/dist/v2/hidden-completion.d.ts +9 -1
  165. package/dist/v2/hidden-completion.d.ts.map +1 -1
  166. package/dist/v2/hooks/context.d.ts.map +1 -1
  167. package/dist/v2/hooks/hidden-child.d.ts +1 -0
  168. package/dist/v2/hooks/hidden-child.d.ts.map +1 -1
  169. package/dist/v2/hooks/hidden-terminal-error.d.ts +8 -0
  170. package/dist/v2/hooks/hidden-terminal-error.d.ts.map +1 -0
  171. package/dist/v2/hooks/lkg-system.d.ts +11 -0
  172. package/dist/v2/hooks/lkg-system.d.ts.map +1 -0
  173. package/dist/v2/hooks/session-project-backfill.d.ts +1 -1
  174. package/dist/v2/hooks/session-project-backfill.d.ts.map +1 -1
  175. package/dist/v2/hooks/storage-gate.d.ts +11 -5
  176. package/dist/v2/hooks/storage-gate.d.ts.map +1 -1
  177. package/dist/v2/hooks/tools.d.ts +1 -1
  178. package/dist/v2/hooks/tools.d.ts.map +1 -1
  179. package/dist/v2/host-service.d.ts +2 -1
  180. package/dist/v2/host-service.d.ts.map +1 -1
  181. package/dist/v2/server.js +24948 -22659
  182. package/dist/v2/store-reader.d.ts +2 -0
  183. package/dist/v2/store-reader.d.ts.map +1 -1
  184. package/dist/{wrapup-orchestrator-35y56636.js → wrapup-orchestrator-sy4m56a7.js} +7 -6
  185. package/package.json +1 -1
  186. package/src/agents/dreamer.ts +4 -6
  187. package/src/agents/hidden-agent-registrations.ts +3 -3
  188. package/src/agents/permissions.test.ts +3 -4
  189. package/src/config/latch-permanence-guard.test.ts +13 -0
  190. package/src/config/live-child-output-cap.test.ts +26 -0
  191. package/src/config/live-child-output-cap.ts +6 -2
  192. package/src/config/schema/magic-context.ts +21 -6
  193. package/src/features/magic-context/dreamer/task-registry.ts +13 -0
  194. package/src/shared/data-path.test.ts +1 -1
  195. package/src/shared/logger.test.ts +1 -0
  196. package/src/shared/model-suggestion-retry.ts +9 -2
  197. package/src/shared/models-dev-cache.test.ts +30 -1
  198. package/src/shared/prompt-async-transport.test.ts +254 -1
  199. package/src/shared/prompt-async-transport.ts +153 -44
  200. package/src/shared/rpc-async-probes.test.ts +230 -0
  201. package/src/shared/rpc-server.ts +6 -4
  202. package/src/shared/rpc-utils.test.ts +27 -1
  203. package/src/shared/rpc-utils.ts +161 -9
  204. package/src/shared/run-token-log.test.ts +65 -0
  205. package/src/shared/run-token-log.ts +39 -0
  206. package/src/shared/sqlite-acquisition-fence.test.ts +132 -0
  207. package/src/shared/sqlite-acquisition-routing.test.ts +102 -0
  208. package/src/shared/sqlite-pass-scope.test.ts +153 -0
  209. package/src/shared/sqlite-transaction-mode-guard.test.ts +42 -10
  210. package/src/shared/sqlite-write-locker-test-support.ts +1 -0
  211. package/src/shared/sqlite.ts +245 -56
  212. package/src/shared/storage-busy.test.ts +72 -0
  213. package/src/shared/subagent-runner.ts +4 -0
  214. package/src/shared/user-facing-codes.ts +6 -0
  215. package/src/shared/windows-hidden-spawns.test.ts +132 -0
  216. package/src/shared/write-transaction-attribution.test.ts +14 -11
@@ -39,6 +39,7 @@
39
39
  * `run()` → {changes,lastInsertRowid}) is identical and was verified directly.
40
40
  */
41
41
 
42
+ import { AsyncLocalStorage } from "node:async_hooks";
42
43
  import { statSync } from "node:fs";
43
44
  // Type import only — runtime is loaded dynamically below. @types/better-sqlite3
44
45
  // has the richest definitions and is a structural superset of the API surface
@@ -211,6 +212,72 @@ export interface SqliteMemoryStats {
211
212
  const trackedSqliteConnections = new Map<number, TrackedSqliteConnection>();
212
213
  let nextSqliteConnectionSequence = 1;
213
214
 
215
+ /** Route native Bun and Node transaction entry through the same acquisition retry.
216
+ * Native Bun transaction wrappers execute BEGIN internally, bypassing an exec override,
217
+ * so both runtimes use this small synchronous wrapper instead. Writable handles
218
+ * default to IMMEDIATE; explicit deferred calls and readonly handles keep read
219
+ * snapshots without acquiring the writer lock. */
220
+ function installTransactionRouting(db: BetterSqlite3.Database, readonly: boolean): void {
221
+ const nativeExec = db.exec.bind(db);
222
+ Object.defineProperty(db, "exec", {
223
+ configurable: true,
224
+ writable: true,
225
+ value: (sql: string) => {
226
+ if (/^\s*BEGIN\s+(?:IMMEDIATE|EXCLUSIVE)(?:\s+TRANSACTION)?\s*;?\s*$/i.test(sql)) {
227
+ if (transformPassScope.getStore()?.active || backgroundWriterScope.getStore())
228
+ acquireShort(db, () => nativeExec(sql), sql.trim());
229
+ else nativeExec(sql);
230
+ return db;
231
+ }
232
+ return nativeExec(sql);
233
+ },
234
+ });
235
+ Object.defineProperty(db, "transaction", {
236
+ configurable: true,
237
+ writable: true,
238
+ // biome-ignore lint/suspicious/noExplicitAny: preserve the native callback's receiver and argument types.
239
+ value: (fn: (...args: any[]) => any) => {
240
+ const make = (mode: "IMMEDIATE" | "EXCLUSIVE" | "DEFERRED") =>
241
+ function (this: unknown, ...args: unknown[]): unknown {
242
+ const nested = isInTransaction(db);
243
+ // Equal savepoint names stack LIFO, matching both native backends.
244
+ const savepoint = "mc_tx_sp";
245
+ db.exec(nested ? `SAVEPOINT ${savepoint}` : `BEGIN ${mode}`);
246
+ try {
247
+ const result = fn.apply(this, args);
248
+ db.exec(nested ? `RELEASE ${savepoint}` : "COMMIT");
249
+ return result;
250
+ } catch (error) {
251
+ if (isInTransaction(db)) {
252
+ if (nested) {
253
+ db.exec(`ROLLBACK TO ${savepoint}`);
254
+ db.exec(`RELEASE ${savepoint}`);
255
+ } else db.exec("ROLLBACK");
256
+ }
257
+ throw error;
258
+ }
259
+ };
260
+ const defaultTransaction = make(readonly ? "DEFERRED" : "IMMEDIATE");
261
+ const variants = {
262
+ default: defaultTransaction,
263
+ deferred: make("DEFERRED"),
264
+ immediate: make("IMMEDIATE"),
265
+ exclusive: make("EXCLUSIVE"),
266
+ database: db,
267
+ };
268
+ for (const transaction of [
269
+ variants.default,
270
+ variants.deferred,
271
+ variants.immediate,
272
+ variants.exclusive,
273
+ ]) {
274
+ Object.assign(transaction, variants);
275
+ }
276
+ return defaultTransaction;
277
+ },
278
+ });
279
+ }
280
+
214
281
  function trackSqliteConnection(
215
282
  db: BetterSqlite3.Database,
216
283
  filename: unknown,
@@ -232,6 +299,7 @@ function trackSqliteConnection(
232
299
  ((options as { readonly?: unknown }).readonly === true ||
233
300
  (options as { readOnly?: unknown }).readOnly === true),
234
301
  };
302
+ installTransactionRouting(db, metadata.readonly);
235
303
  Object.defineProperty(db, "close", {
236
304
  configurable: true,
237
305
  value: (...args: unknown[]) => {
@@ -260,18 +328,10 @@ const TrackedDatabase = new Proxy(DatabaseImpl, {
260
328
  * Wrap node:sqlite's `DatabaseSync` so it presents the better-sqlite3/bun
261
329
  * surface the rest of the codebase calls:
262
330
  * - translate the `{ readonly }` constructor option → node:sqlite's `readOnly`
263
- * - add a `transaction(fn)` helper that matches better-sqlite3 semantics,
264
- * using `db.isTransaction` to pick BEGIN (top-level) vs SAVEPOINT (nested),
265
- * so it composes correctly with manual `BEGIN IMMEDIATE` blocks too.
331
+ * Transaction routing is installed for both runtimes by trackSqliteConnection.
266
332
  */
267
333
  // biome-ignore lint/suspicious/noExplicitAny: node:sqlite has no shipped types here; the public export is cast to the better-sqlite3 shape.
268
334
  function buildNodeSqliteDatabaseClass(DatabaseSync: any): typeof BetterSqlite3 {
269
- // Single constant savepoint name is correct for arbitrary nesting depth:
270
- // SQLite savepoints with the same name stack LIFO — RELEASE / ROLLBACK TO
271
- // always target the most recent. node:sqlite is synchronous + single-process
272
- // per connection, so there is no concurrent-savepoint hazard.
273
- const SAVEPOINT = "mc_tx_sp";
274
-
275
335
  class NodeSqliteDatabase extends DatabaseSync {
276
336
  constructor(filename?: string | Buffer, options?: BetterSqlite3.Options) {
277
337
  const translated: Record<string, unknown> = { ...options };
@@ -306,52 +366,6 @@ function buildNodeSqliteDatabaseClass(DatabaseSync: any): typeof BetterSqlite3 {
306
366
  }
307
367
  return stmt;
308
368
  }
309
-
310
- // biome-ignore lint/suspicious/noExplicitAny: mirrors better-sqlite3's generic transaction(fn) signature.
311
- transaction<F extends (...args: any[]) => any>(fn: F): F {
312
- // biome-ignore lint/suspicious/noExplicitAny: faithful pass-through of this/args to fn.
313
- const self = this as any;
314
- const execute = (
315
- mode: "" | "DEFERRED" | "IMMEDIATE" | "EXCLUSIVE",
316
- receiver: unknown,
317
- args: unknown[],
318
- ) => {
319
- const nested = self.isTransaction === true;
320
- self.exec(nested ? `SAVEPOINT ${SAVEPOINT}` : `BEGIN${mode ? ` ${mode}` : ""}`);
321
- try {
322
- const result = fn.apply(receiver, args);
323
- self.exec(nested ? `RELEASE ${SAVEPOINT}` : "COMMIT");
324
- return result;
325
- } catch (error) {
326
- if (nested) {
327
- // ROLLBACK TO unwinds the savepoint's changes but leaves
328
- // it on the stack; RELEASE then pops it (better-sqlite3
329
- // does both).
330
- self.exec(`ROLLBACK TO ${SAVEPOINT}`);
331
- self.exec(`RELEASE ${SAVEPOINT}`);
332
- } else {
333
- self.exec("ROLLBACK");
334
- }
335
- throw error;
336
- }
337
- };
338
- const wrapped = function (this: unknown, ...args: unknown[]): unknown {
339
- return execute("", this, args);
340
- };
341
- wrapped.default = function (this: unknown, ...args: unknown[]): unknown {
342
- return execute("", this, args);
343
- };
344
- wrapped.deferred = function (this: unknown, ...args: unknown[]): unknown {
345
- return execute("DEFERRED", this, args);
346
- };
347
- wrapped.immediate = function (this: unknown, ...args: unknown[]): unknown {
348
- return execute("IMMEDIATE", this, args);
349
- };
350
- wrapped.exclusive = function (this: unknown, ...args: unknown[]): unknown {
351
- return execute("EXCLUSIVE", this, args);
352
- };
353
- return wrapped as unknown as F;
354
- }
355
369
  }
356
370
 
357
371
  return NodeSqliteDatabase as unknown as typeof BetterSqlite3;
@@ -476,6 +490,175 @@ export type Database = BetterSqlite3.Database;
476
490
  export type Statement = BetterSqlite3.Statement<unknown[], unknown>;
477
491
 
478
492
  const privilegeDepth = new WeakMap<Database, number>();
493
+ const transformPassScope = new AsyncLocalStorage<{ active: boolean } | undefined>();
494
+ const admissionScope = new AsyncLocalStorage<boolean>();
495
+ const backgroundWriterScope = new AsyncLocalStorage<boolean>();
496
+
497
+ /** Opt in only a writer that can safely defer its work after a busy acquisition. */
498
+ export function withSqliteBackgroundWriter<T>(operation: () => T): T {
499
+ return backgroundWriterScope.run(true, operation);
500
+ }
501
+
502
+ /** Only an awaited foreground transform may spend the extra acquisition budget.
503
+ * The mutable lease also expires for detached descendants when that pass ends. */
504
+ export function withSqliteTransformPass<T>(operation: () => T): T {
505
+ const lease = { active: true };
506
+ return transformPassScope.run(lease, () => {
507
+ try {
508
+ const result = operation();
509
+ if (result && typeof (result as { then?: unknown }).then === "function") {
510
+ return Promise.resolve(result).finally(() => {
511
+ lease.active = false;
512
+ }) as T;
513
+ }
514
+ lease.active = false;
515
+ return result;
516
+ } catch (error) {
517
+ lease.active = false;
518
+ throw error;
519
+ }
520
+ });
521
+ }
522
+
523
+ /** Start detached maintenance work without borrowing a foreground pass's budget. */
524
+ export function withoutSqliteTransformPass<T>(operation: () => T): T {
525
+ return transformPassScope.run(undefined, operation);
526
+ }
527
+
528
+ /** Bun names SQLite codes; node:sqlite exposes the numeric (possibly extended) errcode. */
529
+ export function isTransientSqliteError(error: unknown): boolean {
530
+ if (!error || typeof error !== "object") return false;
531
+ const value = error as { code?: unknown; errcode?: unknown };
532
+ return (
533
+ (typeof value.code === "string" && /^(SQLITE_BUSY|SQLITE_LOCKED)(_|$)/.test(value.code)) ||
534
+ (typeof value.errcode === "number" && [5, 6].includes(value.errcode & 0xff))
535
+ );
536
+ }
537
+
538
+ export class SqliteAcquisitionBusyError extends Error {
539
+ readonly code = "SQLITE_BUSY";
540
+ readonly stage: string;
541
+ constructor(cause: unknown, stage = "BEGIN IMMEDIATE") {
542
+ super("SQLite writer acquisition remained busy", { cause });
543
+ this.name = "SqliteAcquisitionBusyError";
544
+ this.stage = stage;
545
+ }
546
+ }
547
+
548
+ const SHORT_BUSY_TIMEOUT_MS = 25;
549
+ const FOREGROUND_IN_PASS_BUSY_TIMEOUT_MS = 250;
550
+ const FOREGROUND_ACQUISITION_BUDGET_MS = 16_500;
551
+
552
+ /** The connection is never handed back to another caller with a shortened timeout. */
553
+ function acquireShort(db: Database, acquire: () => unknown, site: string): void {
554
+ const started = performance.now();
555
+ const previous = pragmaNumber(db, "busy_timeout");
556
+ let acquired = false;
557
+ try {
558
+ // Async admission retries after yielding, so each of its BEGIN attempts
559
+ // stays short. An ordinary BEGIN inside a transform pass cannot yield;
560
+ // give that single attempt enough time for a brief writer to finish.
561
+ const timeout =
562
+ transformPassScope.getStore()?.active &&
563
+ !admissionScope.getStore() &&
564
+ !backgroundWriterScope.getStore()
565
+ ? FOREGROUND_IN_PASS_BUSY_TIMEOUT_MS
566
+ : SHORT_BUSY_TIMEOUT_MS;
567
+ db.exec(`PRAGMA busy_timeout=${timeout}`);
568
+ acquire();
569
+ acquired = true;
570
+ } catch (error) {
571
+ if (transformPassScope.getStore()?.active && isTransientSqliteError(error))
572
+ throw new SqliteAcquisitionBusyError(error, site);
573
+ throw error;
574
+ } finally {
575
+ if (previous !== null) db.exec(`PRAGMA busy_timeout=${previous}`);
576
+ const elapsed = performance.now() - started;
577
+ if (elapsed >= 250)
578
+ console.warn(
579
+ `[${new Date().toISOString()}] [magic-context] sqlite acquisition site=${site} lane=${transformPassScope.getStore()?.active ? "foreground" : "background"} elapsed=${Math.round(elapsed)}ms attempts=1 outcome=${acquired ? "acquired" : "busy"}`,
580
+ );
581
+ }
582
+ }
583
+
584
+ /** Begin an immediate transaction with bounded asynchronous retries on writer contention.
585
+ * Callers must recheck any lease or source snapshot after this function returns. */
586
+ export async function beginSqliteWriterAsync(db: Database, site: string): Promise<void> {
587
+ const started = performance.now();
588
+ let attempts = 0;
589
+ for (;;) {
590
+ attempts++;
591
+ try {
592
+ withSqliteBackgroundWriter(() => db.exec("BEGIN IMMEDIATE"));
593
+ const elapsed = performance.now() - started;
594
+ if (elapsed >= 250)
595
+ console.warn(
596
+ `[${new Date().toISOString()}] [magic-context] sqlite acquisition site=${site} lane=background elapsed=${Math.round(elapsed)}ms attempts=${attempts} outcome=acquired`,
597
+ );
598
+ return;
599
+ } catch (error) {
600
+ if (!isTransientSqliteError(error)) throw error;
601
+ const elapsed = performance.now() - started;
602
+ if (elapsed >= FOREGROUND_ACQUISITION_BUDGET_MS) {
603
+ console.warn(
604
+ `[${new Date().toISOString()}] [magic-context] sqlite acquisition site=${site} lane=background elapsed=${Math.round(elapsed)}ms attempts=${attempts} outcome=busy`,
605
+ );
606
+ throw error;
607
+ }
608
+ await new Promise<void>((resolve) =>
609
+ setTimeout(
610
+ resolve,
611
+ Math.min(
612
+ attempts === 1 ? 500 : 1000,
613
+ FOREGROUND_ACQUISITION_BUDGET_MS - elapsed,
614
+ ),
615
+ ),
616
+ );
617
+ }
618
+ }
619
+ }
620
+
621
+ /** Retry admission before any turn work, yielding between short lock attempts. */
622
+ export async function withAsyncPrivilegedWriter<T>(db: Database, operation: () => T): Promise<T> {
623
+ const started = performance.now();
624
+ let attempts = 0;
625
+ for (;;) {
626
+ attempts++;
627
+ try {
628
+ const result = admissionScope.run(true, () =>
629
+ withSqliteTransformPass(() => withPrivilegedWriter(db, operation)),
630
+ );
631
+ const elapsed = performance.now() - started;
632
+ if (elapsed >= 250)
633
+ console.warn(
634
+ `[${new Date().toISOString()}] [magic-context] sqlite acquisition site=privileged_writer lane=foreground elapsed=${Math.round(elapsed)}ms attempts=${attempts} outcome=acquired`,
635
+ );
636
+ return result;
637
+ } catch (error) {
638
+ // A callback failure can occur after writes; only retry a failed BEGIN.
639
+ if (!(error instanceof SqliteAcquisitionBusyError)) throw error;
640
+ const elapsed = performance.now() - started;
641
+ if (elapsed >= FOREGROUND_ACQUISITION_BUDGET_MS) {
642
+ if (elapsed >= 250)
643
+ console.warn(
644
+ `[${new Date().toISOString()}] [magic-context] sqlite acquisition site=privileged_writer lane=foreground elapsed=${Math.round(elapsed)}ms attempts=${attempts} outcome=busy`,
645
+ );
646
+ throw error instanceof SqliteAcquisitionBusyError
647
+ ? error
648
+ : new SqliteAcquisitionBusyError(error);
649
+ }
650
+ await new Promise<void>((resolve) =>
651
+ setTimeout(
652
+ resolve,
653
+ Math.min(
654
+ attempts === 1 ? 500 : 1000,
655
+ FOREGROUND_ACQUISITION_BUDGET_MS - elapsed,
656
+ ),
657
+ ),
658
+ );
659
+ }
660
+ }
661
+ }
479
662
 
480
663
  function isInTransaction(db: Database): boolean {
481
664
  const candidate = db as unknown as { inTransaction?: unknown; isTransaction?: unknown };
@@ -501,7 +684,13 @@ export function withPrivilegedWriter<T>(db: Database, operation: () => T): T {
501
684
  if (nested) {
502
685
  db.exec(`SAVEPOINT ${savepoint}`);
503
686
  } else {
504
- db.exec("BEGIN IMMEDIATE");
687
+ try {
688
+ db.exec("BEGIN IMMEDIATE");
689
+ } catch (error) {
690
+ if (transformPassScope.getStore()?.active && isTransientSqliteError(error))
691
+ throw new SqliteAcquisitionBusyError(error);
692
+ throw error;
693
+ }
505
694
  }
506
695
  privilegeDepth.set(db, previousDepth + 1);
507
696
  try {
@@ -0,0 +1,72 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { mkdtempSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import {
6
+ Database,
7
+ isTransientSqliteError,
8
+ withAsyncPrivilegedWriter,
9
+ withPrivilegedWriter,
10
+ } from "./sqlite";
11
+
12
+ describe("writer acquisition", () => {
13
+ test("exhausted async admission preserves the foreground budget and never invokes the callback", async () => {
14
+ const dir = mkdtempSync(join(tmpdir(), "mc-writer-budget-"));
15
+ const path = join(dir, "context.db");
16
+ const blocker = new Database(path);
17
+ blocker.exec(
18
+ "CREATE TABLE context_privilege_state(id INTEGER PRIMARY KEY, enabled INTEGER)",
19
+ );
20
+ const db = new Database(path);
21
+ db.exec("PRAGMA busy_timeout=5000");
22
+ blocker.exec("BEGIN IMMEDIATE");
23
+ let calls = 0;
24
+ let ticks = 0;
25
+ const timer = setInterval(() => ticks++, 50);
26
+ const started = performance.now();
27
+ try {
28
+ await expect(withAsyncPrivilegedWriter(db, () => calls++)).rejects.toThrow(
29
+ "acquisition remained busy",
30
+ );
31
+ expect(performance.now() - started).toBeGreaterThanOrEqual(16_500);
32
+ expect(performance.now() - started).toBeLessThan(17_500);
33
+ expect(ticks).toBeGreaterThan(100);
34
+ expect(calls).toBe(0);
35
+ expect(db.prepare("PRAGMA busy_timeout").get()).toEqual({ timeout: 5000 });
36
+ } finally {
37
+ clearInterval(timer);
38
+ blocker.exec("ROLLBACK");
39
+ blocker.close();
40
+ db.close();
41
+ rmSync(dir, { recursive: true, force: true });
42
+ }
43
+ }, 30000);
44
+
45
+ test("busy callback rolls back without retrying mutations", () => {
46
+ const db = new Database(":memory:");
47
+ db.exec(
48
+ "CREATE TABLE context_privilege_state(id INTEGER PRIMARY KEY, enabled INTEGER); CREATE TABLE result(value TEXT)",
49
+ );
50
+ let calls = 0;
51
+ try {
52
+ expect(() =>
53
+ withPrivilegedWriter(db, () => {
54
+ calls++;
55
+ db.exec("INSERT INTO result VALUES ('partial')");
56
+ throw Object.assign(new Error("after acquisition"), { code: "SQLITE_BUSY" });
57
+ }),
58
+ ).toThrow("after acquisition");
59
+ expect(calls).toBe(1);
60
+ expect(db.prepare("SELECT * FROM result").all()).toEqual([]);
61
+ } finally {
62
+ db.close();
63
+ }
64
+ });
65
+
66
+ test("recognizes Bun extended and Node numeric contention codes only", () => {
67
+ expect(isTransientSqliteError({ code: "SQLITE_BUSY_SNAPSHOT" })).toBe(true);
68
+ expect(isTransientSqliteError({ code: "ERR_SQLITE_ERROR", errcode: 5 })).toBe(true);
69
+ expect(isTransientSqliteError({ code: "ERR_SQLITE_ERROR", errcode: 262 })).toBe(true);
70
+ expect(isTransientSqliteError({ code: "SQLITE_CORRUPT" })).toBe(false);
71
+ });
72
+ });
@@ -71,6 +71,8 @@ export interface SubagentRunOptions {
71
71
  temperature?: number | undefined;
72
72
  /** Requested output-token budget for the provider request. */
73
73
  maxOutputTokens?: number | undefined;
74
+ /** Per-child cumulative prompt tokens for tool-loop dreamer tasks. */
75
+ tokenBudget?: number | undefined;
74
76
  cwd?: string | undefined;
75
77
  signal?: AbortSignal | undefined;
76
78
  /**
@@ -208,6 +210,8 @@ export type SubagentRunResult =
208
210
  | {
209
211
  ok: false;
210
212
  reason:
213
+ | "step_limit"
214
+ | "token_budget"
211
215
  | "invalid_prompt"
212
216
  | "timeout"
213
217
  | "abort"
@@ -38,6 +38,11 @@ export const USER_FACING_FAILURES = {
38
38
  sentence: "Memory maintenance stopped at its hidden agent step limit.",
39
39
  action: "This task needs less work per run; changing the model connection will not help.",
40
40
  },
41
+ dream_token_budget: {
42
+ code: "MC-D11",
43
+ sentence: "Memory maintenance reached its prompt-token budget.",
44
+ action: "The unfinished items will be retried on the next run.",
45
+ },
41
46
  dream_empty_completion: {
42
47
  code: "MC-D03",
43
48
  sentence: "Memory maintenance received no usable response.",
@@ -285,6 +290,7 @@ const DREAM_FAILURE_KEYS = {
285
290
  provider_timeout: "dream_provider_timeout",
286
291
  provider_error: "dream_provider_error",
287
292
  step_limit: "dream_step_limit",
293
+ token_budget: "dream_token_budget",
288
294
  empty_completion: "dream_empty_completion",
289
295
  no_models: "dream_no_models",
290
296
  child_aborted: "dream_child_aborted",
@@ -0,0 +1,132 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { readdirSync, readFileSync } from "node:fs";
3
+ import { join, relative, resolve } from "node:path";
4
+ import ts from "typescript";
5
+
6
+ const root = resolve(import.meta.dir, "../../../..");
7
+ const subprocessNames = new Set([
8
+ "exec",
9
+ "execSync",
10
+ "execFile",
11
+ "execFileSync",
12
+ "spawn",
13
+ "spawnSync",
14
+ ]);
15
+
16
+ function sourceFiles(dir: string): string[] {
17
+ return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
18
+ const path = join(dir, entry.name);
19
+ return entry.isDirectory() ? sourceFiles(path) : path.endsWith(".ts") ? [path] : [];
20
+ });
21
+ }
22
+
23
+ function missingHiddenOptions(path: string): string[] {
24
+ const text = readFileSync(path, "utf8");
25
+ if (!/node:child_process|Bun\.spawn/.test(text)) return [];
26
+ const source = ts.createSourceFile(path, text, ts.ScriptTarget.Latest, true);
27
+ const imports = new Set<string>();
28
+ const variables = new Map<string, ts.Expression>();
29
+ for (const statement of source.statements) {
30
+ if (
31
+ ts.isImportDeclaration(statement) &&
32
+ statement.moduleSpecifier.getText(source).includes("node:child_process")
33
+ ) {
34
+ const bindings = statement.importClause?.namedBindings;
35
+ if (bindings && ts.isNamedImports(bindings)) {
36
+ for (const element of bindings.elements) {
37
+ if (subprocessNames.has(element.propertyName?.text ?? element.name.text))
38
+ imports.add(element.name.text);
39
+ }
40
+ }
41
+ }
42
+ }
43
+ function collect(node: ts.Node): void {
44
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer)
45
+ variables.set(node.name.text, node.initializer);
46
+ ts.forEachChild(node, collect);
47
+ }
48
+ collect(source);
49
+ function hidden(node: ts.Expression | undefined, seen = new Set<string>()): boolean {
50
+ if (!node) return false;
51
+ if (ts.isIdentifier(node)) {
52
+ if (seen.has(node.text)) return false;
53
+ seen.add(node.text);
54
+ return hidden(variables.get(node.text), seen);
55
+ }
56
+ if (!ts.isObjectLiteralExpression(node)) return false;
57
+ // Later spreads can override an earlier flag, so inspect the effective last assignment.
58
+ for (const property of [...node.properties].reverse()) {
59
+ if (
60
+ ts.isPropertyAssignment(property) &&
61
+ property.name.getText(source) === "windowsHide"
62
+ ) {
63
+ return property.initializer.kind === ts.SyntaxKind.TrueKeyword;
64
+ }
65
+ if (ts.isSpreadAssignment(property) && hidden(property.expression, new Set(seen)))
66
+ return true;
67
+ }
68
+ return false;
69
+ }
70
+ const failures: string[] = [];
71
+ function visit(node: ts.Node): void {
72
+ if (ts.isCallExpression(node)) {
73
+ const expression = node.expression;
74
+ let name: string | undefined;
75
+ if (
76
+ ts.isIdentifier(expression) &&
77
+ (imports.has(expression.text) ||
78
+ [
79
+ "execFileAsync",
80
+ "execFileSyncForIdentity",
81
+ "rpcIdentityExecFileSync",
82
+ "execFileForVerificationPaths",
83
+ ].includes(expression.text) ||
84
+ (expression.text === "exec" && path.endsWith("rpc-utils.ts")))
85
+ )
86
+ name = expression.text;
87
+ if (ts.isPropertyAccessExpression(expression)) {
88
+ const owner = expression.expression.getText(source);
89
+ if (
90
+ owner === "Bun" &&
91
+ (expression.name.text === "spawn" || expression.name.text === "spawnSync")
92
+ )
93
+ name = "Bun.spawn";
94
+ if (
95
+ (owner === "deps" || owner === "this") &&
96
+ (subprocessNames.has(expression.name.text) ||
97
+ expression.name.text === "spawnImpl")
98
+ )
99
+ name = expression.name.text;
100
+ }
101
+ if (name) {
102
+ const index =
103
+ name === "Bun.spawn"
104
+ ? node.arguments.length === 1
105
+ ? 0
106
+ : 1
107
+ : name === "execSync" || (name === "exec" && !path.endsWith("rpc-utils.ts"))
108
+ ? 1
109
+ : 2;
110
+ // A single-argument Bun.spawnSync({cmd, ...}) passes options as its first argument.
111
+ const option = node.arguments[index];
112
+ if (!hidden(option)) {
113
+ const line =
114
+ source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
115
+ failures.push(`${relative(root, path)}:${line} ${name}`);
116
+ }
117
+ }
118
+ }
119
+ ts.forEachChild(node, visit);
120
+ }
121
+ visit(source);
122
+ return failures;
123
+ }
124
+
125
+ describe("Windows child processes", () => {
126
+ test("every plugin, Pi and CLI subprocess hides its Windows console", () => {
127
+ const failures = ["plugin", "pi-plugin", "cli"].flatMap((pkg) =>
128
+ sourceFiles(join(root, "packages", pkg, "src")).flatMap(missingHiddenOptions),
129
+ );
130
+ expect(failures).toEqual([]);
131
+ });
132
+ });
@@ -6,7 +6,7 @@ import { join, resolve } from "node:path";
6
6
  const repoRoot = resolve(import.meta.dir, "../../../../");
7
7
  const beginImmediate = /\b(?:[A-Za-z_$][\w$]*\.)?exec\("BEGIN IMMEDIATE"\)/g;
8
8
 
9
- const fencedSources: Array<{ path: string; sites: string[] }> = [
9
+ const fencedSources: Array<{ path: string; sites: string[]; reporterCalls?: string[] }> = [
10
10
  {
11
11
  path: "packages/plugin/src/features/magic-context/smart-notes/storage.ts",
12
12
  sites: ["smart_note_commit"],
@@ -52,8 +52,8 @@ const fencedSources: Array<{ path: string; sites: string[] }> = [
52
52
  // The chokepoint cannot import the logging chain (Node executes it
53
53
  // directly), so it reports through the injected reporter instead.
54
54
  path: "packages/plugin/src/shared/sqlite.ts",
55
- sites: ["privileged_writer"],
56
- reporterCall: "reportSlowPrivilegedWrite?.(",
55
+ sites: ["async_acquisition_dynamic_site", "privileged_writer"],
56
+ reporterCalls: ["console.warn(", "reportSlowPrivilegedWrite?.("],
57
57
  },
58
58
  {
59
59
  path: "packages/plugin/src/features/magic-context/git-commits/sweep-coordinator.ts",
@@ -75,10 +75,6 @@ const fencedSources: Array<{ path: string; sites: string[] }> = [
75
75
  path: "packages/plugin/src/hooks/magic-context/compartment-runner-recomp.ts",
76
76
  sites: ["historian-publish:recomp"],
77
77
  },
78
- {
79
- path: "packages/plugin/src/hooks/magic-context/compartment-runner-incremental.ts",
80
- sites: ["historian-publish"],
81
- },
82
78
  {
83
79
  path: "packages/plugin/src/features/magic-context/dreamer/lease.ts",
84
80
  sites: ["lease_dynamic_site"],
@@ -98,6 +94,10 @@ const fencedSources: Array<{ path: string; sites: string[] }> = [
98
94
  ];
99
95
 
100
96
  const nonBeginCoveredSites: Array<{ path: string; site: string }> = [
97
+ {
98
+ path: "packages/plugin/src/hooks/magic-context/compartment-runner-incremental.ts",
99
+ site: "historian-publish",
100
+ },
101
101
  {
102
102
  path: "packages/plugin/src/features/magic-context/message-index.ts",
103
103
  site: "message_index_clear",
@@ -159,7 +159,7 @@ function sourceFor(relativePath: string): string {
159
159
  function assertCoveredBeginImmediate(
160
160
  source: string,
161
161
  sites: string[],
162
- reporterCall = "logSlowWriteTransaction(",
162
+ reporterCalls: string[] = [],
163
163
  ): void {
164
164
  const matches = [...source.matchAll(beginImmediate)];
165
165
  expect(matches).toHaveLength(sites.length);
@@ -167,8 +167,10 @@ function assertCoveredBeginImmediate(
167
167
  const match = matches[index];
168
168
  const nextBegin = matches[index + 1]?.index ?? source.length;
169
169
  const transactionRegion = source.slice(match.index, nextBegin);
170
- expect(transactionRegion).toContain(reporterCall);
171
- if (sites[index] === "lease_dynamic_site") {
170
+ expect(transactionRegion).toContain(reporterCalls[index] ?? "logSlowWriteTransaction(");
171
+ if (sites[index] === "async_acquisition_dynamic_site") {
172
+ expect(transactionRegion).toContain("site=${site}");
173
+ } else if (sites[index] === "lease_dynamic_site") {
172
174
  expect(transactionRegion).toContain("logSlowWriteTransaction(site");
173
175
  } else {
174
176
  expect(transactionRegion).toContain(`"${sites[index]}"`);
@@ -190,6 +192,7 @@ describe("write transaction attribution fences", () => {
190
192
  logger.flushLogger();
191
193
  `;
192
194
  const child = Bun.spawn({
195
+ windowsHide: true,
193
196
  cmd: ["bun", "--eval", scenario],
194
197
  cwd: import.meta.dir,
195
198
  env: {
@@ -218,7 +221,7 @@ describe("write transaction attribution fences", () => {
218
221
  assertCoveredBeginImmediate(
219
222
  sourceFor(fencedSource.path),
220
223
  fencedSource.sites,
221
- "reporterCall" in fencedSource ? fencedSource.reporterCall : undefined,
224
+ fencedSource.reporterCalls,
222
225
  );
223
226
  }
224
227
  for (const coveredSite of nonBeginCoveredSites) {