@yansigit/opencodex 2.36.1-dev.20260829.32 → 2.36.1-dev.20260829.34

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 (33) hide show
  1. package/gui/dist/assets/{ApiKeys-nJ-qJbkG.js → ApiKeys-BthcYAks.js} +1 -1
  2. package/gui/dist/assets/{Claude-Br34aNO2.js → Claude-D1uQ1rsr.js} +1 -1
  3. package/gui/dist/assets/{CodexSet-Bo2Vel9f.js → CodexSet-BUNKAStQ.js} +1 -1
  4. package/gui/dist/assets/{FileIntegrationPage-8JuhUwT7.js → FileIntegrationPage-CyrJ_kz1.js} +1 -1
  5. package/gui/dist/assets/{Grok-pTPUqi3L.js → Grok-CeSYaWba.js} +1 -1
  6. package/gui/dist/assets/{Integrations-N-s59k0n.js → Integrations-BkE9NLJW.js} +2 -2
  7. package/gui/dist/assets/{IntegrationsOverview-C6Mkt8OU.js → IntegrationsOverview-D-nDl5t5.js} +1 -1
  8. package/gui/dist/assets/{Logs-BzYAbD9a.js → Logs-uExIBMZr.js} +1 -1
  9. package/gui/dist/assets/{Models-8o4TdPzd.js → Models-CNpjs-ld.js} +1 -1
  10. package/gui/dist/assets/{NumberStepper-y1MLMfxu.js → NumberStepper-CKVXkKzE.js} +1 -1
  11. package/gui/dist/assets/{Providers-oTh_-wqc.js → Providers-D72-5Egl.js} +1 -1
  12. package/gui/dist/assets/{RestoreDialog-BnPA60nS.js → RestoreDialog-2MVe4MQR.js} +1 -1
  13. package/gui/dist/assets/{Startup-eM7zbmU_.js → Startup-ClZlpU_Z.js} +1 -1
  14. package/gui/dist/assets/{Storage-Cuf1wakW.js → Storage-ChC2WGMp.js} +1 -1
  15. package/gui/dist/assets/{Subagents-DEnwUJJx.js → Subagents-BpkXAXLZ.js} +1 -1
  16. package/gui/dist/assets/{Usage-BMBOTm6l.js → Usage-BkEOitI3.js} +1 -1
  17. package/gui/dist/assets/{data-surface-Cq2fDI2w.js → data-surface-BzhqUIJ_.js} +1 -1
  18. package/gui/dist/assets/{index-CEcmjvjC.js → index-DE4QGJSt.js} +2 -2
  19. package/gui/dist/assets/{model-display-CTtXLqIv.js → model-display-CCqxubZd.js} +1 -1
  20. package/gui/dist/assets/{provider-payload-Bs3Sdqv7.js → provider-payload-CI2ayvqv.js} +1 -1
  21. package/gui/dist/index.html +1 -1
  22. package/package.json +1 -1
  23. package/src/adapters/command-code-project-context.ts +24 -19
  24. package/src/codex/catalog/sync.ts +7 -2
  25. package/src/generated/compatibility-version.json +11 -11
  26. package/src/oauth/google-antigravity.ts +18 -5
  27. package/src/storage/cleanup.ts +11 -0
  28. package/src/storage/policy-job.ts +29 -3
  29. package/src/storage/policy-worker.ts +8 -1
  30. package/src/storage/policy.ts +3 -0
  31. package/src/storage/restore-job.ts +5 -0
  32. package/src/storage/restore-worker.ts +7 -1
  33. package/src/storage/storage-mutation-coordinator.ts +6 -0
@@ -10,7 +10,7 @@
10
10
  },
11
11
  {
12
12
  "path": "package.json",
13
- "sha256": "938f15acfdefc9e0d92aa9b59788c73d53d0e8cebd3b2e4a5194ecd1ead2a373"
13
+ "sha256": "3d5021c8d2ed4fd75d71843af16e27288d71580386a8fb5b6f9d2c2ea2851fb6"
14
14
  },
15
15
  {
16
16
  "path": "scripts/model-metadata.source.json",
@@ -54,7 +54,7 @@
54
54
  },
55
55
  {
56
56
  "path": "src/adapters/command-code-project-context.ts",
57
- "sha256": "72bcc19ad2ce2a3f743391d6388c67912e3cf3bfc244dd6b410638c2c34241a5"
57
+ "sha256": "44c4f2ce44231fb19d7068363b6a13f38c8d7fb7ffd7e049f3c47da967abb7ab"
58
58
  },
59
59
  {
60
60
  "path": "src/adapters/command-code.ts",
@@ -814,7 +814,7 @@
814
814
  },
815
815
  {
816
816
  "path": "src/codex/catalog/sync.ts",
817
- "sha256": "ca087dfda6c09914a97aff53a3427e8b56d7e27f9db1e942a435e1fdbaaa4174"
817
+ "sha256": "f03b4c28e4be29f9df3784d1cb58bc83ca8a58e646d91894b9a90fa339508be4"
818
818
  },
819
819
  {
820
820
  "path": "src/codex/codex-write-lock.ts",
@@ -2166,7 +2166,7 @@
2166
2166
  },
2167
2167
  {
2168
2168
  "path": "src/oauth/google-antigravity.ts",
2169
- "sha256": "babefb5a2b6648bf97b47e5cb1ceb52c6c03443625f02195175d6c14b8d83bc0"
2169
+ "sha256": "a5546286e647862c4b6be097495e10ec75ba0026f4c20ab8cb277c1d2697893d"
2170
2170
  },
2171
2171
  {
2172
2172
  "path": "src/oauth/health.ts",
@@ -3074,11 +3074,11 @@
3074
3074
  },
3075
3075
  {
3076
3076
  "path": "src/storage/cleanup.ts",
3077
- "sha256": "57a8a06f9f2a898fa129766124cbbc51596f5c6a650c3ea22b9ba02e139cc20a"
3077
+ "sha256": "2a90e83983c7da9bdb2ac0134870dd7ba85d4469237fae798c60b34985c8e554"
3078
3078
  },
3079
3079
  {
3080
3080
  "path": "src/storage/policy-job.ts",
3081
- "sha256": "25b014d6b188505ab713640bb9ba223807d33c4258d15450a7b3397adec1f4bc"
3081
+ "sha256": "4fa9f2297feb1a9404af6173adbf875c19f5e79f4b1aca0056744e83d503ad8a"
3082
3082
  },
3083
3083
  {
3084
3084
  "path": "src/storage/policy-scheduler.ts",
@@ -3086,19 +3086,19 @@
3086
3086
  },
3087
3087
  {
3088
3088
  "path": "src/storage/policy-worker.ts",
3089
- "sha256": "e4637c162efd2f91cf291d9eecc575f71ca8322afa1868146e4a6f387bbaaee7"
3089
+ "sha256": "c33d74fe319fc9103bd1937a81c8f0b23f319d7f634bea87a691da845ebec282"
3090
3090
  },
3091
3091
  {
3092
3092
  "path": "src/storage/policy.ts",
3093
- "sha256": "64df434d0bc5fb2c9f9ef3cada20c34a439f1bd658a23c251b3b17cdadf4412d"
3093
+ "sha256": "93050c0be848f0df9c7114d903272738fb817b62fb0cdd9d7a59e55b9b267601"
3094
3094
  },
3095
3095
  {
3096
3096
  "path": "src/storage/restore-job.ts",
3097
- "sha256": "b3bd80c88d96cd79a57fb964f58b3134a00d556e56bac01f5d4718515306048e"
3097
+ "sha256": "216a1a1c3610f5d3fbf50952855b792583f851bc989294f986208c93fd9eefaa"
3098
3098
  },
3099
3099
  {
3100
3100
  "path": "src/storage/restore-worker.ts",
3101
- "sha256": "a74ecc8a3ef5d1991da15bcb24a60d43c877ee1bd698e65f0ba86d97750bf96d"
3101
+ "sha256": "4ff17868a3e04fd28db86269c17d2e5600cf6e0222374fc3032854cb0990c766"
3102
3102
  },
3103
3103
  {
3104
3104
  "path": "src/storage/scanner.ts",
@@ -3106,7 +3106,7 @@
3106
3106
  },
3107
3107
  {
3108
3108
  "path": "src/storage/storage-mutation-coordinator.ts",
3109
- "sha256": "b669af88a7c66d1d07b6c4b6cf6111409a07790a74349a22022ffac602832694"
3109
+ "sha256": "65d0560e25f73d6b4bbfb64168cfd37bd796d74781368819207339be254df51b"
3110
3110
  },
3111
3111
  {
3112
3112
  "path": "src/storage/worker-lifecycle.ts",
@@ -37,7 +37,13 @@ const CALLBACK_PATH = "/callback";
37
37
  const REFRESH_SKEW_MS = 5 * 60 * 1000;
38
38
  const REQUEST_TIMEOUT_MS = 30_000;
39
39
  const ONBOARD_ATTEMPTS = 5;
40
- const ONBOARD_POLL_MS = 2_000;
40
+ const ANTIGRAVITY_ONBOARD_POLL_MS = 2_000;
41
+ const defaultSleep = (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms));
42
+ let sleepForTests: ((ms: number) => Promise<void>) | undefined;
43
+
44
+ export function setAntigravityOnboardSleepForTests(sleep: ((ms: number) => Promise<void>) | undefined): void {
45
+ sleepForTests = sleep;
46
+ }
41
47
 
42
48
  function requestSignal(signal: AbortSignal | undefined): AbortSignal {
43
49
  const timeout = AbortSignal.timeout(REQUEST_TIMEOUT_MS);
@@ -136,7 +142,11 @@ async function loadCodeAssistProject(accessToken: string, signal?: AbortSignal):
136
142
  return extractProjectId((await response.json().catch(() => undefined)) as Record<string, unknown> | undefined);
137
143
  }
138
144
 
139
- async function onboardProject(accessToken: string, signal?: AbortSignal): Promise<string | undefined> {
145
+ async function onboardProject(
146
+ accessToken: string,
147
+ signal?: AbortSignal,
148
+ ): Promise<string | undefined> {
149
+ const sleep = sleepForTests ?? defaultSleep;
140
150
  for (let attempt = 0; attempt < ONBOARD_ATTEMPTS; attempt++) {
141
151
  if (signal?.aborted) throw signal.reason ?? new Error("Antigravity onboarding aborted");
142
152
  const response = await fetch(`${DAILY_API}/${API_VERSION}:onboardUser`, {
@@ -153,7 +163,7 @@ async function onboardProject(accessToken: string, signal?: AbortSignal): Promis
153
163
  if (!response.ok) {
154
164
  // Transient (429/5xx): keep polling within the attempt budget. Hard 4xx: give up now.
155
165
  if (response.status === 429 || response.status >= 500) {
156
- await new Promise(resolve => setTimeout(resolve, ONBOARD_POLL_MS));
166
+ await sleep(ANTIGRAVITY_ONBOARD_POLL_MS);
157
167
  continue;
158
168
  }
159
169
  return undefined;
@@ -162,13 +172,16 @@ async function onboardProject(accessToken: string, signal?: AbortSignal): Promis
162
172
  if (data.done === true) {
163
173
  return extractProjectId(data.response as Record<string, unknown> | undefined);
164
174
  }
165
- await new Promise(resolve => setTimeout(resolve, ONBOARD_POLL_MS));
175
+ await sleep(ANTIGRAVITY_ONBOARD_POLL_MS);
166
176
  }
167
177
  return undefined;
168
178
  }
169
179
 
170
180
  /** Discover the CCA project for an access token (loadCodeAssist → onboardUser fallback). */
171
- export async function discoverAntigravityProject(accessToken: string, signal?: AbortSignal): Promise<string | undefined> {
181
+ export async function discoverAntigravityProject(
182
+ accessToken: string,
183
+ signal?: AbortSignal,
184
+ ): Promise<string | undefined> {
172
185
  return (await loadCodeAssistProject(accessToken, signal)) ?? (await onboardProject(accessToken, signal));
173
186
  }
174
187
 
@@ -2578,6 +2578,10 @@ export interface RestoreTestHooks {
2578
2578
  * reconcile, so cleanup can race an in-flight restore.
2579
2579
  */
2580
2580
  holdAfterFileMovesMs?: number;
2581
+ /** Internal test seam: pause after moves until the owner releases it. */
2582
+ waitAfterFileMoves?: SharedArrayBuffer;
2583
+ /** Internal worker-only notification after rollout moves complete. */
2584
+ notifyAfterFileMoves?: boolean;
2581
2585
  }
2582
2586
 
2583
2587
  /**
@@ -2924,6 +2928,13 @@ export function restoreTrashEntry(
2924
2928
  while (Date.now() < deadline) { /* test-only spin wait */ }
2925
2929
  }
2926
2930
  }
2931
+ if (hooks?.waitAfterFileMoves) {
2932
+ if (hooks.notifyAfterFileMoves) {
2933
+ Atomics.store(new Int32Array(hooks.waitAfterFileMoves), 1, 1);
2934
+ Atomics.notify(new Int32Array(hooks.waitAfterFileMoves), 1);
2935
+ }
2936
+ Atomics.wait(new Int32Array(hooks.waitAfterFileMoves), 0, 0);
2937
+ }
2927
2938
 
2928
2939
  if (hooks?.failAfterFileMoves) {
2929
2940
  return abortAfterMoves("fs_failed");
@@ -62,6 +62,14 @@ export interface PolicyJobTestHooks {
62
62
  * policy load, so concurrent PUTs can race completion metadata writes.
63
63
  */
64
64
  blockMs?: number;
65
+ /** Notify race tests once the shared mutation lease is acquired. */
66
+ onAcquired?: () => void;
67
+ /** Notify after the worker has loaded its policy snapshot. */
68
+ onPolicyLoaded?: () => void;
69
+ /** Test-only worker gate after the policy snapshot, before metadata save. */
70
+ waitAfterPolicyLoadedBuffer?: SharedArrayBuffer;
71
+ /** Keep the policy job at the lease boundary until a competing request is issued. */
72
+ waitForRelease?: Promise<void>;
65
73
  /**
66
74
  * When true, run on the main thread via `queueMicrotask` + optional sleep.
67
75
  * Used only for unit tests that cannot spawn workers; responsiveness tests
@@ -292,13 +300,14 @@ function applyMutationBusy(): void {
292
300
  };
293
301
  }
294
302
 
295
- function runInWorker(opts: RequestPolicyRunOptions & { blockMs?: number }): Promise<PolicyRunResult> {
303
+ function runInWorker(opts: RequestPolicyRunOptions & { blockMs?: number; notifyLoaded?: () => void; waitAfterPolicyLoadedBuffer?: SharedArrayBuffer }): Promise<PolicyRunResult> {
296
304
  const reservation = tryReserveStorageWorker();
297
305
  if (!reservation) return Promise.reject(new StorageWorkerAdmissionBusyError());
298
306
  return withStorageWorkerSpawnGate(() => new Promise<PolicyRunResult>((resolve, reject) => {
299
307
  const requestId = crypto.randomUUID();
300
308
  let settled = false;
301
309
  let worker: Worker;
310
+ const loadedRelease = opts.waitAfterPolicyLoadedBuffer;
302
311
  try {
303
312
  worker = new Worker(new URL("./policy-worker.ts", import.meta.url).href);
304
313
  reservation.bind(worker);
@@ -335,6 +344,10 @@ function runInWorker(opts: RequestPolicyRunOptions & { blockMs?: number }): Prom
335
344
  if (!data || typeof data !== "object") return;
336
345
  const msg = data as Record<string, unknown>;
337
346
  if (msg.requestId !== requestId) return;
347
+ if (msg.type === "loaded") {
348
+ opts.notifyLoaded?.();
349
+ return;
350
+ }
338
351
  if (msg.type === "done" && msg.result && typeof msg.result === "object") {
339
352
  finish(() => resolve(msg.result as PolicyRunResult));
340
353
  return;
@@ -357,6 +370,9 @@ function runInWorker(opts: RequestPolicyRunOptions & { blockMs?: number }): Prom
357
370
  ...(opts.codexHome ? { codexHome: opts.codexHome } : {}),
358
371
  ...(opts.busyTimeoutMs !== undefined ? { busyTimeoutMs: opts.busyTimeoutMs } : {}),
359
372
  ...(opts.blockMs !== undefined ? { blockMs: opts.blockMs } : {}),
373
+ ...(opts.notifyLoaded ? { notifyLoaded: true } : {}),
374
+ ...(loadedRelease ? { waitForLoadedRelease: true } : {}),
375
+ ...(loadedRelease ? { loadedRelease } : {}),
360
376
  env: {
361
377
  ...(process.env.CODEX_HOME ? { CODEX_HOME: process.env.CODEX_HOME } : {}),
362
378
  ...(process.env.OPENCODEX_HOME ? { OPENCODEX_HOME: process.env.OPENCODEX_HOME } : {}),
@@ -378,8 +394,12 @@ async function executeJob(opts: RequestPolicyRunOptions): Promise<void> {
378
394
  }
379
395
  return;
380
396
  }
381
- heldMutationLease = gate.lease;
397
+ const lease = gate.lease;
398
+ heldMutationLease = lease;
382
399
  try {
400
+ testHooks?.onAcquired?.();
401
+ await testHooks?.waitForRelease;
402
+ if (generation !== runGeneration) return;
383
403
  const blockMs = testHooks?.blockMs;
384
404
  let result: PolicyRunResult;
385
405
 
@@ -392,12 +412,15 @@ async function executeJob(opts: RequestPolicyRunOptions): Promise<void> {
392
412
  codexHome,
393
413
  ...(opts.busyTimeoutMs !== undefined ? { busyTimeoutMs: opts.busyTimeoutMs } : {}),
394
414
  ...(typeof blockMs === "number" && blockMs > 0 ? { holdAfterLoadMs: blockMs } : {}),
415
+ ...(testHooks?.onPolicyLoaded ? { onPolicyLoaded: testHooks.onPolicyLoaded } : {}),
395
416
  });
396
417
  } else {
397
418
  result = await runInWorker({
398
419
  ...opts,
399
420
  codexHome,
400
421
  ...(typeof blockMs === "number" && blockMs > 0 ? { blockMs } : {}),
422
+ ...(testHooks?.onPolicyLoaded ? { notifyLoaded: testHooks.onPolicyLoaded } : {}),
423
+ ...(testHooks?.waitAfterPolicyLoadedBuffer ? { waitAfterPolicyLoadedBuffer: testHooks.waitAfterPolicyLoadedBuffer } : {}),
401
424
  });
402
425
  }
403
426
 
@@ -408,7 +431,10 @@ async function executeJob(opts: RequestPolicyRunOptions): Promise<void> {
408
431
  if (err instanceof StorageWorkerAdmissionBusyError) applyMutationBusy();
409
432
  else applyFailed(err instanceof Error ? err.message : "worker_failed");
410
433
  } finally {
411
- releaseHeldMutationSlot();
434
+ // A reset may let a newer run acquire the global mirror before this stale
435
+ // continuation resumes. Release only the lease captured by this run.
436
+ lease.release();
437
+ if (heldMutationLease === lease) heldMutationLease = undefined;
412
438
  }
413
439
  }
414
440
 
@@ -13,6 +13,9 @@ interface RunMessage {
13
13
  busyTimeoutMs?: number;
14
14
  /** Test-only: block after loading the start-of-job policy (see holdAfterLoadMs). */
15
15
  blockMs?: number;
16
+ notifyLoaded?: boolean;
17
+ waitForLoadedRelease?: boolean;
18
+ loadedRelease?: SharedArrayBuffer;
16
19
  /** Env snapshot — Workers may not see parent mutations on all platforms. */
17
20
  env?: { CODEX_HOME?: string; OPENCODEX_HOME?: string };
18
21
  }
@@ -29,7 +32,7 @@ declare const self: Worker;
29
32
 
30
33
  self.onmessage = (event: MessageEvent<unknown>) => {
31
34
  if (!isRunMessage(event.data)) return;
32
- const { requestId, reason, force, codexHome, busyTimeoutMs, blockMs, env } = event.data;
35
+ const { requestId, reason, force, codexHome, busyTimeoutMs, blockMs, notifyLoaded, waitForLoadedRelease, loadedRelease, env } = event.data;
33
36
  try {
34
37
  if (env?.CODEX_HOME) process.env.CODEX_HOME = env.CODEX_HOME;
35
38
  if (env?.OPENCODEX_HOME) process.env.OPENCODEX_HOME = env.OPENCODEX_HOME;
@@ -38,6 +41,10 @@ self.onmessage = (event: MessageEvent<unknown>) => {
38
41
  force: force === true,
39
42
  ...(codexHome ? { codexHome } : {}),
40
43
  ...(busyTimeoutMs !== undefined ? { busyTimeoutMs } : {}),
44
+ ...(notifyLoaded ? { onPolicyLoaded: () => {
45
+ self.postMessage({ type: "loaded", requestId });
46
+ if (waitForLoadedRelease && loadedRelease) Atomics.wait(new Int32Array(loadedRelease), 0, 0);
47
+ } } : {}),
41
48
  ...(typeof blockMs === "number" && Number.isFinite(blockMs) && blockMs > 0
42
49
  ? { holdAfterLoadMs: Math.floor(blockMs) }
43
50
  : {}),
@@ -72,6 +72,8 @@ export interface PolicyRunDeps {
72
72
  * land before completion merges run metadata.
73
73
  */
74
74
  holdAfterLoadMs?: number;
75
+ /** Test-only synchronization seam; omitted in production. */
76
+ onPolicyLoaded?: () => void;
75
77
  }
76
78
 
77
79
  /** Canonical defaults — enabled is always false. */
@@ -436,6 +438,7 @@ export function runStorageCleanupPolicy(deps: PolicyRunDeps): PolicyRunResult {
436
438
  const execute = deps.execute ?? executeArchivedCleanup;
437
439
 
438
440
  const policy = normalizeStorageCleanupPolicy(load());
441
+ deps.onPolicyLoaded?.();
439
442
 
440
443
  if (typeof deps.holdAfterLoadMs === "number" && Number.isFinite(deps.holdAfterLoadMs) && deps.holdAfterLoadMs > 0) {
441
444
  Bun.sleepSync(Math.floor(deps.holdAfterLoadMs));
@@ -35,6 +35,8 @@ export interface RestoreJobTestHooks extends StorageMutationCoordinatorTestHooks
35
35
  enableTestStream?: boolean;
36
36
  /** Forwarded to restoreTrashEntry _test hooks. */
37
37
  restoreTest?: RestoreTestHooks;
38
+ /** Notify responsiveness tests once the worker is about to block. */
39
+ workerReady?: SharedArrayBuffer;
38
40
  }
39
41
 
40
42
  const WORKER_TIMEOUT_MS = 10 * 60 * 1000;
@@ -159,6 +161,7 @@ function runInWorker(opts: {
159
161
  busyTimeoutMs?: number;
160
162
  blockMs?: number;
161
163
  restoreTest?: RestoreTestHooks;
164
+ workerReady?: SharedArrayBuffer;
162
165
  }): Promise<RestoreResult> {
163
166
  const reservation = tryReserveStorageWorker();
164
167
  if (!reservation) return Promise.reject(new StorageWorkerAdmissionBusyError());
@@ -222,6 +225,7 @@ function runInWorker(opts: {
222
225
  ...(opts.busyTimeoutMs !== undefined ? { busyTimeoutMs: opts.busyTimeoutMs } : {}),
223
226
  ...(opts.blockMs !== undefined ? { blockMs: opts.blockMs } : {}),
224
227
  ...(opts.restoreTest ? { restoreTest: opts.restoreTest } : {}),
228
+ ...(opts.workerReady ? { workerReady: opts.workerReady } : {}),
225
229
  env: {
226
230
  ...(process.env.CODEX_HOME ? { CODEX_HOME: process.env.CODEX_HOME } : {}),
227
231
  ...(process.env.OPENCODEX_HOME ? { OPENCODEX_HOME: process.env.OPENCODEX_HOME } : {}),
@@ -258,6 +262,7 @@ async function executeRestore(opts: {
258
262
  ...(opts.busyTimeoutMs !== undefined ? { busyTimeoutMs: opts.busyTimeoutMs } : {}),
259
263
  ...(typeof blockMs === "number" && blockMs > 0 ? { blockMs } : {}),
260
264
  ...(restoreTest ? { restoreTest } : {}),
265
+ ...(testHooks?.workerReady ? { workerReady: testHooks.workerReady } : {}),
261
266
  });
262
267
  } catch (err) {
263
268
  if (err instanceof StorageWorkerAdmissionBusyError) return busyRestoreResult();
@@ -13,6 +13,7 @@ interface RunMessage {
13
13
  /** Test-only: block before restore so responsiveness tests can probe /healthz. */
14
14
  blockMs?: number;
15
15
  restoreTest?: RestoreTestHooks;
16
+ workerReady?: SharedArrayBuffer;
16
17
  /** Env snapshot — Workers may not see parent mutations on all platforms. */
17
18
  env?: { CODEX_HOME?: string; OPENCODEX_HOME?: string };
18
19
  }
@@ -27,11 +28,16 @@ declare const self: Worker;
27
28
 
28
29
  self.onmessage = async (event: MessageEvent<unknown>) => {
29
30
  if (!isRunMessage(event.data)) return;
30
- const { requestId, trashId, codexHome, busyTimeoutMs, blockMs, restoreTest, env } = event.data;
31
+ const { requestId, trashId, codexHome, busyTimeoutMs, blockMs, restoreTest, workerReady, env } = event.data;
31
32
  try {
32
33
  if (env?.CODEX_HOME) process.env.CODEX_HOME = env.CODEX_HOME;
33
34
  if (env?.OPENCODEX_HOME) process.env.OPENCODEX_HOME = env.OPENCODEX_HOME;
34
35
  if (typeof blockMs === "number" && Number.isFinite(blockMs) && blockMs > 0) {
36
+ if (workerReady) {
37
+ const ready = new Int32Array(workerReady);
38
+ Atomics.store(ready, 0, 1);
39
+ Atomics.notify(ready, 0);
40
+ }
35
41
  await Bun.sleep(Math.floor(blockMs));
36
42
  }
37
43
  const result = restoreTrashEntry(trashId, {
@@ -16,6 +16,10 @@ export type StorageMutationBusyError = "storage_mutation_busy";
16
16
  export interface StorageMutationCoordinatorTestHooks {
17
17
  /** Block after acquiring the slot, before mutation work (race tests). */
18
18
  blockMs?: number;
19
+ /** Notify race tests as soon as the per-home lease is owned. */
20
+ onAcquired?: () => void;
21
+ /** Keep the lease held until a race test has issued its competing operation. */
22
+ waitForRelease?: Promise<void>;
19
23
  }
20
24
 
21
25
  interface ActiveSlot {
@@ -79,6 +83,7 @@ export function tryBeginStorageMutation(
79
83
  },
80
84
  };
81
85
  slots.set(key, { kind, startedAt: Date.now(), lease: ownerLease });
86
+ testHooks?.onAcquired?.();
82
87
  return { acquired: true, lease: ownerLease };
83
88
  }
84
89
 
@@ -94,6 +99,7 @@ export function endStorageMutation(codexHome?: string): void {
94
99
  }
95
100
 
96
101
  async function applyCoordinatorBlock(): Promise<void> {
102
+ await testHooks?.waitForRelease;
97
103
  const blockMs = testHooks?.blockMs;
98
104
  if (typeof blockMs === "number" && Number.isFinite(blockMs) && blockMs > 0) {
99
105
  await Bun.sleep(Math.floor(blockMs));