@bitkyc08/opencodex 2.84.0 → 2.86.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 (246) hide show
  1. package/README.md +0 -4
  2. package/bin/ocx.mjs +61 -3
  3. package/gui/dist/assets/App-BjlttVLy.js +58 -0
  4. package/gui/dist/assets/{Tray-C9TGtwad.js → Tray-DyR9yBee.js} +1 -1
  5. package/gui/dist/assets/index-BMssP4k3.js +94 -0
  6. package/gui/dist/assets/quota-summary-D8M1bc_R.js +1 -0
  7. package/gui/dist/index.html +1 -1
  8. package/package.json +1 -2
  9. package/src/adapters/anthropic/beta-allowlist.ts +87 -8
  10. package/src/adapters/anthropic/client-identity.ts +1 -1
  11. package/src/adapters/anthropic/passthrough.ts +33 -4
  12. package/src/adapters/azure.ts +19 -1
  13. package/src/adapters/client-fingerprint.ts +24 -0
  14. package/src/adapters/coding-agent/turn.ts +22 -9
  15. package/src/adapters/cursor/arg-codec.ts +17 -5
  16. package/src/adapters/cursor/catalog.ts +8 -0
  17. package/src/adapters/cursor/effort-map.ts +3 -0
  18. package/src/adapters/cursor/freeform-args.ts +75 -0
  19. package/src/adapters/cursor/protobuf-events.ts +36 -23
  20. package/src/adapters/google-antigravity-wire.ts +22 -1
  21. package/src/adapters/google.ts +2 -2
  22. package/src/adapters/openai-chat/response-events.ts +59 -1
  23. package/src/adapters/openai-chat.ts +8 -5
  24. package/src/adapters/qoder/adapter.ts +40 -1
  25. package/src/advisor/consult.ts +220 -0
  26. package/src/advisor/context.ts +226 -0
  27. package/src/advisor/disclosure.ts +20 -0
  28. package/src/advisor/runtime.ts +305 -0
  29. package/src/advisor/settings.ts +135 -0
  30. package/src/advisor/state.ts +369 -0
  31. package/src/advisor/synthetic-tool.ts +33 -0
  32. package/src/bridge/errors.ts +5 -0
  33. package/src/chat/inbound.ts +30 -6
  34. package/src/chat/outbound.ts +5 -0
  35. package/src/claude/desktop-3p-library.ts +43 -3
  36. package/src/claude/desktop-3p.ts +22 -8
  37. package/src/claude/desktop-picker-profile.ts +11 -4
  38. package/src/claude/desktop-profile.ts +10 -1
  39. package/src/claude/desktop-remote-store-io.ts +5 -3
  40. package/src/claude/inbound.ts +121 -50
  41. package/src/claude/intercept/local-ca.ts +8 -1
  42. package/src/claude/model-default-settings.ts +154 -0
  43. package/src/claude/model-info.ts +7 -7
  44. package/src/claude/outbound.ts +13 -6
  45. package/src/cli/advisor.ts +151 -0
  46. package/src/cli/auxiliary-listener-preflight.ts +37 -0
  47. package/src/cli/capabilities-agents-routing.ts +18 -0
  48. package/src/cli/capabilities-base.ts +1 -1
  49. package/src/cli/chatgpt-command.ts +35 -14
  50. package/src/cli/claude.ts +46 -6
  51. package/src/cli/dispatch.ts +5 -1
  52. package/src/cli/doctor.ts +9 -0
  53. package/src/cli/ensure-desktop-cold-start.ts +329 -0
  54. package/src/cli/export-command.ts +1 -1
  55. package/src/cli/help.ts +2 -1
  56. package/src/cli/index.ts +13 -4
  57. package/src/cli/integration-plan-dto.ts +2 -0
  58. package/src/cli/registry.ts +15 -3
  59. package/src/cli/restart-scope.ts +3 -1
  60. package/src/clients/config-export/contracts.ts +1 -0
  61. package/src/clients/config-export/model-metadata.ts +3 -2
  62. package/src/clients/config-export/qoder.ts +93 -0
  63. package/src/clients/config-export.ts +17 -4
  64. package/src/codex/auth-api/pool-mode-gate.ts +5 -2
  65. package/src/codex/auth-context.ts +79 -5
  66. package/src/codex/autostart-health.ts +4 -0
  67. package/src/codex/catalog/build-entries.ts +11 -2
  68. package/src/codex/catalog/configured-tiers.ts +50 -0
  69. package/src/codex/catalog/effort.ts +4 -1
  70. package/src/codex/catalog/model-hints.ts +21 -3
  71. package/src/codex/catalog/native-ultrafast.ts +106 -0
  72. package/src/codex/catalog/parsing.ts +5 -0
  73. package/src/codex/catalog/provider-models.ts +4 -0
  74. package/src/codex/catalog/retained-sync.ts +2 -0
  75. package/src/codex/catalog/routed-gather.ts +22 -4
  76. package/src/codex/catalog/vendor-context.ts +82 -0
  77. package/src/codex/config-write-lock.ts +40 -2
  78. package/src/codex/convergence.ts +2 -0
  79. package/src/codex/desktop-app/types.ts +2 -0
  80. package/src/codex/desktop-app/windows.ts +29 -16
  81. package/src/codex/desktop-app-restart.ts +4 -2
  82. package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
  83. package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
  84. package/src/codex/desktop-compatibility/connection-store.ts +87 -0
  85. package/src/codex/desktop-compatibility/installed-build.ts +55 -0
  86. package/src/codex/desktop-compatibility/json-body.ts +29 -0
  87. package/src/codex/desktop-compatibility/native-identity.ts +62 -0
  88. package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
  89. package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
  90. package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
  91. package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
  92. package/src/codex/desktop-compatibility/runtime.ts +173 -0
  93. package/src/codex/desktop-compatibility/service.ts +30 -0
  94. package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
  95. package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
  96. package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
  97. package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
  98. package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
  99. package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
  100. package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
  101. package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
  102. package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
  103. package/src/codex/desktop-compatibility/windows-key-protection.ts +59 -0
  104. package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
  105. package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
  106. package/src/codex/history-provider.ts +1 -1
  107. package/src/codex/inject/config-write-section.ts +4 -1
  108. package/src/codex/inject/remove.ts +18 -6
  109. package/src/codex/journal.ts +16 -1
  110. package/src/codex/main-account.ts +99 -15
  111. package/src/codex/native-main-refresh-mode.ts +217 -0
  112. package/src/codex/routing/health-store.ts +1 -1
  113. package/src/codex/routing/pool-recovery.ts +79 -0
  114. package/src/codex/shim-diagnostics.ts +6 -0
  115. package/src/codex/shim-overlay.ts +7 -2
  116. package/src/config/diagnostics.ts +2 -1
  117. package/src/config/live-reconcile.ts +13 -0
  118. package/src/config/load-degrade.ts +6 -1
  119. package/src/config/paths.ts +10 -3
  120. package/src/config/schema/config-schema.ts +17 -0
  121. package/src/config/schema/desktop-compatibility.ts +9 -0
  122. package/src/config/schema/leaf-validators.ts +1 -0
  123. package/src/generated/compatibility-version.json +451 -163
  124. package/src/generated/model-metadata.ts +1 -1
  125. package/src/integrations/catalog-refresh.ts +1 -1
  126. package/src/integrations/config-io.ts +20 -2
  127. package/src/integrations/journal.ts +15 -1
  128. package/src/integrations/mutation-plan.ts +1 -0
  129. package/src/integrations/registry.ts +7 -0
  130. package/src/integrations/store.ts +3 -0
  131. package/src/integrations/writer.ts +10 -1
  132. package/src/lib/advisor-activation.ts +16 -0
  133. package/src/lib/background-work.ts +65 -0
  134. package/src/lib/bounded-file-read.ts +58 -0
  135. package/src/lib/bun-path-runtime.d.mts +10 -1
  136. package/src/lib/bun-path-runtime.mjs +62 -3
  137. package/src/lib/desktop-proxy-route.ts +30 -0
  138. package/src/lib/desktop-upstream-tunnel.ts +107 -0
  139. package/src/lib/errors.ts +7 -0
  140. package/src/lib/local-internal-call-capability.ts +55 -0
  141. package/src/lib/package-tree-integrity.ts +2 -2
  142. package/src/lib/socks5-fetch.ts +48 -71
  143. package/src/lib/socks5-handshake.ts +112 -0
  144. package/src/lib/translator-budget.ts +7 -1
  145. package/src/oauth/generic-account-failover.ts +29 -5
  146. package/src/oauth/index.ts +4 -2
  147. package/src/oauth/kiro-device-login.ts +2 -1
  148. package/src/oauth/kiro.ts +7 -1
  149. package/src/protocols/codecs/chat-ir.ts +467 -0
  150. package/src/protocols/codecs/chat.ts +10 -1
  151. package/src/protocols/codecs/messages-ir.ts +500 -0
  152. package/src/protocols/features.ts +10 -2
  153. package/src/protocols/path.ts +11 -1
  154. package/src/protocols/plan-snapshot.ts +39 -16
  155. package/src/protocols/plan.ts +9 -1
  156. package/src/protocols/settings.ts +8 -0
  157. package/src/providers/antigravity-account-roster.ts +102 -0
  158. package/src/providers/antigravity-models.ts +85 -7
  159. package/src/providers/model-rename-fields.ts +2 -0
  160. package/src/providers/model-rename-migration.ts +5 -3
  161. package/src/providers/registry/entries-core.ts +7 -6
  162. package/src/providers/registry/entries-extended.ts +19 -0
  163. package/src/responses/parser.ts +30 -19
  164. package/src/responses/state.ts +32 -29
  165. package/src/routing/history/build-file.ts +92 -0
  166. package/src/routing/history/indexer.ts +248 -216
  167. package/src/routing/history/ingest.ts +238 -0
  168. package/src/routing/history/rebuild-job.ts +111 -0
  169. package/src/routing/history/rebuild-protocol.ts +15 -0
  170. package/src/routing/history/rebuild-worker.ts +34 -0
  171. package/src/server/admission-model-scope.ts +4 -1
  172. package/src/server/antigravity-direct-eligibility.ts +208 -0
  173. package/src/server/auth-cors.ts +4 -0
  174. package/src/server/chat-completions.ts +75 -9
  175. package/src/server/claude-messages.ts +110 -20
  176. package/src/server/index/desktop-compatibility-startup.ts +56 -0
  177. package/src/server/index/serve-options.ts +2 -2
  178. package/src/server/index/startup-warnings.ts +9 -0
  179. package/src/server/index/websocket-handler.ts +9 -0
  180. package/src/server/index.ts +13 -10
  181. package/src/server/inference/client-encoder-delivery.ts +6 -5
  182. package/src/server/login-shell-path.ts +146 -0
  183. package/src/server/management/advisor-routes.ts +191 -0
  184. package/src/server/management/agent-settings-routes.ts +22 -4
  185. package/src/server/management/companion-routes.ts +4 -0
  186. package/src/server/management/config-routes.ts +2 -2
  187. package/src/server/management/context.ts +4 -0
  188. package/src/server/management/desktop-compatibility-routes.ts +46 -0
  189. package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
  190. package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
  191. package/src/server/management/model-routes.ts +2 -2
  192. package/src/server/management/protocol-settings-patch.ts +1 -0
  193. package/src/server/management/route-registry.ts +11 -0
  194. package/src/server/management/sibling-guard.ts +1 -0
  195. package/src/server/management-api.ts +6 -0
  196. package/src/server/messages-antigravity-direct.ts +195 -0
  197. package/src/server/messages-native.ts +5 -1
  198. package/src/server/request-log.ts +7 -0
  199. package/src/server/responses/adapter-continuation.ts +35 -18
  200. package/src/server/responses/adapter-delivery.ts +24 -2
  201. package/src/server/responses/adapter-dispatch.ts +29 -2
  202. package/src/server/responses/advisor-plan-slot.ts +27 -0
  203. package/src/server/responses/advisor-slot.ts +336 -0
  204. package/src/server/responses/code-mode-collab-surface.ts +116 -8
  205. package/src/server/responses/core-combo-failure.ts +10 -1
  206. package/src/server/responses/core-combo.ts +21 -0
  207. package/src/server/responses/core-lifetime.ts +2 -0
  208. package/src/server/responses/core-normalize.ts +11 -0
  209. package/src/server/responses/core-options.ts +46 -0
  210. package/src/server/responses/core-replay.ts +7 -4
  211. package/src/server/responses/core.ts +1 -1
  212. package/src/server/responses/passthrough-delivery.ts +16 -10
  213. package/src/server/responses/passthrough-dispatch.ts +14 -3
  214. package/src/server/responses/request-prepare.ts +68 -5
  215. package/src/server/responses/request-transport.ts +16 -0
  216. package/src/server/responses/run-turn-execution.ts +70 -8
  217. package/src/server/responses/sidecar-execution.ts +78 -3
  218. package/src/server/responses/terminal-guard.ts +2 -1
  219. package/src/server/responses-undeclared-tool-guard.ts +30 -9
  220. package/src/server/startup-action-control.ts +16 -2
  221. package/src/server/startup-health-cache.ts +1 -0
  222. package/src/service/desktop-startup.ts +14 -0
  223. package/src/service/health-sentinel-main.ts +23 -4
  224. package/src/service/health-sentinel-probe.ts +24 -7
  225. package/src/service/logging.ts +72 -0
  226. package/src/tray/windows-tray.ps1 +43 -24
  227. package/src/types/config.ts +63 -6
  228. package/src/types/provider.ts +7 -0
  229. package/src/types/request.ts +15 -0
  230. package/src/types/tools.ts +2 -0
  231. package/src/update/async-check.ts +1 -1
  232. package/src/update/badge.ts +2 -2
  233. package/src/update/index.ts +88 -15
  234. package/src/update/job-supervised-restart.ts +107 -0
  235. package/src/update/job.ts +32 -32
  236. package/src/update/notify.ts +1 -1
  237. package/src/update/refresh-scheduler.ts +2 -2
  238. package/src/update/supervised-recovery.d.mts +27 -0
  239. package/src/update/supervised-recovery.mjs +157 -0
  240. package/src/usage/cache-diagnostic.ts +2 -0
  241. package/src/usage/expected-prices.ts +12 -0
  242. package/src/web-search/gemini-executor.ts +2 -2
  243. package/assets/sponsors/packycode.png +0 -0
  244. package/gui/dist/assets/App-CZtqiGcw.js +0 -56
  245. package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
  246. package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
package/src/update/job.ts CHANGED
@@ -2,18 +2,8 @@ import { spawn, spawnSync, type ChildProcess } from "node:child_process";
2
2
  import { existsSync, mkdirSync, openSync, readFileSync, writeSync } from "node:fs";
3
3
  import { dirname, join, sep } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import {
6
- atomicWriteFile,
7
- getConfigDir,
8
- loadConfig,
9
- } from "../config";
10
- import {
11
- readPid,
12
- readRuntimePort,
13
- removePid,
14
- removeRuntimePort,
15
- verifyPidIdentity,
16
- } from "../config/process-state";
5
+ import { atomicWriteFile, getConfigDir, loadConfig } from "../config";
6
+ import { readPid, readRuntimePort, removePid, removeRuntimePort, verifyPidIdentity } from "../config/process-state";
17
7
  import { isProcessAlive, killProxy } from "../lib/process-control";
18
8
  import { selfLaunchArgv } from "../lib/self-launch-argv";
19
9
  import { killWindowsSchedulerWrappers } from "../lib/windows-service-wrappers";
@@ -33,9 +23,10 @@ import {
33
23
  } from "../server/proxy-liveness";
34
24
  import { isServiceInstalled, isServiceViable, readServiceBackend, stopWindows } from "../service";
35
25
  import { runUpdateRestartWithOwnershipLease, type ServiceOwnershipResolution } from "./restart-ownership";
26
+ import { restartServiceManagerAfterUpdate, unsettledManagerRestartFailure, unsupervisedRestartFailure, type ManagerRestartFn } from "./job-supervised-restart";
36
27
  import {
37
28
  type Channel, type Installer,
38
- PKG,
29
+ PKG, STANDALONE_UPDATE_HINT,
39
30
  checkUpdatePackageIntegrity,
40
31
  currentVersion,
41
32
  defaultUpdateTag,
@@ -492,17 +483,17 @@ export function checkForUpdate(
492
483
  const ownership = deps.detectInstallOwnership?.();
493
484
  const installer = ownership?.installer ?? deps.detectInstall();
494
485
  const channel = requestedChannel ?? normalizeUpdateChannel(null, current);
495
- const latest = installer === "source" ? null : deps.latestVersion(channel);
486
+ const latest = installer === "source" || installer === "standalone" ? null : deps.latestVersion(channel);
496
487
  const updateAvailable = !!latest && isNewer(latest, current, channel);
497
488
  let reason: string | undefined;
498
- let command = installer === "source"
489
+ let command = installer === "standalone" ? STANDALONE_UPDATE_HINT : installer === "source"
499
490
  ? manualSourceCommand()
500
491
  : installer === "mise"
501
492
  ? (ownership && deps.miseUpdateCommand?.(ownership)) ?? ""
502
493
  : updateExecutionCommand(installer, channel).display;
503
494
 
504
- if (installer === "source") {
505
- reason = "source_checkout";
495
+ if (installer === "source" || installer === "standalone") {
496
+ reason = installer === "source" ? "source_checkout" : "standalone_install";
506
497
  } else if (installer === "mise") {
507
498
  reason = command ? "externally_managed" : "external_ownership_invalid";
508
499
  } else if (!latest) {
@@ -517,7 +508,7 @@ export function checkForUpdate(
517
508
  channel,
518
509
  installer,
519
510
  updateAvailable,
520
- canUpdate: installer !== "source" && installer !== "mise" && updateAvailable,
511
+ canUpdate: installer !== "source" && installer !== "standalone" && installer !== "mise" && updateAvailable,
521
512
  command,
522
513
  releaseNotesUrl: RELEASE_NOTES_URL,
523
514
  ...(reason ? { reason } : {}),
@@ -1005,6 +996,7 @@ export interface RestartIo {
1005
996
  releaseForServiceManagerFn?: () => void;
1006
997
  /** Re-acquire the lease for the direct-start fallthrough, then re-run the veto; a notice stops the restart. */
1007
998
  reacquireForDirectStartFn?: () => { readonly notice: string; readonly failed: boolean } | null;
999
+ restartServiceManagerFn?: ManagerRestartFn; // launchd/systemd restart before a managed direct fallback (#7043)
1008
1000
  probeProxy?: (port: number, hostname?: string) => Promise<boolean>;
1009
1001
  /** Richer /healthz read for update-correlated restart evidence (pid + version). */
1010
1002
  probeProxyIdentity?: (port: number, hostname?: string) => Promise<RestartProxyIdentity | null>;
@@ -1035,7 +1027,7 @@ export interface RestartIo {
1035
1027
  job: UpdateJobState,
1036
1028
  captured?: { port: number; hostname: string; oldPid?: number },
1037
1029
  io?: RestartIo,
1038
- ) => Promise<false | void>;
1030
+ ) => Promise<false | void | "unsupervised">;
1039
1031
  /**
1040
1032
  * PIDs currently LISTENing on the captured port. Used to widen the post-update
1041
1033
  * kill allowlist beyond the pre-update PID (Windows often leaves a respawned
@@ -1098,7 +1090,7 @@ async function restartAfterUpdate(
1098
1090
  job: UpdateJobState,
1099
1091
  captured?: { port: number; hostname: string; oldPid?: number },
1100
1092
  io: RestartIo = {},
1101
- ): Promise<false | void> {
1093
+ ): Promise<false | void | "unsupervised"> {
1102
1094
  const serviceInstalled = (io.serviceInstalledFn ?? isServiceInstalled)();
1103
1095
  const config = loadConfig();
1104
1096
  // Stop-first update already cleared pid/runtime state, so the pre-update capture is
@@ -1250,6 +1242,12 @@ async function restartAfterUpdate(
1250
1242
  }
1251
1243
  }
1252
1244
 
1245
+ // #7043: a managed install asks launchd/systemd first; only the manager's own job counts as recovered.
1246
+ const managed = !serviceInstalled ? "failed" : await (io.restartServiceManagerFn ?? restartServiceManagerAfterUpdate)(
1247
+ { port, hostname, release: io.releaseForServiceManagerFn }, line => updateJob(job, {}, line));
1248
+ if (managed === "supervised") return;
1249
+ if (managed === "unsettled") { updateJob(job, ...unsettledManagerRestartFailure(port)); return false; }
1250
+ const fallback = serviceInstalled ? "unsupervised" as const : undefined; // every exit below is unproven ownership
1253
1251
  // The service refresh ran outside the lease; take it back so the veto and the kills/start below stay serialized.
1254
1252
  const restartVeto = io.reacquireForDirectStartFn?.();
1255
1253
  if (restartVeto) {
@@ -1282,7 +1280,7 @@ async function restartAfterUpdate(
1282
1280
  );
1283
1281
  if (liveHolders.length > 0) {
1284
1282
  updateJob(job, {}, `Live holder(s) remain on port ${port}; not starting on another port. Retry 'ocx start --port ${port}'.`);
1285
- return;
1283
+ return fallback;
1286
1284
  }
1287
1285
  // Dead PIDs can still own LISTEN rows. SetTcpEntry needs elevation (rc 317 on a
1288
1286
  // normal update worker), so poll until netstat is empty and the start runtime can bind.
@@ -1306,7 +1304,7 @@ async function restartAfterUpdate(
1306
1304
  + `${formatPortHolders(port, listPids, verifyOcx, directAllow)} `
1307
1305
  + `Retry 'ocx start --port ${port}'.`,
1308
1306
  );
1309
- return;
1307
+ return fallback;
1310
1308
  }
1311
1309
  }
1312
1310
  }
@@ -1314,7 +1312,7 @@ async function restartAfterUpdate(
1314
1312
  // retries on missing /healthz after prepare + ghost-LISTEN clear.
1315
1313
  if (io.spawnStart) {
1316
1314
  io.spawnStart(job, job.installer, port, launcher);
1317
- return;
1315
+ return fallback;
1318
1316
  }
1319
1317
  const sleep = io.sleepMs ?? ((ms: number) => new Promise<void>(r => setTimeout(r, ms)));
1320
1318
  const probe = io.probeProxy ?? (async (p: number, host?: string) => (
@@ -1334,7 +1332,7 @@ async function restartAfterUpdate(
1334
1332
  {},
1335
1333
  `Proxy already healthy on ${hostname}:${port} at ${expectedVersion}; skipping pinned start.`,
1336
1334
  );
1337
- return;
1335
+ return fallback;
1338
1336
  }
1339
1337
  }
1340
1338
  const attempts = 3;
@@ -1393,12 +1391,13 @@ async function restartAfterUpdate(
1393
1391
  child.once("close", retireChild);
1394
1392
  const healthDeadline = now() + perAttemptHealthMs;
1395
1393
  while (now() < healthDeadline) {
1396
- if (await probe(port, hostname)) return;
1394
+ if (await probe(port, hostname)) return fallback;
1397
1395
  await sleep(500);
1398
1396
  }
1399
1397
  }
1400
1398
  // Exhausted retries: do not leave a hung pinned-start child owning the port.
1401
1399
  killSpawnAttempt(lastChild);
1400
+ return fallback;
1402
1401
  }
1403
1402
 
1404
1403
  /** Compact listen-holder summary for update-job logs when reclaim fails. */
@@ -1454,7 +1453,7 @@ export function restartAfterUpdateForTests(
1454
1453
  job: UpdateJobState,
1455
1454
  captured: { port: number; hostname: string; oldPid?: number },
1456
1455
  io: RestartIo,
1457
- ): Promise<false | void> {
1456
+ ): Promise<false | void | "unsupervised"> {
1458
1457
  return restartAfterUpdate(job, captured, io);
1459
1458
  }
1460
1459
 
@@ -1729,12 +1728,13 @@ export async function finishGuiUpdateRestart(
1729
1728
  }
1730
1729
  }
1731
1730
  const restartFn = io.restartAfterUpdateFn ?? restartAfterUpdate;
1732
- if ((await restartFn(job, captured, io)) === false) return false;
1733
- if (!usesNodeLauncher(installer)) {
1734
- // Bun/source: health alone remains enough unless a richer identity probe is supplied.
1735
- if (!io.probeProxyIdentity) return confirmRestartedProxy(job, captured, io);
1736
- }
1737
- return confirmPackageManagerExplicitRestart(job, captured, io);
1731
+ const restarted = await restartFn(job, captured, io);
1732
+ if (restarted === false) return false;
1733
+ // Bun/source: health alone remains enough unless a richer identity probe is supplied.
1734
+ const confirm = !usesNodeLauncher(installer) && !io.probeProxyIdentity ? confirmRestartedProxy : confirmPackageManagerExplicitRestart;
1735
+ const confirmed = await confirm(job, captured, io);
1736
+ if (confirmed && restarted === "unsupervised") updateJob(job, ...unsupervisedRestartFailure(captured.port)); // #7043
1737
+ return confirmed && restarted !== "unsupervised";
1738
1738
  }
1739
1739
 
1740
1740
  /**
@@ -152,7 +152,7 @@ export function interactiveGuardOk(): boolean {
152
152
  */
153
153
  export function shouldConsider(): { channel: Channel; current: string } | null {
154
154
  const installer = detectInstall();
155
- if (installer === "source" || installer === "mise") return null;
155
+ if (installer === "source" || installer === "standalone" || installer === "mise") return null;
156
156
  const current = currentVersion();
157
157
  if (current === "?" || isSourceBuildVersion(current)) return null;
158
158
  if (!interactiveGuardOk()) return null;
@@ -53,7 +53,7 @@ export function createRefreshScheduler(deps: RefreshDeps = defaults) {
53
53
  const version = deps.current();
54
54
  const installer = deps.install();
55
55
  return !deps.disabled() && version !== "?" && !isSourceBuildVersion(version)
56
- && installer !== "source" && installer !== "mise";
56
+ && installer !== "source" && installer !== "standalone" && installer !== "mise";
57
57
  };
58
58
  const channel = () => defaultUpdateTag(deps.current());
59
59
  const stale = (tag: Channel) => {
@@ -123,7 +123,7 @@ export function createRefreshScheduler(deps: RefreshDeps = defaults) {
123
123
  },
124
124
  async check(tag: Channel): Promise<UpdateCheckResult> {
125
125
  const installer = deps.install();
126
- const latest = installer === "source" || installer === "mise" ? null : await lookup(tag, false);
126
+ const latest = installer === "source" || installer === "standalone" || installer === "mise" ? null : await lookup(tag, false);
127
127
  return checkForUpdate(tag, {
128
128
  currentVersion: deps.current,
129
129
  detectInstall: deps.install,
@@ -0,0 +1,27 @@
1
+ /** Declarations for the plain-ESM manager recovery shared with `bin/ocx.mjs` (#7043). */
2
+ export type ServiceManagerRun = (file: string, args: string[]) => { ok: boolean; stdout: string; stderr: string; timedOut?: boolean };
3
+ export interface ServiceManagerDeps {
4
+ platform?: NodeJS.Platform;
5
+ uid?: number;
6
+ run?: ServiceManagerRun;
7
+ }
8
+ export type SupervisionVerdict =
9
+ | { kind: "supervised"; managerPid: number; servingPid: number }
10
+ | { kind: "failed"; reason: string };
11
+ export declare const LAUNCHD_SERVICE_LABEL: string;
12
+ export declare const SYSTEMD_SERVICE_UNIT: string;
13
+ export declare function parseLaunchdJobPid(printOutput: string): number | null;
14
+ export declare function parseSystemdMainPid(showOutput: string): number | null;
15
+ export declare function hasRestartableServiceManager(deps?: ServiceManagerDeps): boolean;
16
+ export declare function restartServiceManagerJob(deps?: ServiceManagerDeps):
17
+ { ok: true } | { ok: false; reason: string; unsettled?: true };
18
+ export declare function freshServingPid(recordPid: number, staleRecordPid: number | null): number | null;
19
+ export declare function unsettledManagerWarning(port: number): string;
20
+ export declare function readServiceManagerPid(deps?: ServiceManagerDeps): number | null;
21
+ export declare function readParentPid(pid: number, deps?: ServiceManagerDeps): number | null;
22
+ export declare function supervisionVerdict(input: {
23
+ servingPid: number | null;
24
+ managerPid: number | null;
25
+ parentPid: (pid: number) => number | null;
26
+ }): SupervisionVerdict;
27
+ export declare function unsupervisedRuntimeWarning(port: number): string;
@@ -0,0 +1,157 @@
1
+ import { spawnSync } from "node:child_process";
2
+
3
+ /**
4
+ * Manager-mediated runtime recovery after an update (#7043).
5
+ *
6
+ * When the service refresh after an update fails or leaves a non-viable manager, both update
7
+ * lanes used to fall straight back to a detached `ocx start`. That proxy answers, but launchd
8
+ * or systemd no longer owns it: a crash is not restarted, and the next `launchctl kickstart`
9
+ * races the detached process for the port. Before that fallback, the lanes now ask the manager
10
+ * to restart the job it already holds, and count it as recovered only when the serving proxy is
11
+ * provably the manager's process: the runtime record's PID is the manager's main PID, or that
12
+ * PID's direct child (the job execs the Node launcher, which runs the Bun proxy as its child).
13
+ *
14
+ * Plain synchronous ESM because `bin/ocx.mjs` runs under Node, cannot import TypeScript, and
15
+ * keeps straight-line control flow; `src/update/index.ts` composes the same pieces with its own
16
+ * asynchronous identity wait.
17
+ */
18
+
19
+ /** Must match `LABEL` / `TASK` in src/service/state.ts; a test pins the pairing. */
20
+ export const LAUNCHD_SERVICE_LABEL = "com.opencodex.proxy";
21
+ export const SYSTEMD_SERVICE_UNIT = "opencodex-proxy";
22
+
23
+ function positivePid(value) {
24
+ if (typeof value !== "string" || !/^\d+$/.test(value.trim())) return null;
25
+ const pid = Number(value.trim());
26
+ return Number.isSafeInteger(pid) && pid > 0 ? pid : null;
27
+ }
28
+
29
+ function defaultRun(file, args) {
30
+ // An armed test process must never reach the developer's real launchd or systemd job;
31
+ // same refusal as `assertLiveServiceManagerAllowed` for the TypeScript service layer.
32
+ if (process.env.OCX_TEST_HOME_GUARD === "1") {
33
+ return { ok: false, stdout: "", stderr: `refusing to run ${file} from an armed test process` };
34
+ }
35
+ // A bare SSH session has no user-bus address; systemd's user manager still lives at the
36
+ // conventional runtime directory (same fallback as the service layer's ensureUserBusEnv).
37
+ const env = file === "systemctl" && !process.env.XDG_RUNTIME_DIR && typeof process.getuid === "function"
38
+ ? { ...process.env, XDG_RUNTIME_DIR: `/run/user/${process.getuid()}` }
39
+ : process.env;
40
+ const result = spawnSync(file, args, { encoding: "utf8", timeout: 20_000, windowsHide: true, env });
41
+ if (result.error) {
42
+ // A timeout kills only the waiting client; the manager's queued job keeps running.
43
+ const timedOut = result.error.code === "ETIMEDOUT";
44
+ return { ok: false, stdout: "", stderr: String(result.error.message ?? ""), ...(timedOut ? { timedOut } : {}) };
45
+ }
46
+ return { ok: result.status === 0, stdout: String(result.stdout ?? ""), stderr: String(result.stderr ?? "").trim() };
47
+ }
48
+
49
+ function launchdTarget(uid) {
50
+ return `gui/${uid}/${LAUNCHD_SERVICE_LABEL}`;
51
+ }
52
+
53
+ /** `launchctl print` reports a running job as a `pid = <n>` line. */
54
+ export function parseLaunchdJobPid(printOutput) {
55
+ const match = /^\s*pid\s*=\s*(\d+)\s*$/m.exec(String(printOutput ?? ""));
56
+ return positivePid(match?.[1]);
57
+ }
58
+
59
+ /** `systemctl show -p MainPID` prints `MainPID=<n>`; 0 means not running. */
60
+ export function parseSystemdMainPid(showOutput) {
61
+ const match = /^MainPID=(\d+)\s*$/m.exec(String(showOutput ?? ""));
62
+ return positivePid(match?.[1]);
63
+ }
64
+
65
+ function resolve(deps) {
66
+ const platform = deps.platform ?? process.platform;
67
+ const uid = deps.uid ?? (typeof process.getuid === "function" ? process.getuid() : 0);
68
+ return { platform, uid, run: deps.run ?? defaultRun };
69
+ }
70
+
71
+ /** Whether this platform has a manager this module can ask (launchd or systemd). */
72
+ export function hasRestartableServiceManager(deps = {}) {
73
+ const { platform } = resolve(deps);
74
+ return platform === "darwin" || platform === "linux";
75
+ }
76
+
77
+ /**
78
+ * Ask the manager to restart the job in place. Never throws. `unsettled` means the client gave up
79
+ * waiting while the manager may still complete the restart: callers must not start a competing
80
+ * proxy on that answer.
81
+ */
82
+ export function restartServiceManagerJob(deps = {}) {
83
+ const { platform, uid, run } = resolve(deps);
84
+ const result = platform === "darwin"
85
+ ? run("/bin/launchctl", ["kickstart", "-k", launchdTarget(uid)])
86
+ : platform === "linux"
87
+ ? run("systemctl", ["--user", "restart", SYSTEMD_SERVICE_UNIT])
88
+ : null;
89
+ if (result === null) return { ok: false, reason: "no launchd or systemd manager on this platform" };
90
+ if (result.timedOut) return { ok: false, unsettled: true, reason: "the service manager did not finish the restart in time" };
91
+ if (!result.ok) return { ok: false, reason: result.stderr.split("\n")[0] || "the service manager refused the restart" };
92
+ return { ok: true };
93
+ }
94
+
95
+ /** The manager's main PID for the job, or null when it reports none. Never throws. */
96
+ export function readServiceManagerPid(deps = {}) {
97
+ const { platform, uid, run } = resolve(deps);
98
+ if (platform === "darwin") {
99
+ const printed = run("/bin/launchctl", ["print", launchdTarget(uid)]);
100
+ return printed.ok ? parseLaunchdJobPid(printed.stdout) : null;
101
+ }
102
+ if (platform === "linux") {
103
+ const shown = run("systemctl", ["--user", "show", "-p", "MainPID", SYSTEMD_SERVICE_UNIT]);
104
+ return shown.ok ? parseSystemdMainPid(shown.stdout) : null;
105
+ }
106
+ return null;
107
+ }
108
+
109
+ /** Parent PID of a process, or null when it cannot be read. Never throws. */
110
+ export function readParentPid(pid, deps = {}) {
111
+ const { run } = resolve(deps);
112
+ if (!Number.isSafeInteger(pid) || pid <= 0) return null;
113
+ const result = run("ps", ["-o", "ppid=", "-p", String(pid)]);
114
+ return result.ok ? positivePid(result.stdout) : null;
115
+ }
116
+
117
+ /**
118
+ * Whether the serving proxy is the manager's job. `parentPid` is consulted only when the
119
+ * serving PID is not the manager's own.
120
+ */
121
+ export function supervisionVerdict({ servingPid, managerPid, parentPid }) {
122
+ if (!Number.isSafeInteger(servingPid) || servingPid <= 0) {
123
+ return { kind: "failed", reason: "the manager restarted the job but no proxy answered" };
124
+ }
125
+ if (!Number.isSafeInteger(managerPid) || managerPid <= 0) {
126
+ return { kind: "failed", reason: "the manager reports no running process for the job" };
127
+ }
128
+ if (servingPid === managerPid || parentPid(servingPid) === managerPid) {
129
+ return { kind: "supervised", managerPid, servingPid };
130
+ }
131
+ return { kind: "failed", reason: `the serving proxy (pid ${servingPid}) is not the manager's job (pid ${managerPid})` };
132
+ }
133
+
134
+ /**
135
+ * The serving PID to judge after a restart: a record still naming the pre-restart process is not
136
+ * evidence about the restarted one.
137
+ */
138
+ export function freshServingPid(recordPid, staleRecordPid) {
139
+ return Number.isSafeInteger(recordPid) && recordPid > 0 && recordPid !== staleRecordPid ? recordPid : null;
140
+ }
141
+
142
+ /** The warning printed when an update had to leave a proxy the manager does not own. */
143
+ export function unsupervisedRuntimeWarning(port) {
144
+ return [
145
+ `⚠️ The proxy on port ${port} is running UNSUPERVISED: the background service manager did not take it back after the update.`,
146
+ " A crash will not be restarted, and a later service start can race it for the port.",
147
+ " Run 'ocx service repair' to hand it back to the background service.",
148
+ ].join("\n");
149
+ }
150
+
151
+ /** The warning printed when the manager may still be restarting; no second proxy is started. */
152
+ export function unsettledManagerWarning(port) {
153
+ return [
154
+ `⚠️ The service manager is still restarting the proxy on port ${port}; no second proxy was started.`,
155
+ " Check it with 'ocx service status', and run 'ocx service repair' if it does not come back.",
156
+ ].join("\n");
157
+ }
@@ -294,11 +294,13 @@ export function appendFinalCacheDiagnostic(facts: CacheDiagnosticFinalFacts): vo
294
294
 
295
295
  const CACHE_DIAGNOSTIC_HOOK = Symbol.for("opencodex.cache-diagnostic.v1");
296
296
  interface CacheDiagnosticHooks {
297
+ enabled(): boolean;
297
298
  observeInbound(body: unknown, headers: Headers, source: PromptCacheKeySource): CacheDiagnosticDraft;
298
299
  rebind(body: unknown, draft: CacheDiagnosticDraft | undefined): void;
299
300
  finalize(facts: CacheDiagnosticFinalFacts): void;
300
301
  }
301
302
  (globalThis as Record<symbol, CacheDiagnosticHooks | undefined>)[CACHE_DIAGNOSTIC_HOOK] = {
303
+ enabled: isCacheDiagnosticEnabled,
302
304
  observeInbound,
303
305
  rebind: rebindCacheDiagnosticBodyAlias,
304
306
  finalize: appendFinalCacheDiagnostic,
@@ -88,6 +88,10 @@ const GEMINI_37_FLASH: Cost4 = { input: 0.75, output: 3.75, cacheRead: 0.075, ca
88
88
  // rising to $1.50 / $7.50 on 2027-01-01. A SEPARATE constant on purpose: equal today, but
89
89
  // aliasing them would silently drag 3.8 along if 3.7's row is ever re-verified differently.
90
90
  const GEMINI_38_FLASH: Cost4 = { input: 0.75, output: 3.75, cacheRead: 0.075, cacheWrite: 0 };
91
+ // Gemini 4 Argon introductory paid-tier rate: $2 in / $10 out (thinking included) / $0.10 cached
92
+ // per 1M through 2026-11-30, stepping up to $4 / $20 / $0.20 on 2026-12-01. Revisit this row
93
+ // then. No free tier. Implicit caching only, so there is no per-token cache-write price.
94
+ const GEMINI_4_ARGON: Cost4 = { input: 2, output: 10, cacheRead: 0.1, cacheWrite: 0 };
91
95
  const MINIMAX_M21_HIGHSPEED: Cost4 = { input: 0.6, output: 2.4, cacheRead: 0.03, cacheWrite: 0.375 };
92
96
  const KIMI_K3: Cost4 = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3 };
93
97
  const KIMI_K27_CODE: Cost4 = { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite: 0.95 };
@@ -200,6 +204,7 @@ export function cursorFastPriceSource(provider: string, modelId: string): string
200
204
 
201
205
  const GEMINI_PRICING = "https://ai.google.dev/gemini-api/docs/pricing (2026-07-22); cacheWrite=0: storage is billed per-hour, not per-token";
202
206
  const GEMINI_37_PRICING = "https://ai.google.dev/gemini-api/docs/pricing (2026-08-14); promotional rate through 2026-12-31, rises to 1.50/7.50 on 2027-01-01; cacheWrite=0: storage is billed per-hour, not per-token";
207
+ const GEMINI_4_ARGON_PRICING = "https://ai.google.dev/gemini-api/docs/pricing (2026-10-14); introductory rate through 2026-11-30, rises to 4/20 (0.20 cached) on 2026-12-01; cacheWrite=0: implicit caching only";
203
208
  const GEMINI_38_PRICING = "https://ai.google.dev/gemini-api/docs/pricing (2026-09-03); promotional rate through 2026-12-31, rises to 1.50/7.50 on 2027-01-01; cacheWrite=0: storage is billed per-hour, not per-token";
204
209
  const MINIMAX_PRICING = "https://platform.minimax.io/docs/guides/pricing-paygo";
205
210
  const OPENAI_GPT56_PRICING = "https://developers.openai.com/api/docs/pricing";
@@ -346,6 +351,12 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
346
351
  // 3.7 Flash rides CCA, whose billing equivalence to the Developer API list price is
347
352
  // not published, so this is `verified-derived` rather than `verified`: the number is
348
353
  // proven, the claim that Antigravity charges it is inferred.
354
+ // Gemini 4 Argon on Antigravity (AI Ultra / Enterprise only): the picker id plus its three
355
+ // verified CCA wire ids. Billing equivalence to the API price is inferred.
356
+ { provider: "google-antigravity", modelId: "gemini-4-argon", cost4: GEMINI_4_ARGON, source: `derived: Gemini 4 Argon introductory rate through 2026-11-30 ${GEMINI_4_ARGON_PRICING}`, verifiedAt: "2026-10-14", status: "verified-derived" },
357
+ { provider: "google-antigravity", modelId: "gemini-4-argon-low", cost4: GEMINI_4_ARGON, source: `derived: gemini-4-argon ${GEMINI_4_ARGON_PRICING}`, verifiedAt: "2026-10-14", status: "verified-derived" },
358
+ { provider: "google-antigravity", modelId: "gemini-4-argon-medium", cost4: GEMINI_4_ARGON, source: `derived: gemini-4-argon ${GEMINI_4_ARGON_PRICING}`, verifiedAt: "2026-10-14", status: "verified-derived" },
359
+ { provider: "google-antigravity", modelId: "gemini-4-argon-high", cost4: GEMINI_4_ARGON, source: `derived: gemini-4-argon ${GEMINI_4_ARGON_PRICING}`, verifiedAt: "2026-10-14", status: "verified-derived" },
349
360
  { provider: "google-antigravity", modelId: "gemini-3.8-flash", cost4: GEMINI_38_FLASH, source: `derived: Gemini 3.8 Flash promotional rate through 2026-12-31 ${GEMINI_38_PRICING}`, verifiedAt: "2026-09-03", status: "verified-derived" },
350
361
  { provider: "google-antigravity", modelId: "gemini-3.8-flash-low", cost4: GEMINI_38_FLASH, source: `derived: gemini-3.8-flash ${GEMINI_38_PRICING}`, verifiedAt: "2026-09-03", status: "verified-derived" },
351
362
  { provider: "google-antigravity", modelId: "gemini-3.8-flash-medium", cost4: GEMINI_38_FLASH, source: `derived: gemini-3.8-flash ${GEMINI_38_PRICING}`, verifiedAt: "2026-09-03", status: "verified-derived" },
@@ -403,6 +414,7 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
403
414
  // Developer API row: the price IS published for this surface, so `verified`.
404
415
  { provider: "google", modelId: "gemini-3.7-flash", cost4: GEMINI_37_FLASH, source: GEMINI_37_PRICING, verifiedAt: "2026-08-14", status: "verified" },
405
416
  { provider: "google", modelId: "gemini-3.8-flash", cost4: GEMINI_38_FLASH, source: GEMINI_38_PRICING, verifiedAt: "2026-09-03", status: "verified" },
417
+ { provider: "google", modelId: "gemini-4-argon", cost4: GEMINI_4_ARGON, source: GEMINI_4_ARGON_PRICING, verifiedAt: "2026-10-14", status: "verified" },
406
418
  { provider: "google-antigravity", modelId: "gemini-3.1-pro-preview", cost4: GEMINI_31_PRO, source: GEMINI_PRICING, verifiedAt: "2026-07-20", status: "verified" },
407
419
  // Antigravity-bundled third-party models — derived from the underlying vendor's
408
420
  // official API price (Antigravity itself bills via subscription quota).
@@ -15,7 +15,7 @@ import { cancelBodyOnAbort, signalWithTimeout } from "../lib/abort";
15
15
  import { readBoundedResponseBytes } from "../lib/bounded-body";
16
16
  import { sidecarEnter } from "../lib/sidecar-tracker";
17
17
  import { redactSecretString } from "../lib/redact";
18
- import { ANTIGRAVITY_REQUEST_UA } from "../adapters/google-antigravity-wire";
18
+ import { antigravityUserAgentForWireModel } from "../adapters/google-antigravity-wire";
19
19
  import { resolveAntigravityEffortWireModel } from "../providers/antigravity-models";
20
20
  import { getProviderRegistryEntry } from "../providers/registry";
21
21
  import { MAX_SIDECAR_RESPONSE_BYTES, type WebSearchSource } from "./parse";
@@ -74,7 +74,7 @@ export async function runGeminiWebSearch(
74
74
  headers: {
75
75
  "Content-Type": "application/json",
76
76
  "Authorization": `Bearer ${token}`,
77
- "User-Agent": ANTIGRAVITY_REQUEST_UA,
77
+ "User-Agent": antigravityUserAgentForWireModel(wireModelId),
78
78
  },
79
79
  body: JSON.stringify(envelope),
80
80
  signal: linkedSignal.signal,
Binary file