@bitkyc08/opencodex 2.67.0 → 2.68.0-preview.20260927

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 (257) hide show
  1. package/bin/ocx.mjs +3 -0
  2. package/gui/dist/assets/App-B5-cAEd2.js +51 -0
  3. package/gui/dist/assets/App-DCBismRi.css +1 -0
  4. package/gui/dist/assets/Tray-DFnMiyD0.js +1 -0
  5. package/gui/dist/assets/{index-DtNmX7hW.css → index-BDUBS8PW.css} +1 -1
  6. package/gui/dist/assets/index-BcXblvem.js +86 -0
  7. package/gui/dist/assets/quota-summary-i84eOU3o.js +1 -0
  8. package/gui/dist/index.html +21 -2
  9. package/package.json +1 -1
  10. package/src/adapters/anthropic-image-guard.ts +13 -1
  11. package/src/adapters/anthropic-image-normalize.ts +3 -2
  12. package/src/adapters/anthropic-output-schema.ts +36 -0
  13. package/src/adapters/anthropic.ts +10 -2
  14. package/src/adapters/base.ts +7 -0
  15. package/src/adapters/codebuddy/live-models.ts +180 -0
  16. package/src/adapters/coding-agent/protocol.ts +91 -12
  17. package/src/adapters/coding-agent/turn.ts +32 -27
  18. package/src/adapters/devin/cloud-direct/index.ts +1 -0
  19. package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +18 -4
  20. package/src/adapters/devin.ts +9 -6
  21. package/src/adapters/google-http.ts +4 -4
  22. package/src/adapters/google.ts +93 -3
  23. package/src/adapters/kiro/adapter.ts +6 -2
  24. package/src/adapters/kiro/stream.ts +12 -2
  25. package/src/adapters/kiro/usage.ts +3 -1
  26. package/src/adapters/kiro-errors.ts +11 -1
  27. package/src/adapters/kiro-events.ts +27 -1
  28. package/src/adapters/kiro-refusal.ts +30 -0
  29. package/src/adapters/kiro-retry.ts +71 -31
  30. package/src/adapters/openai-chat/deepseek-artifact-schema.ts +45 -0
  31. package/src/adapters/openai-chat/messages.ts +31 -4
  32. package/src/adapters/openai-chat/serialized-tool-call-content.ts +52 -7
  33. package/src/adapters/openai-chat/tool-call-id-remint.ts +65 -0
  34. package/src/adapters/openai-chat/tool-schema.ts +6 -2
  35. package/src/adapters/openai-chat.ts +2 -2
  36. package/src/adapters/openai-responses/muse-tool-choice.ts +31 -0
  37. package/src/adapters/openai-responses/passthrough.ts +9 -3
  38. package/src/adapters/opencode-go-additional-tools.ts +19 -10
  39. package/src/adapters/physical-send.ts +10 -3
  40. package/src/adapters/registry.ts +2 -1
  41. package/src/adapters/responses-tool-schema.ts +31 -3
  42. package/src/adapters/run-turn-queue.ts +63 -23
  43. package/src/adapters/unique-tool-call-ids.ts +63 -0
  44. package/src/adapters/xai-web-search.ts +32 -2
  45. package/src/bridge/sse.ts +14 -2
  46. package/src/chat/inbound.ts +20 -6
  47. package/src/claude/agents-inject.ts +4 -0
  48. package/src/claude/desktop-3p.ts +53 -13
  49. package/src/claude/desktop-profile.ts +41 -4
  50. package/src/claude/inbound-model-options.ts +14 -2
  51. package/src/claude/inbound.ts +1 -1
  52. package/src/claude/intercept/connect-proxy.ts +17 -1
  53. package/src/claude/intercept/local-ca.ts +7 -1
  54. package/src/claude/message-threads.ts +28 -0
  55. package/src/cli/account-api.ts +13 -0
  56. package/src/cli/account-auth.ts +49 -2
  57. package/src/cli/account-extended.ts +4 -2
  58. package/src/cli/account.ts +4 -2
  59. package/src/cli/capabilities.ts +17 -0
  60. package/src/cli/claude.ts +3 -1
  61. package/src/cli/dispatch.ts +62 -5
  62. package/src/cli/index.ts +84 -79
  63. package/src/cli/minimax.ts +4 -2
  64. package/src/cli/opencode.ts +6 -1
  65. package/src/cli/restart-handoff.ts +110 -0
  66. package/src/cli/status.ts +51 -0
  67. package/src/client/connect.ts +34 -15
  68. package/src/client/link-ingress.ts +102 -0
  69. package/src/client/link-join.ts +37 -16
  70. package/src/client/link-relay.ts +228 -45
  71. package/src/client/link-state.ts +54 -0
  72. package/src/client/link-status.ts +39 -0
  73. package/src/client/link-teardown.ts +2 -2
  74. package/src/client/link-tunnel.ts +615 -86
  75. package/src/client/machine-api.ts +10 -4
  76. package/src/client/machine-listener.ts +57 -10
  77. package/src/client/runtime.ts +210 -35
  78. package/src/clients/config-export/constants.ts +1 -12
  79. package/src/clients/config-export/contracts.ts +2 -0
  80. package/src/clients/config-export/model-metadata.ts +9 -3
  81. package/src/clients/config-export/omp.ts +1 -1
  82. package/src/clients/config-export/zcode-store.ts +2 -2
  83. package/src/clients/config-export.ts +36 -3
  84. package/src/codex/app-server-processes.ts +32 -0
  85. package/src/codex/app-server-restart-service.ts +20 -3
  86. package/src/codex/catalog/access-programs.ts +50 -0
  87. package/src/codex/catalog/build-entries.ts +13 -0
  88. package/src/codex/catalog/derive-entry.ts +1 -0
  89. package/src/codex/catalog/model-hints.ts +11 -0
  90. package/src/codex/catalog/parsing.ts +6 -2
  91. package/src/codex/catalog/provider-models.ts +81 -0
  92. package/src/codex/catalog/retained-sync.ts +3 -0
  93. package/src/codex/codex-write-lock.ts +2 -2
  94. package/src/codex/convergence.ts +2 -0
  95. package/src/codex/desired-state.ts +14 -4
  96. package/src/codex/home.ts +21 -3
  97. package/src/codex/inject/restore.ts +17 -0
  98. package/src/codex/inject/routing-classify.ts +3 -2
  99. package/src/codex/inject-coordination.ts +4 -1
  100. package/src/codex/inject.ts +8 -1
  101. package/src/codex/journal.ts +6 -2
  102. package/src/codex/management-convergence.ts +9 -0
  103. package/src/codex/model-entitlements.ts +28 -5
  104. package/src/codex/routing/idle-window.ts +58 -0
  105. package/src/codex/routing-drift.ts +134 -0
  106. package/src/codex/routing-healer.ts +419 -0
  107. package/src/codex/routing.ts +10 -0
  108. package/src/codex/runtime.ts +188 -57
  109. package/src/codex/sibling-handoff.ts +71 -0
  110. package/src/codex/sibling-start.ts +139 -0
  111. package/src/codex/sync.ts +5 -3
  112. package/src/combos/failover.ts +32 -5
  113. package/src/combos/request.ts +11 -3
  114. package/src/combos/reset-window.ts +10 -5
  115. package/src/combos/resolve.ts +36 -6
  116. package/src/config/diagnostics.ts +2 -1
  117. package/src/config/load-degrade.ts +2 -0
  118. package/src/config/paths.ts +7 -1
  119. package/src/config/process-state.ts +10 -1
  120. package/src/config/schema/compaction-recovery.ts +15 -0
  121. package/src/config/schema/config-schema.ts +2 -0
  122. package/src/config/schema/leaf-validators.ts +9 -3
  123. package/src/generated/compatibility-version.json +360 -204
  124. package/src/images/loop.ts +47 -32
  125. package/src/integrations/catalog-refresh.ts +4 -0
  126. package/src/integrations/omp-yaml-source.ts +1 -0
  127. package/src/lib/claude-request-projection.ts +101 -0
  128. package/src/lib/local-account-switch-capability.ts +48 -0
  129. package/src/lib/local-upstream.ts +146 -0
  130. package/src/lib/package-tree-integrity.ts +25 -3
  131. package/src/lib/package-tree-retarget.ts +130 -0
  132. package/src/lib/process-control.ts +4 -1
  133. package/src/lib/system-restart-contract.ts +106 -0
  134. package/src/lib/upstream-retry.ts +57 -1
  135. package/src/link/ports.ts +9 -0
  136. package/src/link/ssh-argv.ts +16 -0
  137. package/src/link/ssh-runner.ts +70 -2
  138. package/src/link/tunnel-state.ts +58 -9
  139. package/src/oauth/account-quota-rank.ts +29 -18
  140. package/src/oauth/generic-account-failover.ts +179 -22
  141. package/src/oauth/index.ts +17 -4
  142. package/src/oauth/kiro-account-load.ts +107 -0
  143. package/src/oauth/kiro-device-login.ts +312 -0
  144. package/src/oauth/kiro-terminal-failover.ts +28 -0
  145. package/src/oauth/login-flow-state.ts +7 -1
  146. package/src/oauth/pool-settings-capability.ts +23 -14
  147. package/src/oauth/store.ts +56 -3
  148. package/src/oauth/types.ts +4 -0
  149. package/src/plugins/loader.ts +350 -0
  150. package/src/plugins/upstream-hooks.ts +119 -0
  151. package/src/protocols/encoders/adapter-events.ts +9 -2
  152. package/src/providers/account-quota-disk.ts +42 -4
  153. package/src/providers/codebuddy-models.ts +6 -2
  154. package/src/providers/command-code-efforts.ts +33 -10
  155. package/src/providers/kiro-account-state-disk.ts +44 -0
  156. package/src/providers/kiro-model-catalog.ts +162 -0
  157. package/src/providers/kiro-models.ts +5 -4
  158. package/src/providers/kiro-quota-metrics.ts +35 -0
  159. package/src/providers/kiro-usage.ts +118 -14
  160. package/src/providers/quota/account-cache.ts +43 -7
  161. package/src/providers/quota/antigravity.ts +8 -3
  162. package/src/providers/quota/kiro-account-probe.ts +12 -0
  163. package/src/providers/quota/vendor-probes-key.ts +31 -26
  164. package/src/providers/quota/vendor-probes-oauth.ts +16 -7
  165. package/src/providers/quota-types.ts +14 -0
  166. package/src/providers/quota.ts +21 -21
  167. package/src/providers/registry/entries-extended.ts +5 -3
  168. package/src/providers/request-pacing.ts +410 -12
  169. package/src/remote-control/workspace-codex-runtime.ts +4 -3
  170. package/src/responses/citation-markers.ts +132 -65
  171. package/src/responses/hosted-tool-policy.ts +14 -3
  172. package/src/responses/parser-content.ts +3 -2
  173. package/src/responses/parser.ts +4 -1
  174. package/src/responses/schema.ts +4 -0
  175. package/src/responses/visualization-directives.ts +182 -0
  176. package/src/server/chat-native-sse.ts +24 -12
  177. package/src/server/chat-native.ts +137 -68
  178. package/src/server/claude-messages.ts +147 -12
  179. package/src/server/index/link-listener.ts +7 -0
  180. package/src/server/index/optional-listeners.ts +4 -1
  181. package/src/server/index/package-tree-guard.ts +35 -16
  182. package/src/server/index/serve-options.ts +4 -2
  183. package/src/server/index/startup-warnings.ts +6 -0
  184. package/src/server/index.ts +7 -7
  185. package/src/server/inference/client-encoder-delivery.ts +3 -0
  186. package/src/server/inference/context.ts +30 -2
  187. package/src/server/local-account-switch-auth.ts +79 -0
  188. package/src/server/management/agent-settings-routes.ts +11 -3
  189. package/src/server/management/config-routes.ts +28 -11
  190. package/src/server/management/context.ts +2 -0
  191. package/src/server/management/link-routes.ts +123 -39
  192. package/src/server/management/logs-usage-routes.ts +9 -42
  193. package/src/server/management/model-rows.ts +1 -0
  194. package/src/server/management/oauth-account-routes.ts +67 -12
  195. package/src/server/management/route-registry.ts +5 -5
  196. package/src/server/management/sibling-guard.ts +60 -0
  197. package/src/server/management/storage-log-guard-routes.ts +72 -6
  198. package/src/server/management/system-restart.ts +92 -135
  199. package/src/server/management/system-routes.ts +1 -1
  200. package/src/server/management-api.ts +31 -3
  201. package/src/server/management-auth.ts +3 -0
  202. package/src/server/port-reclaim.ts +111 -1
  203. package/src/server/proxy-liveness.ts +16 -0
  204. package/src/server/relay.ts +16 -15
  205. package/src/server/request-log-filter.ts +28 -27
  206. package/src/server/request-log.ts +7 -3
  207. package/src/server/request-metrics.ts +28 -0
  208. package/src/server/responses/adapter-continuation.ts +151 -21
  209. package/src/server/responses/adapter-delivery.ts +21 -7
  210. package/src/server/responses/adapter-dispatch.ts +239 -51
  211. package/src/server/responses/codex-ws-pool.ts +5 -4
  212. package/src/server/responses/codex-ws-request.ts +4 -1
  213. package/src/server/responses/compact.ts +8 -4
  214. package/src/server/responses/compaction-recovery-policy.ts +116 -0
  215. package/src/server/responses/compaction-recovery.ts +330 -0
  216. package/src/server/responses/core-combo-failure.ts +1 -0
  217. package/src/server/responses/core-combo.ts +5 -1
  218. package/src/server/responses/core-lifetime.ts +22 -0
  219. package/src/server/responses/core-options.ts +16 -1
  220. package/src/server/responses/core.ts +14 -4
  221. package/src/server/responses/empty-completion-guard.ts +2 -0
  222. package/src/server/responses/fetch-helpers.ts +82 -31
  223. package/src/server/responses/passthrough-delivery.ts +3 -2
  224. package/src/server/responses/passthrough-dispatch.ts +12 -2
  225. package/src/server/responses/passthrough-execution.ts +2 -2
  226. package/src/server/responses/request-send-budget.ts +21 -5
  227. package/src/server/responses/request-transport.ts +116 -18
  228. package/src/server/responses/reset-replay.ts +32 -0
  229. package/src/server/responses/run-turn-execution.ts +136 -19
  230. package/src/server/responses/sidecar-execution.ts +41 -8
  231. package/src/server/responses/terminal-guard.ts +2 -0
  232. package/src/server/responses/ws-upstream.ts +44 -4
  233. package/src/server/restart-replacement.ts +382 -0
  234. package/src/server/startup-health-cache.ts +23 -4
  235. package/src/server/stop-teardown.ts +35 -1
  236. package/src/server/system-env.ts +3 -0
  237. package/src/service/windows-taskxml.ts +11 -7
  238. package/src/service/windows-wrapper-exit.ts +8 -0
  239. package/src/stall-timeout.ts +35 -10
  240. package/src/storage/policy-job.ts +4 -0
  241. package/src/storage/scanner.ts +177 -29
  242. package/src/storage/storage-mutation-coordinator.ts +17 -0
  243. package/src/tray/windows-tray.ps1 +50 -23
  244. package/src/types/config.ts +4 -0
  245. package/src/types/provider.ts +5 -1
  246. package/src/types/request.ts +13 -2
  247. package/src/update/index.ts +3 -1
  248. package/src/update/job.ts +6 -2
  249. package/src/update/mise-launcher-target.ts +100 -0
  250. package/src/update/worker-launch.ts +46 -0
  251. package/src/usage/log.ts +2 -0
  252. package/src/web-search/loop.ts +5 -2
  253. package/gui/dist/assets/App-BJsT8Icc.css +0 -1
  254. package/gui/dist/assets/App-D3pNGiN4.js +0 -51
  255. package/gui/dist/assets/Tray-yb05wCjb.js +0 -1
  256. package/gui/dist/assets/index-CouvvtMV.js +0 -86
  257. package/gui/dist/assets/tray-data-CYGdjCJ7.js +0 -1
@@ -23,7 +23,7 @@ import {
23
23
  loopbackCompanionBindError,
24
24
  websocketsEnabled,
25
25
  } from "../config";
26
- import { flushConfigDirHardening } from "../config/paths";
26
+ import { flushConfigDirHardeningAndReaps } from "../config/paths";
27
27
  import { migrateStartupSubagentModels } from "./subagent-models-startup";
28
28
  import { migrateStartupXaiResponses } from "./xai-responses-startup";
29
29
  import { migrateStartupZaiResponses } from "./zai-responses-startup";
@@ -117,7 +117,7 @@ import { setUsageLedgerRetention } from "./usage-ledger-retention";
117
117
  import { admitHttpWorkflowTurn, workflowDecisionRefusalResponse, type WorkflowRefusalLog } from "./workflow-refusal";
118
118
  export {
119
119
  addFinalRequestLog,
120
- filterRequestLogs,
120
+ filterRequestLogs, queryRequestLogs,
121
121
  hydrateRequestLogsFromDisk,
122
122
  httpStatusForTerminalStatus,
123
123
  httpStatusFromTerminalError,
@@ -788,12 +788,12 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
788
788
  await backgroundLifecycle.release();
789
789
  await releaseNativeMainStartupLifecycle(server);
790
790
  } finally {
791
- // icacls.exe from hardenConfigDir() holds the config dir open; a caller that
792
- // removes the dir right after stop() settles would hit EPERM/EBUSY on Windows
793
- // otherwise. Config hardening still flushes when an earlier release rejects. The
794
- // spend owner is retained when a listener stop failed because the socket may live.
791
+ // icacls.exe from hardenConfigDir() holds the config dir open. The caller-facing
792
+ // hardening deadline can settle before its child exits, so also wait for that
793
+ // child's reap before stop() promises the directory is removable. The spend owner
794
+ // is retained when a listener stop failed because the socket may live.
795
795
  try { if (listenersStopped) spendLedgerLifecycle.release(); }
796
- finally { await flushConfigDirHardening(startupConfigDir); }
796
+ finally { await flushConfigDirHardeningAndReaps(startupConfigDir); }
797
797
  }
798
798
  },
799
799
  );
@@ -94,6 +94,8 @@ export interface ClientEncodedDelivery {
94
94
  toolSearchToolNames?: Set<string>;
95
95
  };
96
96
  stallTimeoutSec?: number;
97
+ /** Operator-trusted local upstream: an unset stall budget stays disabled there (#5876). */
98
+ localUpstream?: boolean;
97
99
  turnAdmissionLease?: AdmissionLease;
98
100
  onFirstOutput?: () => void;
99
101
  /** The bridge's `onCancel`: stop completion notification and abort the upstream. */
@@ -212,6 +214,7 @@ export async function deliverClientEncodedResponse(input: ClientEncodedDelivery)
212
214
  translatorBudget,
213
215
  ...input.fold,
214
216
  ...(input.stallTimeoutSec !== undefined ? { stallTimeoutSec: input.stallTimeoutSec } : {}),
217
+ ...(input.localUpstream !== undefined ? { localUpstream: input.localUpstream } : {}),
215
218
  hooks,
216
219
  };
217
220
  const encoded = encoder.protocol === "chat"
@@ -1,7 +1,16 @@
1
- import { createRequestExecutionBudget, type RequestExecutionBudget } from "../../lib/request-execution-budget";
1
+ import { CODEX_TEXT_GUARDED_BUDGET_POLICY, createRequestExecutionBudget, type RequestExecutionBudget } from "../../lib/request-execution-budget";
2
+ import { TRANSIENT_RETRY_MAX_ATTEMPTS, type TransientSendBudget } from "../../lib/upstream-retry";
3
+ import { GENERIC_OAUTH_MAX_ACCOUNTS_PER_REQUEST } from "../../oauth/generic-account-failover";
2
4
  import type { RequestLogContext } from "../request-log";
3
5
  import { attachRequestSpendTracker } from "../responses/request-spend";
4
6
 
7
+ // Only ingress owns an expandable policy. Exact caller budgets and combo-derived scopes
8
+ // never enter this map, even when their policy happens to equal the default profile.
9
+ const ingressPolicies = new WeakMap<TransientSendBudget, {
10
+ maxTotalModelSends: number;
11
+ baseSendAllowance: number;
12
+ }>();
13
+
5
14
  /**
6
15
  * The one construction of an ingress-owned send budget: the default guarded policy, no logical
7
16
  * request id, and this request's spend tracker as the send observer. Attaching the tracker
@@ -11,5 +20,24 @@ export function createInferenceSendBudget(
11
20
  req: Pick<Request, "headers">,
12
21
  logCtx: RequestLogContext,
13
22
  ): RequestExecutionBudget {
14
- return createRequestExecutionBudget(undefined, undefined, attachRequestSpendTracker(req, logCtx));
23
+ const policy = { ...CODEX_TEXT_GUARDED_BUDGET_POLICY };
24
+ const budget = createRequestExecutionBudget(policy, undefined, attachRequestSpendTracker(req, logCtx));
25
+ ingressPolicies.set(budget, policy);
26
+ return budget;
27
+ }
28
+
29
+ /**
30
+ * Fund each account's normal transient ladder once, before the first physical send. The account
31
+ * count is clamped here as well as at the caller, so no caller can raise the ceiling past
32
+ * GENERIC_OAUTH_MAX_ACCOUNTS_PER_REQUEST × TRANSIENT_RETRY_MAX_ATTEMPTS.
33
+ */
34
+ export function expandInferenceOAuthSendBudget(budget: TransientSendBudget | undefined, accounts: number): void {
35
+ if (!budget) return;
36
+ const policy = ingressPolicies.get(budget);
37
+ if (!policy || budget.used !== 0) return;
38
+ ingressPolicies.delete(budget);
39
+ if (accounts < 2 || !Number.isSafeInteger(accounts)) return;
40
+ const sends = Math.min(accounts, GENERIC_OAUTH_MAX_ACCOUNTS_PER_REQUEST) * TRANSIENT_RETRY_MAX_ATTEMPTS;
41
+ policy.baseSendAllowance = Math.max(policy.baseSendAllowance, sends);
42
+ policy.maxTotalModelSends = Math.max(policy.maxTotalModelSends, sends);
15
43
  }
@@ -0,0 +1,79 @@
1
+ import { timingSafeEqual } from "node:crypto";
2
+ import {
3
+ LOCAL_MANAGEMENT_CAPABILITY_EXPIRES_AT_HEADER,
4
+ LOCAL_MANAGEMENT_CAPABILITY_HEADER,
5
+ LOCAL_MANAGEMENT_EXPECTED_PID_HEADER,
6
+ LOCAL_MANAGEMENT_NONCE_HEADER,
7
+ parseExpectedLocalManagementPid,
8
+ } from "../lib/local-management-capability";
9
+ import {
10
+ accountSwitchBodyDigest, isLocalAccountSwitchPath,
11
+ LOCAL_ACCOUNT_SWITCH_BODY_HEADER, verifyLocalAccountSwitchCapability,
12
+ } from "../lib/local-account-switch-capability";
13
+ import type { LocalManagementAuthContext } from "./management-auth";
14
+
15
+ const REPLAY_LIMIT = 256;
16
+ const BODY_LIMIT = 1024;
17
+ const consumed = new Map<string, number>();
18
+ const admittedBodyDigests = new WeakMap<Request, string>();
19
+
20
+ /** Admit one exact body-bound account switch on the attested local listener. */
21
+ export function hasLocalAccountSwitchCapability(
22
+ req: Request, local: LocalManagementAuthContext | undefined, now = Date.now(),
23
+ ): boolean {
24
+ if (admittedBodyDigests.has(req)) return true;
25
+ if (!local || req.method !== "PUT" || req.headers.has("origin")) return false;
26
+ let url: URL;
27
+ try { url = new URL(req.url); } catch { return false; }
28
+ if (!isLocalAccountSwitchPath(url.pathname) || url.search !== "") return false;
29
+ const expectedPid = parseExpectedLocalManagementPid(req.headers.get(LOCAL_MANAGEMENT_EXPECTED_PID_HEADER));
30
+ if (expectedPid.kind !== "present" || expectedPid.pid !== local.pid) return false;
31
+ const expiry = req.headers.get(LOCAL_MANAGEMENT_CAPABILITY_EXPIRES_AT_HEADER);
32
+ if (!expiry || !/^[1-9]\d*$/.test(expiry)) return false;
33
+ const expiresAt = Number(expiry);
34
+ const capability = req.headers.get(LOCAL_MANAGEMENT_CAPABILITY_HEADER);
35
+ const bodyDigest = req.headers.get(LOCAL_ACCOUNT_SWITCH_BODY_HEADER);
36
+ if (!verifyLocalAccountSwitchCapability(
37
+ local.attestationSecret, req.headers.get(LOCAL_MANAGEMENT_NONCE_HEADER),
38
+ req.method, url.pathname, local.pid, local.port, expiresAt, bodyDigest, capability, now,
39
+ )) return false;
40
+ for (const [proof, until] of consumed) if (until <= now) consumed.delete(proof);
41
+ if (!capability || !bodyDigest || consumed.has(capability) || consumed.size >= REPLAY_LIMIT) return false;
42
+ consumed.set(capability, expiresAt);
43
+ admittedBodyDigests.set(req, bodyDigest);
44
+ return true;
45
+ }
46
+
47
+ /** Stream a bounded raw body and compare it with the authenticated, cached digest. */
48
+ export async function readVerifiedAccountSwitchBody(
49
+ req: Request,
50
+ ): Promise<{ status: 200; body: Uint8Array } | { status: 403 | 413 }> {
51
+ const expected = admittedBodyDigests.get(req);
52
+ if (!expected) return { status: 403 };
53
+ const reader = req.body?.getReader();
54
+ const chunks: Uint8Array[] = [];
55
+ let size = 0;
56
+ if (reader) {
57
+ try {
58
+ while (true) {
59
+ const { done, value } = await reader.read();
60
+ if (done) break;
61
+ size += value.byteLength;
62
+ if (size > BODY_LIMIT) {
63
+ await reader.cancel();
64
+ return { status: 413 };
65
+ }
66
+ chunks.push(value);
67
+ }
68
+ } catch {
69
+ return { status: 403 };
70
+ } finally {
71
+ reader.releaseLock();
72
+ }
73
+ }
74
+ const body = new Uint8Array(size);
75
+ let offset = 0;
76
+ for (const chunk of chunks) { body.set(chunk, offset); offset += chunk.byteLength; }
77
+ if (!timingSafeEqual(Buffer.from(expected), Buffer.from(accountSwitchBodyDigest(body)))) return { status: 403 };
78
+ return { status: 200, body };
79
+ }
@@ -44,6 +44,7 @@ import { clearThreadAccountMap } from "../../codex/routing";
44
44
  import { primeCodexPoolQuotas } from "../../codex/auth-api";
45
45
  import { DEFAULT_PROVIDER_CONTEXT_CAP, globalContextCapValue, providerContextCap, providerContextCaps, setAllProviderContextCaps, setGlobalContextCapValue, setProviderContextCap } from "../../providers/context-cap";
46
46
  import { resolveCodexHomeDir } from "../../codex/home";
47
+ import { siblingOfLivePort } from "../../codex/sibling-start";
47
48
  import { MULTI_AGENT_MODE_HINT_RECOMMENDATION } from "../../codex/multi-agent-mode-policy";
48
49
  import { readUsageEntries } from "../../usage/log";
49
50
  import { getUsageDebugLogEntries } from "../../usage/debug";
@@ -210,6 +211,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
210
211
 
211
212
  /** Best-effort Desktop 3P config auto-reconcile when providers change. */
212
213
  async function autoApplyDesktopBestEffort(): Promise<void> {
214
+ if (siblingOfLivePort() !== null) return;
213
215
  try {
214
216
  const { claudeDesktopIntegrationEnabled } = await import("../../codex/desired-state");
215
217
  const admitted = loadConfig();
@@ -226,9 +228,11 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
226
228
  if (["not_installed", "no_owned_state", "foreign", "unsafe", "broken"].includes(beforeKind)) return;
227
229
  const { filterCatalogVisibleModels, desktopVisibleNativeSlugs } = await import("../../codex/catalog");
228
230
  const allModels = await (deps.fetchAllModels ?? fetchAllModels)(admitted);
231
+ if (siblingOfLivePort() !== null) return;
229
232
  // Serialized with Desktop mode transitions: a first-party switch cannot interleave with this write.
230
233
  const { runPickerTransition } = await import("./claude-desktop-picker-routes");
231
234
  await runPickerTransition(admitted, async () => {
235
+ if (siblingOfLivePort() !== null) return undefined;
232
236
  const current = loadConfig();
233
237
  // This is the real guard: the catalog await admits a concurrent explicit OFF.
234
238
  if (!claudeDesktopIntegrationEnabled(current)) return undefined;
@@ -753,9 +757,13 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
753
757
  ...[...new Set(chosen)].filter(model => !selectableSet.has(model)),
754
758
  ];
755
759
  // #857: let CLI/GUI show when a running Codex app-server keeps an older
756
- // in-memory catalog than the one on disk.
757
- const { collectCodexAppServerCatalogState } = await import("../../codex/app-server-processes");
758
- const catalogState = collectCodexAppServerCatalogState();
760
+ // in-memory catalog than the one on disk. Bounded request-path read: the synchronous
761
+ // collector blocked the event loop for the whole Windows CIM walk (4-7s measured).
762
+ const {
763
+ collectCodexAppServerCatalogStateWithin,
764
+ DASHBOARD_CATALOG_STATE_DEADLINE_MS,
765
+ } = await import("../../codex/app-server-processes");
766
+ const catalogState = await collectCodexAppServerCatalogStateWithin(DASHBOARD_CATALOG_STATE_DEADLINE_MS);
759
767
  return jsonResponse({
760
768
  chosen, available, catalogState,
761
769
  pickerAvailable: [...new Set(filterCatalogVisibleModels(models, config).map(catalogModelSlug).filter(slug => slug.includes("/")))],
@@ -1,4 +1,5 @@
1
1
  import { compactionRoutingSchema } from "../../config/schema/leaf-validators";
2
+ import { compactionRecoverySchema } from "../../config/schema/compaction-recovery";
2
3
  import { captureConfigTopLevelRollback } from "../../config/rebase-provenance";
3
4
  import type { IntegrationClientId } from "../../integrations/registry";
4
5
  import { randomUUID } from "node:crypto";
@@ -69,6 +70,7 @@ import {
69
70
  initializeDefaultCodexAccountNamespaces,
70
71
  } from "../../codex/account-namespaces";
71
72
  import { catalogRefreshIsPending } from "../../codex/catalog-refresh-status";
73
+ import { siblingOfLivePort } from "../../codex/sibling-start";
72
74
  import { DEFAULT_PROVIDER_CONTEXT_CAP, globalContextCapValue, providerContextCap, providerContextCaps, setAllProviderContextCaps, setGlobalContextCapValue, setProviderContextCap } from "../../providers/context-cap";
73
75
  import { resolveCodexHomeDir } from "../../codex/home";
74
76
  import { readUsageEntries } from "../../usage/log";
@@ -119,10 +121,10 @@ import type { PersistedUsageAttempt } from "../../usage/log";
119
121
  import { isAllowedRequestOrigin, jsonResponse, providerManagementConfigError, publicProviderBaseUrl, safeConfigDTO } from "../auth-cors";
120
122
  import { withProviderCatalogCapabilityDTO } from "./provider-capability-config";
121
123
  import { applySystemEnvToggle } from "../system-env";
122
- import { getCachedStartupHealth, invalidateStartupHealthCache } from "../startup-health-cache";
124
+ import { getCachedStartupHealth, getStartupHealthSnapshot, invalidateStartupHealthCache } from "../startup-health-cache";
123
125
  import { runWindowsTrayAction } from "../windows-tray-control";
124
126
  import { runStartupInstallAction, type StartupInstallAction } from "../startup-action-control";
125
- import { displayCodexRuntimePath, effortClampAppliesToRuntime, liveRemovedEfforts, loadLastEffortClamp, resolveCodexRuntime } from "../../codex/runtime";
127
+ import { displayCodexRuntimePath, effortClampAppliesToRuntime, getCodexRuntimeSnapshot, liveRemovedEfforts, loadLastEffortClamp } from "../../codex/runtime";
126
128
 
127
129
  import { isPlainRecord, parseDebugLogQuery, tokPerSecondResult, unavailableCostReason, costResult, requestLogDto, stripRegistryOnlyStaticHeaders, fetchAllModels } from "./shared";
128
130
  import type { MetricUnavailableReason, TokPerSecondResult, CostEstimateReason, CostResult, MetricSource } from "./shared";
@@ -196,7 +198,9 @@ export async function syncEnabledClientIntegrations(
196
198
  deps: Pick<ManagementContext["deps"],
197
199
  "fetchAllModels" | "refreshOwnedCatalogIntegrations" | "writeDesktop3pConfig"> = {},
198
200
  ): Promise<ClientIntegrationSyncOutcome[]> {
199
- if (port === undefined) return [];
201
+ // A sibling instance passes its OWN port here; the Grok fence and the Desktop gateway profile
202
+ // stay on the live owner's (`src/codex/sibling-start.ts`).
203
+ if (port === undefined || siblingOfLivePort() !== null) return [];
200
204
  const { claudeDesktopIntegrationEnabled, grokIntegrationEnabled } = await import("../../codex/desired-state");
201
205
  const out: ClientIntegrationSyncOutcome[] = [];
202
206
 
@@ -291,6 +295,10 @@ function publicVisionSidecarSettings(
291
295
  export async function handleConfigRoutes(ctx: ManagementContext): Promise<Response | null> {
292
296
  const { req, url, config, deps, convergeCodexCatalog, syncClaudeAgentDefsBestEffort } = ctx;
293
297
  const readStartupHealth = deps.getCachedStartupHealth ?? getCachedStartupHealth;
298
+ // Settings only seed the dashboard chip; /api/startup-health owns the bounded fresh read.
299
+ // Waiting on the Windows service-manager probe here held settings reads and saves open
300
+ // for up to 15s, including the admin-token check. An injected reader stays authoritative.
301
+ const readStartupHealthSnapshot = deps.getCachedStartupHealth ?? getStartupHealthSnapshot;
294
302
  if (url.pathname === "/api/config" && req.method === "GET") {
295
303
  return jsonResponse(withProviderCatalogCapabilityDTO(safeConfigDTO(config), config));
296
304
  }
@@ -300,10 +308,12 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
300
308
  }
301
309
 
302
310
  if (url.pathname === "/api/settings" && req.method === "GET") {
303
- let resolved: ReturnType<typeof resolveCodexRuntime>;
311
+ let resolved: ReturnType<typeof getCodexRuntimeSnapshot>;
304
312
  try {
305
- // Full alternative discovery (memoized) so newerAvailable warnings work.
306
- resolved = resolveCodexRuntime();
313
+ // Full alternative discovery so newerAvailable warnings work, served stale-while-
314
+ // revalidate: the sync resolver ran `codex --version` per candidate on this request and
315
+ // froze the whole proxy for ~0.6s every memo expiry while the dashboard polled here.
316
+ resolved = getCodexRuntimeSnapshot();
307
317
  } catch {
308
318
  resolved = {
309
319
  runtime: { command: "codex", version: null, source: "fallback" },
@@ -363,7 +373,8 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
363
373
  codexClientCompaction: config.codexClientCompaction === true,
364
374
  codexDesktopSwitches: describeCodexDesktopSwitches(config, await observedCodexDesktopSwitchApply()),
365
375
  compactionRouting: config.compactionRouting ?? null,
366
- startupHealth: await readStartupHealth(config),
376
+ compactionRecovery: config.compactionRecovery ?? null,
377
+ startupHealth: await readStartupHealthSnapshot(config),
367
378
  codexRuntime: {
368
379
  path: displayCodexRuntimePath(resolved.runtime.command),
369
380
  version: resolved.runtime.version,
@@ -455,6 +466,7 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
455
466
  codexDesktopAuthless?: unknown;
456
467
  codexClientCompaction?: unknown;
457
468
  compactionRouting?: unknown;
469
+ compactionRecovery?: unknown;
458
470
  };
459
471
  if (body.codexAutoStart === undefined
460
472
  && body.streamMode === undefined
@@ -467,8 +479,8 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
467
479
  && body.codexMainAccountHardLock === undefined
468
480
  && body.codexDesktopAuthless === undefined
469
481
  && body.codexClientCompaction === undefined
470
- && body.compactionRouting === undefined) {
471
- return jsonResponse({ error: "provide codexAutoStart, streamMode, appOwnedMemoryBudgetMb, codexAccountPickerEnabled, codexQuotaAutoRefresh, oauthOpenBrowser, ultraFastTier, fastRows, codexMainAccountHardLock, codexDesktopAuthless, codexClientCompaction, or compactionRouting" }, 400);
482
+ && body.compactionRouting === undefined && body.compactionRecovery === undefined) {
483
+ return jsonResponse({ error: "provide codexAutoStart, streamMode, appOwnedMemoryBudgetMb, codexAccountPickerEnabled, codexQuotaAutoRefresh, oauthOpenBrowser, ultraFastTier, fastRows, codexMainAccountHardLock, codexDesktopAuthless, codexClientCompaction, compactionRouting, or compactionRecovery" }, 400);
472
484
  }
473
485
  if (body.codexAutoStart !== undefined && typeof body.codexAutoStart !== "boolean") {
474
486
  return jsonResponse({ error: "codexAutoStart boolean is required" }, 400);
@@ -501,6 +513,8 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
501
513
  const compactionRouting = body.compactionRouting == null
502
514
  ? body.compactionRouting
503
515
  : compactionRoutingSchema.safeParse(body.compactionRouting);
516
+ const compactionRecovery = body.compactionRecovery == null ? body.compactionRecovery : compactionRecoverySchema.safeParse(body.compactionRecovery);
517
+ if (compactionRecovery != null && !compactionRecovery.success) return jsonResponse({ error: "compactionRecovery requires enabled, a model, and optional boolean allowDevinInvalidArgument" }, 400);
504
518
  if (compactionRouting != null && !compactionRouting.success) {
505
519
  return jsonResponse({ error: "compactionRouting requires a model, an optional valid reasoningEffort, and optional non-repeating triggers drawn from \"manual\" and \"auto\"" }, 400);
506
520
  }
@@ -533,7 +547,7 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
533
547
  )) {
534
548
  return jsonResponse({ error: `appOwnedMemoryBudgetMb must be an integer from ${MIN_APP_OWNED_MEMORY_BUDGET_MB} to ${MAX_APP_OWNED_MEMORY_BUDGET_MB}` }, 400);
535
549
  }
536
- const restoreCompactionRouting = captureConfigTopLevelRollback(config, ["compactionRouting"]);
550
+ const restoreCompactionRouting = captureConfigTopLevelRollback(config, ["compactionRouting", "compactionRecovery"]);
537
551
  const previousSettings = {
538
552
  codexAutoStart: config.codexAutoStart,
539
553
  hasCodexAutoStart: Object.hasOwn(config, "codexAutoStart"),
@@ -604,6 +618,8 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
604
618
  else if (body.codexClientCompaction === false) deleteConfigTopLevelKey(config, "codexClientCompaction");
605
619
  if (compactionRouting === null) deleteConfigTopLevelKey(config, "compactionRouting");
606
620
  else if (compactionRouting?.success) config.compactionRouting = compactionRouting.data;
621
+ if (compactionRecovery === null) deleteConfigTopLevelKey(config, "compactionRecovery");
622
+ else if (compactionRecovery?.success) config.compactionRecovery = compactionRecovery.data;
607
623
  if (quotaAutoRefreshChange) {
608
624
  const { id, window, enabled } = quotaAutoRefreshChange;
609
625
  const setting = { ...(config.codexQuotaAutoRefresh?.[id] ?? {}) };
@@ -705,9 +721,10 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
705
721
  codexClientCompaction: clientCompactionIsEnabled,
706
722
  codexDesktopSwitches,
707
723
  compactionRouting: config.compactionRouting ?? null,
724
+ compactionRecovery: config.compactionRecovery ?? null,
708
725
  codexMainAccountHardLock: isMainAccountHardLockEnabled(config),
709
726
  mainAccountHardLock: getMainAccountHardLockStatus(config),
710
- startupHealth: await readStartupHealth(config),
727
+ startupHealth: await readStartupHealthSnapshot(config),
711
728
  });
712
729
  }
713
730
 
@@ -154,6 +154,8 @@ export interface ManagementApiDeps {
154
154
  issueApiKey?: (config: OcxConfig, name: string) => IssuedApiKey;
155
155
  revokeApiKey?: (config: OcxConfig, id: string) => boolean;
156
156
  loadLinkCandidates?: () => Array<{ alias: string; source: "ssh_config" | "tailscale" }>;
157
+ /** The port this runtime listens on; a join is refused unless it is the configured port. */
158
+ liveListenPort?: () => number | undefined;
157
159
  now?: () => number;
158
160
  }
159
161