@bitkyc08/opencodex 2.38.0 → 2.39.0

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 (81) hide show
  1. package/bin/ocx.mjs +58 -6
  2. package/gui/dist/assets/index-D-lchsPw.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/baseten.svg +13 -0
  6. package/gui/dist/provider-icons/bizrouter.svg +41 -0
  7. package/gui/dist/provider-icons/cerebras.svg +26 -0
  8. package/gui/dist/provider-icons/deepinfra.svg +75 -0
  9. package/gui/dist/provider-icons/digitalocean.svg +10 -0
  10. package/gui/dist/provider-icons/featherless.svg +4 -0
  11. package/gui/dist/provider-icons/gajae-code.svg +410 -0
  12. package/gui/dist/provider-icons/hermes-agent.svg +207 -0
  13. package/gui/dist/provider-icons/hyperbolic.svg +18 -0
  14. package/gui/dist/provider-icons/kilo.svg +13 -0
  15. package/gui/dist/provider-icons/litellm.svg +1 -0
  16. package/gui/dist/provider-icons/minimax.svg +1 -0
  17. package/gui/dist/provider-icons/nanogpt.svg +74 -0
  18. package/gui/dist/provider-icons/nebius.svg +1 -0
  19. package/gui/dist/provider-icons/neuralwatt.svg +27 -0
  20. package/gui/dist/provider-icons/nous.svg +149 -0
  21. package/gui/dist/provider-icons/novita.svg +32 -0
  22. package/gui/dist/provider-icons/orcarouter.svg +175 -0
  23. package/gui/dist/provider-icons/parallel.svg +13 -0
  24. package/gui/dist/provider-icons/sambanova.svg +276 -0
  25. package/gui/dist/provider-icons/scaleway.svg +11 -0
  26. package/gui/dist/provider-icons/siliconflow.svg +18 -0
  27. package/gui/dist/provider-icons/synthetic.svg +12 -0
  28. package/gui/dist/provider-icons/together.svg +18 -0
  29. package/gui/dist/provider-icons/umans.svg +30 -0
  30. package/gui/dist/provider-icons/venice.svg +165 -0
  31. package/gui/dist/provider-icons/vultr.svg +15 -0
  32. package/gui/dist/provider-icons/zai.svg +218 -0
  33. package/gui/dist/provider-icons/zenmux.svg +1 -0
  34. package/package.json +1 -1
  35. package/src/adapters/cursor/live-models.ts +1 -0
  36. package/src/adapters/openai-responses.ts +72 -10
  37. package/src/bridge.ts +15 -15
  38. package/src/cli/dispatch.ts +54 -18
  39. package/src/cli/index.ts +420 -30
  40. package/src/cli/integrations.ts +24 -2
  41. package/src/cli/uninstall-plan.ts +86 -0
  42. package/src/codex/account-store.ts +121 -8
  43. package/src/codex/auth-api.ts +202 -33
  44. package/src/codex/catalog/provider-fetch.ts +76 -9
  45. package/src/codex/history-job.ts +10 -0
  46. package/src/codex/history-manifest.ts +35 -2
  47. package/src/codex/history-provider.ts +196 -19
  48. package/src/codex/history-worker.ts +3 -0
  49. package/src/codex/quota-401-recovery.ts +190 -0
  50. package/src/codex/quota-recovery-timing.ts +28 -0
  51. package/src/codex/quota.ts +6 -0
  52. package/src/codex/routing.ts +68 -16
  53. package/src/codex/subagent-model-fallback.ts +4 -1
  54. package/src/config/pending-teardown-names.d.mts +8 -0
  55. package/src/config/pending-teardown-names.mjs +69 -0
  56. package/src/config/pending-teardown.ts +286 -0
  57. package/src/generated/compatibility-version.json +84 -32
  58. package/src/integrations/journal.ts +12 -1
  59. package/src/integrations/writer.ts +82 -7
  60. package/src/lib/process-control.ts +30 -5
  61. package/src/lib/state-store-registrations.ts +8 -0
  62. package/src/oauth/index.ts +220 -13
  63. package/src/oauth/store.ts +220 -18
  64. package/src/responses/spill-store.ts +20 -0
  65. package/src/responses/state.ts +288 -2
  66. package/src/server/management/integration-routes.ts +36 -5
  67. package/src/server/management-api.ts +66 -12
  68. package/src/server/responses/encrypted-payload.ts +18 -1
  69. package/src/server/stop-teardown.ts +84 -0
  70. package/src/service.ts +155 -20
  71. package/src/update/index.ts +36 -4
  72. package/src/update/proxy-liveness-probe.d.mts +6 -0
  73. package/src/update/proxy-liveness-probe.mjs +84 -0
  74. package/src/update/stop-contract.d.mts +2 -0
  75. package/src/update/stop-contract.mjs +15 -0
  76. package/src/update/stop-decision.d.mts +10 -0
  77. package/src/update/stop-decision.mjs +34 -0
  78. package/src/vision/eligibility.ts +19 -1
  79. package/src/vision/index.ts +4 -18
  80. package/gui/dist/assets/index-C14iCj_Q.js +0 -112
  81. package/gui/dist/assets/index-D7PIz7_g.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,6 +20,7 @@ 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,
@@ -202,7 +206,12 @@ function runNpmSelfUpdate() {
202
206
  // Capture listen target before stop clears runtime-port.json (mirrors GUI/CLI update worker).
203
207
  // Do not treat a live runtime port of 10100 as "missing" — track whether the read succeeded.
204
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";
205
213
  let sawRuntimePort = false;
214
+ let sawRuntimeHostname = false;
206
215
  try {
207
216
  const rt = JSON.parse(readFileSync(join(configDir(), "runtime-port.json"), "utf8"));
208
217
  if (Number.isFinite(rt?.port) && rt.port > 0 && rt.port <= 65535) {
@@ -219,16 +228,29 @@ function runNpmSelfUpdate() {
219
228
  }
220
229
  if (runtimeLive) {
221
230
  bakePort = Math.trunc(rt.port);
231
+ if (typeof rt?.hostname === "string" && rt.hostname.trim() !== "") {
232
+ bakeHostname = rt.hostname.trim();
233
+ sawRuntimeHostname = true;
234
+ }
222
235
  sawRuntimePort = true;
223
236
  }
224
237
  }
225
238
  } catch { /* fall through to config */ }
226
- 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") {
227
242
  try {
228
243
  const cfg = JSON.parse(readFileSync(join(configDir(), "config.json"), "utf8"));
229
- 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
+ }
230
250
  } catch { /* keep default */ }
231
251
  }
252
+ // Wildcard and bracketed-IPv6 normalization lives in probeProxyLiveness, so both lanes
253
+ // get it from one place.
232
254
 
233
255
  const launcher = fileURLToPath(import.meta.url);
234
256
 
@@ -343,17 +365,47 @@ function runNpmSelfUpdate() {
343
365
  }
344
366
  }
345
367
 
346
- 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) {
347
376
  console.log("⏹ Stopping the running proxy before updating...");
348
377
  const stopRes = spawnSync(process.execPath, [launcher, "stop"], { stdio: "inherit", windowsHide: true });
349
378
  const stillHasRuntimeState =
350
379
  existsSync(join(configDir(), "ocx.pid")) || existsSync(join(configDir(), "runtime-port.json"));
351
- 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) {
352
399
  if (trayBeforeUpdate.restoreOnFailure) runTrayLifecycle(launcher, "start");
353
- 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.");
354
406
  process.exit(1);
355
407
  }
356
- if (historyRestoreIncomplete()) {
408
+ if (historyOnlyStop || historyRestoreIncomplete()) {
357
409
  console.warn(
358
410
  "opencodex: WARNING — Codex resume-history metadata restore is incomplete (a backup manifest remains).\n" +
359
411
  " The DB may be busy or the manifest/target may need review; untracked routed history is intentionally unchanged.\n" +