@bitkyc08/opencodex 2.37.0 → 2.39.0-preview.20260901

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 (131) hide show
  1. package/bin/ocx.mjs +127 -16
  2. package/gui/dist/assets/index-6HZDkIpX.js +112 -0
  3. package/gui/dist/assets/index-uvENYLin.css +1 -0
  4. package/gui/dist/index.html +2 -2
  5. package/gui/dist/provider-icons/aside.svg +3 -0
  6. package/gui/dist/provider-icons/baseten.svg +13 -0
  7. package/gui/dist/provider-icons/bizrouter.svg +41 -0
  8. package/gui/dist/provider-icons/cerebras.svg +26 -0
  9. package/gui/dist/provider-icons/deepinfra.svg +75 -0
  10. package/gui/dist/provider-icons/deepseek-harness.svg +3 -0
  11. package/gui/dist/provider-icons/digitalocean.svg +10 -0
  12. package/gui/dist/provider-icons/featherless.svg +4 -0
  13. package/gui/dist/provider-icons/gajae-code.svg +410 -0
  14. package/gui/dist/provider-icons/hermes-agent.svg +207 -0
  15. package/gui/dist/provider-icons/hyperbolic.svg +18 -0
  16. package/gui/dist/provider-icons/kilo.svg +13 -0
  17. package/gui/dist/provider-icons/litellm.svg +1 -0
  18. package/gui/dist/provider-icons/minimax.svg +1 -0
  19. package/gui/dist/provider-icons/nanogpt.svg +74 -0
  20. package/gui/dist/provider-icons/nebius.svg +1 -0
  21. package/gui/dist/provider-icons/neuralwatt.svg +27 -0
  22. package/gui/dist/provider-icons/nous.svg +149 -0
  23. package/gui/dist/provider-icons/novita.svg +32 -0
  24. package/gui/dist/provider-icons/oh-my-pi.svg +11 -0
  25. package/gui/dist/provider-icons/openclaw.svg +54 -0
  26. package/gui/dist/provider-icons/orcarouter.svg +175 -0
  27. package/gui/dist/provider-icons/parallel.svg +13 -0
  28. package/gui/dist/provider-icons/prime-agent.svg +21 -0
  29. package/gui/dist/provider-icons/sambanova.svg +276 -0
  30. package/gui/dist/provider-icons/scaleway.svg +11 -0
  31. package/gui/dist/provider-icons/siliconflow.svg +18 -0
  32. package/gui/dist/provider-icons/synthetic.svg +12 -0
  33. package/gui/dist/provider-icons/together.svg +18 -0
  34. package/gui/dist/provider-icons/umans.svg +30 -0
  35. package/gui/dist/provider-icons/venice.svg +165 -0
  36. package/gui/dist/provider-icons/vultr.svg +15 -0
  37. package/gui/dist/provider-icons/zai.svg +218 -0
  38. package/gui/dist/provider-icons/zcode.svg +219 -0
  39. package/gui/dist/provider-icons/zenmux.svg +1 -0
  40. package/package.json +1 -1
  41. package/src/adapters/cursor/live-models.ts +1 -0
  42. package/src/adapters/cursor/protobuf-request.ts +4 -1
  43. package/src/adapters/cursor/tool-definitions.ts +36 -4
  44. package/src/adapters/openai-responses.ts +72 -10
  45. package/src/bridge.ts +15 -15
  46. package/src/cli/capabilities.ts +14 -0
  47. package/src/cli/codex-cli-update.ts +96 -0
  48. package/src/cli/codex-shim-autorestore.ts +3 -0
  49. package/src/cli/dispatch.ts +54 -18
  50. package/src/cli/export-command.ts +18 -17
  51. package/src/cli/help.ts +2 -2
  52. package/src/cli/index.ts +423 -32
  53. package/src/cli/integrations.ts +24 -2
  54. package/src/cli/launcher-context.ts +53 -2
  55. package/src/cli/opencode.ts +126 -33
  56. package/src/cli/registry.ts +16 -10
  57. package/src/cli/system-command.ts +6 -1
  58. package/src/cli/uninstall-plan.ts +86 -0
  59. package/src/clients/config-export.ts +293 -28
  60. package/src/codex/account-store.ts +131 -12
  61. package/src/codex/auth-api.ts +202 -33
  62. package/src/codex/autostart-health.ts +3 -3
  63. package/src/codex/catalog/provider-fetch.ts +96 -10
  64. package/src/codex/catalog/sync.ts +4 -3
  65. package/src/codex/cli-install-provenance.ts +795 -0
  66. package/src/codex/convergence.ts +4 -3
  67. package/src/codex/credential-mutation-epoch.ts +11 -0
  68. package/src/codex/history-job.ts +10 -0
  69. package/src/codex/history-manifest.ts +35 -2
  70. package/src/codex/history-provider.ts +196 -19
  71. package/src/codex/history-worker.ts +3 -0
  72. package/src/codex/main-account.ts +2 -0
  73. package/src/codex/model-entitlements.ts +430 -27
  74. package/src/codex/native-profile-manager.ts +4 -0
  75. package/src/codex/quota-401-recovery.ts +190 -0
  76. package/src/codex/quota-recovery-timing.ts +28 -0
  77. package/src/codex/quota.ts +6 -0
  78. package/src/codex/reset-credit-operation-ledger.ts +1411 -0
  79. package/src/codex/reset-credit-recovery.ts +20 -2
  80. package/src/codex/routing.ts +68 -16
  81. package/src/codex/shim.ts +204 -18
  82. package/src/codex/subagent-model-fallback.ts +4 -1
  83. package/src/codex/user-identity.ts +2 -1
  84. package/src/config/paths.ts +18 -3
  85. package/src/config/pending-teardown-names.d.mts +8 -0
  86. package/src/config/pending-teardown-names.mjs +69 -0
  87. package/src/config/pending-teardown.ts +286 -0
  88. package/src/config.ts +23 -0
  89. package/src/generated/compatibility-version.json +155 -67
  90. package/src/integrations/journal.ts +12 -1
  91. package/src/integrations/registry.ts +112 -0
  92. package/src/integrations/state.ts +67 -5
  93. package/src/integrations/writer.ts +107 -16
  94. package/src/lib/bounded-subprocess.ts +36 -0
  95. package/src/lib/process-control.ts +30 -5
  96. package/src/lib/state-store-registrations.ts +8 -0
  97. package/src/lib/strict-semver.ts +47 -0
  98. package/src/lib/windows-elevation.ts +32 -1
  99. package/src/lib/windows-secret-acl.ts +47 -25
  100. package/src/lib/windows-service-mutation-lock.ts +133 -0
  101. package/src/lib/windows-user-principal.ts +15 -17
  102. package/src/oauth/index.ts +220 -13
  103. package/src/oauth/store.ts +220 -18
  104. package/src/responses/spill-store.ts +354 -29
  105. package/src/responses/state.ts +775 -8
  106. package/src/server/index.ts +4 -3
  107. package/src/server/lifecycle.ts +5 -1
  108. package/src/server/management/integration-routes.ts +36 -5
  109. package/src/server/management/model-rows.ts +11 -2
  110. package/src/server/management/provider-routes.ts +4 -0
  111. package/src/server/management/system-restart.ts +5 -5
  112. package/src/server/management-api.ts +72 -13
  113. package/src/server/responses/encrypted-payload.ts +18 -1
  114. package/src/server/startup-action-control.ts +3 -2
  115. package/src/server/stop-teardown.ts +84 -0
  116. package/src/service.ts +749 -53
  117. package/src/sidecar/candidates.ts +1 -1
  118. package/src/update/codex-cli-update-launch-policy.d.mts +18 -0
  119. package/src/update/codex-cli-update-launch-policy.mjs +30 -0
  120. package/src/update/index.ts +39 -6
  121. package/src/update/job.ts +10 -11
  122. package/src/update/proxy-liveness-probe.d.mts +6 -0
  123. package/src/update/proxy-liveness-probe.mjs +84 -0
  124. package/src/update/stop-contract.d.mts +2 -0
  125. package/src/update/stop-contract.mjs +15 -0
  126. package/src/update/stop-decision.d.mts +10 -0
  127. package/src/update/stop-decision.mjs +34 -0
  128. package/src/vision/eligibility.ts +19 -1
  129. package/src/vision/index.ts +4 -18
  130. package/gui/dist/assets/index-CowztZdo.js +0 -112
  131. package/gui/dist/assets/index-jqE_VOKI.css +0 -1
package/bin/ocx.mjs CHANGED
@@ -9,6 +9,9 @@
9
9
  * src/cli/index.ts — only the published npm `bin` routes through here.)
10
10
  */
11
11
  import { spawn, spawnSync } from "node:child_process";
12
+ import { STOP_HISTORY_INCOMPLETE_EXIT_CODE } from "../src/update/stop-contract.mjs";
13
+ import { probeProxyLiveness } from "../src/update/proxy-liveness-probe.mjs";
14
+ import { decidePostStopUpdate } from "../src/update/stop-decision.mjs";
12
15
  import { randomBytes } from "node:crypto";
13
16
  import { createRequire } from "node:module";
14
17
  import { existsSync, readFileSync, readdirSync } from "node:fs";
@@ -17,12 +20,17 @@ import { dirname, join, resolve } from "node:path";
17
20
  import { fileURLToPath } from "node:url";
18
21
  import { isRealBunBinary } from "../src/lib/bun-binary-validator.mjs";
19
22
  import { npmInvocation } from "../src/update/npm-invocation.mjs";
23
+ import { hasPendingTeardownIn } from "../src/config/pending-teardown-names.mjs";
20
24
  import {
21
25
  npmCachePreflightFailureMessage,
22
26
  runNpmCachePreflight,
23
27
  } from "../src/update/npm-cache-preflight.mjs";
24
28
  import { handoffWindowsTrayForUpdate, planWindowsTrayUpdate } from "../src/update/tray-update-plan.mjs";
25
29
  import { bootRestoreProbe, transactionalNpmUpdate } from "../src/update/transactional-install.mjs";
30
+ import {
31
+ CODEX_CLI_VERSION_MANAGER_ROOT_ENV_SLOTS,
32
+ isCodexCliUpdateInspectionArgv,
33
+ } from "../src/update/codex-cli-update-launch-policy.mjs";
26
34
 
27
35
  const PKG = "@bitkyc08/opencodex";
28
36
  const require = createRequire(import.meta.url);
@@ -155,10 +163,9 @@ function runNpmSelfUpdate() {
155
163
  process.platform === "win32" ? trayInstallState() : { installed: false, running: false },
156
164
  );
157
165
  /**
158
- * Refresh the existing service without re-registering it. `service repair` discovers
159
- * the installed backend itself and, on Windows scheduler installs, rewrites the wrapper
160
- * assets and restarts the existing task without `schtasks /create` — the elevation a
161
- * non-admin `ocx update` does not have.
166
+ * Refresh the existing service in place. `service repair` discovers the installed backend;
167
+ * healthy Windows scheduler registrations avoid `schtasks /create`, while stale definitions
168
+ * may be re-registered and require elevation.
162
169
  */
163
170
  function serviceRefreshArgs() {
164
171
  return [launcher, "service", "repair"];
@@ -199,7 +206,12 @@ function runNpmSelfUpdate() {
199
206
  // Capture listen target before stop clears runtime-port.json (mirrors GUI/CLI update worker).
200
207
  // Do not treat a live runtime port of 10100 as "missing" — track whether the read succeeded.
201
208
  let bakePort = 10100;
209
+ // The hostname travels with the port: a proxy bound to ::1 or a specific interface is
210
+ // invisible to a probe that assumes 127.0.0.1, and "no answer" would then read as
211
+ // "stopped" for exactly the proxy the probe exists to find.
212
+ let bakeHostname = "127.0.0.1";
202
213
  let sawRuntimePort = false;
214
+ let sawRuntimeHostname = false;
203
215
  try {
204
216
  const rt = JSON.parse(readFileSync(join(configDir(), "runtime-port.json"), "utf8"));
205
217
  if (Number.isFinite(rt?.port) && rt.port > 0 && rt.port <= 65535) {
@@ -216,16 +228,29 @@ function runNpmSelfUpdate() {
216
228
  }
217
229
  if (runtimeLive) {
218
230
  bakePort = Math.trunc(rt.port);
231
+ if (typeof rt?.hostname === "string" && rt.hostname.trim() !== "") {
232
+ bakeHostname = rt.hostname.trim();
233
+ sawRuntimeHostname = true;
234
+ }
219
235
  sawRuntimePort = true;
220
236
  }
221
237
  }
222
238
  } catch { /* fall through to config */ }
223
- if (!sawRuntimePort) {
239
+ // Port and hostname resolve INDEPENDENTLY: a legacy runtime record carries a port and no
240
+ // hostname, and skipping config in that case probed 127.0.0.1 for a proxy bound to ::1.
241
+ if (!sawRuntimePort || bakeHostname === "127.0.0.1") {
224
242
  try {
225
243
  const cfg = JSON.parse(readFileSync(join(configDir(), "config.json"), "utf8"));
226
- if (Number.isFinite(cfg?.port) && cfg.port > 0 && cfg.port <= 65535) bakePort = Math.trunc(cfg.port);
244
+ if (!sawRuntimePort && Number.isFinite(cfg?.port) && cfg.port > 0 && cfg.port <= 65535) {
245
+ bakePort = Math.trunc(cfg.port);
246
+ }
247
+ if (!sawRuntimeHostname && typeof cfg?.hostname === "string" && cfg.hostname.trim() !== "") {
248
+ bakeHostname = cfg.hostname.trim();
249
+ }
227
250
  } catch { /* keep default */ }
228
251
  }
252
+ // Wildcard and bracketed-IPv6 normalization lives in probeProxyLiveness, so both lanes
253
+ // get it from one place.
229
254
 
230
255
  const launcher = fileURLToPath(import.meta.url);
231
256
 
@@ -290,7 +315,8 @@ function runNpmSelfUpdate() {
290
315
  }
291
316
  }
292
317
  if (needDirectStart) {
293
- // A repair needs no elevation, but it can still fail or exit 0 while leaving
318
+ // Repair normally avoids elevation for a healthy registration, but a stale Windows
319
+ // scheduler definition can require it. It can also fail — or exit 0 while leaving
294
320
  // a non-viable manager. Fall back to a direct detached proxy start so the
295
321
  // update never leaves the user without a running proxy.
296
322
  console.warn(
@@ -339,17 +365,47 @@ function runNpmSelfUpdate() {
339
365
  }
340
366
  }
341
367
 
342
- if (serviceWasInstalled || hasRuntimeState) {
368
+ // An outstanding pending-teardown receipt is a fourth reason to run the stop. After a
369
+ // parent crashed mid-deferral the service, pid and runtime records can all be absent
370
+ // while the shared client config still points at a proxy that is gone; installing over
371
+ // that silently skips the recovery the receipt was written to trigger (#3008). Presence
372
+ // is the whole test here — the launcher cannot parse it, and `ocx stop` is what decides
373
+ // whether the obligation is safe to finish.
374
+ const hasPendingTeardown = hasPendingTeardownIn(readdirSync, configDir());
375
+ if (serviceWasInstalled || hasRuntimeState || hasPendingTeardown) {
343
376
  console.log("⏹ Stopping the running proxy before updating...");
344
377
  const stopRes = spawnSync(process.execPath, [launcher, "stop"], { stdio: "inherit", windowsHide: true });
345
378
  const stillHasRuntimeState =
346
379
  existsSync(join(configDir(), "ocx.pid")) || existsSync(join(configDir(), "runtime-port.json"));
347
- if (stopRes.status !== 0 || stillHasRuntimeState) {
380
+ // A history-only failure means teardown succeeded and a backup manifest is waiting for
381
+ // review: the proxy is down and replacing package files is safe. Every other nonzero
382
+ // status is a stop that did not finish, and a signal kill (status null) says nothing
383
+ // about whether it did - both abort, because replacing files under a live server
384
+ // leaves it running mixed old and new modules (#3008).
385
+ // The same decision the Bun updater makes, from the same module (#3008). Absent PID and
386
+ // runtime files are weak evidence, so the captured endpoint is asked; "unknown" aborts
387
+ // because a silent listener is exactly the state where replacing files is dangerous.
388
+ const decision = decidePostStopUpdate({
389
+ status: stopRes.status,
390
+ hasRuntimeState: stillHasRuntimeState,
391
+ // Re-checked AFTER the stop: a quarantined receipt lets the stop itself succeed
392
+ // (there is nothing left to stop), so a pre-stop check alone let the retry install
393
+ // over a teardown that never ran.
394
+ teardownOutstanding: hasPendingTeardownIn(readdirSync, configDir()),
395
+ liveness: probeProxyLiveness(bakePort, bakeHostname),
396
+ });
397
+ const historyOnlyStop = decision.reason === "history-only";
398
+ if (!decision.proceed) {
348
399
  if (trayBeforeUpdate.restoreOnFailure) runTrayLifecycle(launcher, "start");
349
- console.error("opencodex: could not stop the running proxy; aborting the update. Run 'ocx stop' and retry.");
400
+ if (decision.reason === "teardown-outstanding") {
401
+ console.error("opencodex: a shared teardown from an earlier stop is still outstanding and needs manual review; aborting the update.");
402
+ console.error("opencodex: confirm no proxy is running, run 'ocx restore', then remove the pending-teardown file in the opencodex home.");
403
+ } else console.error(decision.reason === "proxy-unknown"
404
+ ? `opencodex: could not confirm the proxy on ${bakeHostname}:${bakePort} is stopped; aborting the update. Run 'ocx stop' and retry.`
405
+ : "opencodex: could not stop the running proxy; aborting the update. Run 'ocx stop' and retry.");
350
406
  process.exit(1);
351
407
  }
352
- if (historyRestoreIncomplete()) {
408
+ if (historyOnlyStop || historyRestoreIncomplete()) {
353
409
  console.warn(
354
410
  "opencodex: WARNING — Codex resume-history metadata restore is incomplete (a backup manifest remains).\n" +
355
411
  " The DB may be busy or the manifest/target may need review; untracked routed history is intentionally unchanged.\n" +
@@ -468,7 +524,7 @@ function fail(msg) {
468
524
  process.exit(1);
469
525
  }
470
526
 
471
- function resolveBun() {
527
+ function resolveBun({ allowInstall = true } = {}) {
472
528
  // Keep direct npm-launcher starts aligned with durable service/shim installs:
473
529
  // a valid explicit runtime must win even when the bundled dependency exists.
474
530
  const override = process.env[BUN_OVERRIDE_ENV]?.trim();
@@ -493,7 +549,7 @@ function resolveBun() {
493
549
  // Lazy fallback: --ignore-scripts (or a failed postinstall) leaves the
494
550
  // ~450-byte placeholder stub. Run the bun package's own installer once.
495
551
  const installJs = join(bunDir, "install.js");
496
- if (existsSync(installJs)) {
552
+ if (allowInstall && existsSync(installJs)) {
497
553
  const r = spawnSync(process.execPath, [installJs], { stdio: "inherit" });
498
554
  if (r.status === 0) bin = findBunBinary(bunDir);
499
555
  }
@@ -512,6 +568,12 @@ if (updateHelpRequested) {
512
568
  process.exit(0);
513
569
  }
514
570
 
571
+ const codexCliUpdateInspection = isCodexCliUpdateInspectionArgv(process.argv);
572
+ if (codexCliUpdateInspection && typeof process.versions.bun === "string") {
573
+ console.error("opencodex: codex-cli-update inspection must use the published Node launcher.");
574
+ process.exit(1);
575
+ }
576
+
515
577
  if (process.argv[2] === "update" && isNodeModulesInstall() && !isBunGlobalInstall()) {
516
578
  runNpmSelfUpdate();
517
579
  }
@@ -519,7 +581,7 @@ if (process.argv[2] === "update" && isNodeModulesInstall() && !isBunGlobalInstal
519
581
  // #1849 boot probe: a prior update that lost power (or double-faulted) mid-swap leaves a
520
582
  // backup sibling and a broken live tree. Restore before anything tries to run from the
521
583
  // broken tree; reap stale backups once the live tree verifies healthy.
522
- if (isNodeModulesInstall() && !isBunGlobalInstall()) {
584
+ if (!codexCliUpdateInspection && isNodeModulesInstall() && !isBunGlobalInstall()) {
523
585
  try {
524
586
  const probe = bootRestoreProbe(resolve(here, ".."));
525
587
  if (probe.action === "restored") {
@@ -530,7 +592,7 @@ if (isNodeModulesInstall() && !isBunGlobalInstall()) {
530
592
  } catch { /* the probe must never block launch */ }
531
593
  }
532
594
 
533
- const bunRuntime = resolveBun();
595
+ const bunRuntime = resolveBun({ allowInstall: !codexCliUpdateInspection });
534
596
  const bun = bunRuntime.path;
535
597
 
536
598
  // Run the Bun child asynchronously and FORWARD termination signals to it, then wait
@@ -554,12 +616,61 @@ const bun = bunRuntime.path;
554
616
  // interpolation and provider settings legitimately read the project environment.
555
617
  const preBunAnthropicSlots = ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_BASE_URL"]
556
618
  .filter(name => typeof process.env[name] === "string" && process.env[name] !== "");
619
+ // A configured CODEX_CLI_PATH may legitimately be cwd-relative (`./tools/codex`), which the
620
+ // ordinary runtime resolver accepts. Inspection only trusts absolute local paths, so capture
621
+ // the absolute form here, in the launcher, while the original cwd is still authoritative;
622
+ // resolving it later would silently reinterpret it against a different working directory.
623
+ //
624
+ // A bare command with no separator (`codex`) is NOT a relative path: the runtime resolver
625
+ // deliberately hands those to executable lookup along PATH. Rewriting it to `<cwd>/codex`
626
+ // would make the inspector treat it as an explicit path and stop searching PATH entirely.
627
+ const configuredCodexCliPath = typeof process.env.CODEX_CLI_PATH === "string" && process.env.CODEX_CLI_PATH !== ""
628
+ ? process.env.CODEX_CLI_PATH
629
+ : null;
630
+ const preBunCodexCliPath = configuredCodexCliPath !== null
631
+ && (configuredCodexCliPath.includes("/") || configuredCodexCliPath.includes("\\") || /^[A-Za-z]:/.test(configuredCodexCliPath))
632
+ ? resolve(configuredCodexCliPath)
633
+ : configuredCodexCliPath;
634
+ const preBunPath = typeof process.env.PATH === "string" ? process.env.PATH : null;
635
+ const preBunPathExt = typeof process.env.PATHEXT === "string" ? process.env.PATHEXT : null;
636
+ const preBunCodexCliManagerRoots = Object.fromEntries(
637
+ CODEX_CLI_VERSION_MANAGER_ROOT_ENV_SLOTS.flatMap(name => {
638
+ const value = process.env[name];
639
+ return typeof value === "string" && value !== "" ? [[name, value]] : [];
640
+ }),
641
+ );
557
642
  const launchProof = randomBytes(32).toString("base64url");
558
643
  const launchContext = JSON.stringify({
559
644
  version: 1,
560
645
  proof: launchProof,
561
646
  anthropicEnvSlots: preBunAnthropicSlots,
647
+ codexCliInspectionEnv: codexCliUpdateInspection ? {
648
+ codexCliPath: preBunCodexCliPath,
649
+ path: preBunPath,
650
+ pathExt: preBunPathExt,
651
+ managerRoots: preBunCodexCliManagerRoots,
652
+ configDir: configDir(),
653
+ } : null,
562
654
  });
655
+ // The inspection snapshot above already carries PATH, PATHEXT, and the manager-root slots as
656
+ // proof-bound values, and `inspectCodexCliInstall` reads them from that snapshot rather than
657
+ // from the live environment. Inheriting them again would spend the 32,767-character Windows
658
+ // environment block twice, so a large-but-valid shell environment could stop the Bun child
659
+ // from spawning and fail the command before it reports anything. Drop the duplicates for the
660
+ // one-shot inspection launch only; every other launch inherits the environment unchanged.
661
+ // Windows environment names are case-insensitive, but this spread produces an ordinary
662
+ // case-sensitive object, and a real Windows environment commonly spells the variable `Path`.
663
+ // Deleting only the canonical upper-case spelling would silently leave that copy behind and
664
+ // reintroduce the duplication this block exists to prevent, so match on the lowercase form.
665
+ const inheritedEnv = { ...process.env };
666
+ if (codexCliUpdateInspection) {
667
+ const snapshotted = new Set(
668
+ ["PATH", "PATHEXT", ...CODEX_CLI_VERSION_MANAGER_ROOT_ENV_SLOTS].map(name => name.toLowerCase()),
669
+ );
670
+ for (const name of Object.keys(inheritedEnv)) {
671
+ if (snapshotted.has(name.toLowerCase())) delete inheritedEnv[name];
672
+ }
673
+ }
563
674
  const child = spawn(bun, [cliPath, `${NODE_LAUNCH_PROOF_PREFIX}${launchProof}`, ...process.argv.slice(2)], {
564
675
  stdio: "inherit",
565
676
  // A headless Windows parent (Task Scheduler, dashboard restart, shortcut) has no
@@ -567,7 +678,7 @@ const child = spawn(bun, [cliPath, `${NODE_LAUNCH_PROOF_PREFIX}${launchProof}`,
567
678
  // the long-running Bun child, and closing that window kills the proxy (#1236).
568
679
  windowsHide: true,
569
680
  env: {
570
- ...process.env,
681
+ ...inheritedEnv,
571
682
  [NODE_LAUNCH_CONTEXT_ENV]: launchContext,
572
683
  [BUN_RUNTIME_SOURCE_ENV]: bunRuntime.source,
573
684
  [BUN_RUNTIME_PATH_ENV]: bunRuntime.path,