@bitkyc08/opencodex 2.82.0 → 2.84.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 (266) hide show
  1. package/gui/dist/assets/App-CZtqiGcw.js +56 -0
  2. package/gui/dist/assets/Tray-C9TGtwad.js +1 -0
  3. package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
  4. package/gui/dist/assets/index-CTm4l-HQ.js +94 -0
  5. package/gui/dist/assets/{quota-summary-DTBC8n7-.js → quota-summary-BbEeRfPY.js} +1 -1
  6. package/gui/dist/index.html +2 -2
  7. package/package.json +2 -2
  8. package/src/adapters/coding-agent/protocol.ts +15 -8
  9. package/src/adapters/cursor/catalog.ts +2 -2
  10. package/src/adapters/cursor/effort-map.ts +1 -1
  11. package/src/adapters/devin/live-models.ts +2 -2
  12. package/src/adapters/google-truncation.ts +22 -0
  13. package/src/adapters/google.ts +7 -3
  14. package/src/adapters/ollama-native.ts +47 -15
  15. package/src/adapters/openai-chat/messages.ts +8 -4
  16. package/src/adapters/openai-responses/empty-assistant.ts +32 -0
  17. package/src/adapters/openai-responses/passthrough.ts +5 -0
  18. package/src/adapters/openai-responses/plaintext-reasoning-notice.ts +85 -0
  19. package/src/adapters/openai-responses/reasoning.ts +11 -1
  20. package/src/adapters/openai-responses/request-strips.ts +10 -2
  21. package/src/adapters/qoder/adapter.ts +16 -14
  22. package/src/bridge/errors.ts +26 -1
  23. package/src/bridge.ts +1 -1
  24. package/src/claude/context-windows.ts +57 -4
  25. package/src/claude/desktop-picker.ts +1 -1
  26. package/src/claude/intercept/connect-proxy.ts +10 -3
  27. package/src/claude/intercept/picker-ca-persistence.ts +3 -1
  28. package/src/claude/intercept/picker-listener.ts +49 -1
  29. package/src/claude/intercept/settings-no-proxy.ts +146 -0
  30. package/src/claude/intercept/settings.ts +101 -9
  31. package/src/cli/account-api.ts +65 -0
  32. package/src/cli/account-auth.ts +44 -12
  33. package/src/cli/account.ts +41 -5
  34. package/src/cli/capabilities-access-remote.ts +8 -0
  35. package/src/cli/capabilities-base.ts +13 -1
  36. package/src/cli/catalog-command-result.ts +2 -1
  37. package/src/cli/claude.ts +15 -6
  38. package/src/cli/config-command.ts +8 -2
  39. package/src/cli/debug.ts +8 -0
  40. package/src/cli/dispatch.ts +16 -3
  41. package/src/cli/export-command.ts +14 -1
  42. package/src/cli/hub-disable.ts +99 -0
  43. package/src/cli/hub.ts +4 -2
  44. package/src/cli/index.ts +13 -4
  45. package/src/cli/link-child.ts +104 -0
  46. package/src/cli/link-enrollment.ts +96 -0
  47. package/src/cli/link.ts +39 -24
  48. package/src/cli/observe-stream.ts +6 -1
  49. package/src/cli/observe.ts +10 -2
  50. package/src/cli/provider.ts +12 -0
  51. package/src/cli/registry.ts +5 -4
  52. package/src/cli/resolve.ts +1 -1
  53. package/src/cli/runtime-api.ts +27 -2
  54. package/src/cli/status.ts +104 -20
  55. package/src/client/connect.ts +39 -4
  56. package/src/client/hub-client.ts +14 -7
  57. package/src/client/hub-state.ts +25 -14
  58. package/src/client/link-home.ts +248 -0
  59. package/src/client/link-management-relay.ts +61 -0
  60. package/src/client/link-relay.ts +6 -1
  61. package/src/client/link-status.ts +14 -0
  62. package/src/client/machine-api.ts +14 -7
  63. package/src/client/machine-listener.ts +29 -4
  64. package/src/clients/config-export/model-metadata.ts +16 -0
  65. package/src/clients/config-export/zcode-store.ts +46 -11
  66. package/src/clients/config-export/zcode.ts +3 -5
  67. package/src/clients/config-export.ts +47 -17
  68. package/src/clients/omo-role-models.ts +6 -3
  69. package/src/codex/agent-role-models.ts +26 -8
  70. package/src/codex/auth-api/account-list.ts +17 -2
  71. package/src/codex/auth-api/account-selection.ts +17 -7
  72. package/src/codex/auth-api/pool-mode-gate.ts +4 -5
  73. package/src/codex/auth-api/routes.ts +5 -1
  74. package/src/codex/auth-context.ts +8 -3
  75. package/src/codex/autostart-health.ts +3 -1
  76. package/src/codex/catalog/bundled.ts +5 -1
  77. package/src/codex/catalog/effort.ts +2 -2
  78. package/src/codex/catalog/metadata.ts +4 -2
  79. package/src/codex/catalog/native-models.ts +17 -3
  80. package/src/codex/catalog/provider-models.ts +25 -0
  81. package/src/codex/desktop-app/darwin.ts +1 -1
  82. package/src/codex/desktop-app/handoff.ts +1 -1
  83. package/src/codex/desktop-app/linux.ts +1 -1
  84. package/src/codex/desktop-app/lock.ts +1 -1
  85. package/src/codex/desktop-app/types.ts +1 -1
  86. package/src/codex/desktop-app/windows.ts +1 -1
  87. package/src/codex/desktop-app-restart.ts +1 -1
  88. package/src/codex/main-account.ts +6 -7
  89. package/src/codex/native-profile-startup.ts +4 -1
  90. package/src/codex/prompt-journal.ts +20 -2
  91. package/src/codex/prompt-layers/encoding.ts +49 -0
  92. package/src/codex/prompt-layers/import-source.ts +30 -0
  93. package/src/codex/prompt-layers/toml-edit.ts +269 -37
  94. package/src/codex/prompt-layers/toml-read.ts +52 -4
  95. package/src/codex/prompt-layers.ts +383 -20
  96. package/src/codex/prompt-lock-claim.ts +13 -3
  97. package/src/codex/prompt-lock-evidence.ts +3 -3
  98. package/src/codex/prompt-lock-owner.ts +14 -6
  99. package/src/codex/prompt-text-probe.ts +34 -5
  100. package/src/codex/quota.ts +13 -6
  101. package/src/codex/role-auto-assign.ts +2 -5
  102. package/src/codex/routing/active-account.ts +21 -0
  103. package/src/codex/routing.ts +1 -1
  104. package/src/codex/runtime.ts +196 -8
  105. package/src/codex/shim-overlay.ts +8 -4
  106. package/src/codex/shim.ts +1 -1
  107. package/src/combos/failover.ts +3 -0
  108. package/src/config/api-key-reconcile.ts +34 -0
  109. package/src/config/diagnostics.ts +16 -0
  110. package/src/config/feature-flags.ts +5 -0
  111. package/src/config/live-reconcile.ts +43 -29
  112. package/src/config/live-topology.ts +29 -0
  113. package/src/config/load-degrade.ts +2 -1
  114. package/src/config/paths.ts +3 -0
  115. package/src/config/salvage.ts +3 -1
  116. package/src/config/schema/config-schema.ts +2 -0
  117. package/src/generated/compatibility-version.json +373 -217
  118. package/src/grok/grpc-web.ts +23 -1
  119. package/src/grok/inject.ts +245 -221
  120. package/src/grok/reset-coupon-ledger.ts +293 -57
  121. package/src/grok/reset-coupons.ts +15 -6
  122. package/src/integrations/cursor-detect.ts +1 -1
  123. package/src/integrations/mutation-plan.ts +13 -7
  124. package/src/integrations/registry.ts +45 -11
  125. package/src/integrations/state.ts +19 -7
  126. package/src/integrations/target.ts +42 -5
  127. package/src/integrations/writer.ts +14 -6
  128. package/src/lib/app-owned-memory-stores.ts +4 -0
  129. package/src/lib/app-owned-memory.ts +11 -0
  130. package/src/lib/errors.ts +25 -0
  131. package/src/lib/json-byte-size.ts +31 -0
  132. package/src/lib/optional-shutdown-hooks.ts +35 -0
  133. package/src/lib/outbound-credential-registry.ts +210 -0
  134. package/src/lib/passive-read.ts +35 -0
  135. package/src/lib/redact.ts +2 -2
  136. package/src/lib/shadow-call.ts +44 -5
  137. package/src/link/codex-settings.ts +155 -0
  138. package/src/link/management-routes.ts +89 -0
  139. package/src/link/remote-probe.ts +38 -0
  140. package/src/link/ssh-argv.ts +26 -5
  141. package/src/link/ssh-runner.ts +58 -27
  142. package/src/link/status-projection.ts +22 -4
  143. package/src/link/supervisor.ts +144 -49
  144. package/src/link/tunnel-state.ts +11 -4
  145. package/src/oauth/anthropic-continuity.ts +4 -3
  146. package/src/oauth/anthropic-identity.ts +13 -2
  147. package/src/oauth/anthropic-model-quota.ts +13 -1
  148. package/src/oauth/anthropic-routing.ts +230 -23
  149. package/src/oauth/anthropic.ts +18 -4
  150. package/src/oauth/google-antigravity.ts +24 -7
  151. package/src/oauth/index.ts +25 -16
  152. package/src/oauth/kiro-account-identity.ts +106 -0
  153. package/src/oauth/kiro.ts +48 -5
  154. package/src/oauth/meta-muse-device.ts +1 -1
  155. package/src/oauth/plan.ts +29 -0
  156. package/src/oauth/store.ts +40 -3
  157. package/src/oauth/types.ts +3 -1
  158. package/src/plugins/loader.ts +11 -0
  159. package/src/providers/anthropic-billing-ownership.ts +74 -0
  160. package/src/providers/anthropic-billing-recovery.ts +31 -0
  161. package/src/providers/anthropic-billing-refusal.ts +55 -0
  162. package/src/providers/anthropic-fast.ts +1 -1
  163. package/src/providers/anthropic-reset-admission.ts +62 -0
  164. package/src/providers/anthropic-reset-grant-ledger.ts +29 -7
  165. package/src/providers/api-key-resolve.ts +17 -2
  166. package/src/providers/devin-cli-authmode-migration.ts +1 -1
  167. package/src/providers/devin-provider-merge-migration.ts +1 -1
  168. package/src/providers/github-copilot-auto.ts +169 -0
  169. package/src/providers/key-failover.ts +52 -5
  170. package/src/providers/model-presets.ts +1 -1
  171. package/src/providers/model-rename-startup.ts +5 -3
  172. package/src/providers/muse-subscription-usage.ts +1 -1
  173. package/src/providers/quota/account-cache.ts +11 -0
  174. package/src/providers/quota/anthropic-account-quota.ts +4 -2
  175. package/src/providers/quota/report-cache.ts +14 -0
  176. package/src/providers/quota/vendor-probes-oauth.ts +3 -3
  177. package/src/providers/quota.ts +4 -1
  178. package/src/providers/registry/entries-core.ts +20 -20
  179. package/src/providers/registry/entries-extended.ts +4 -1
  180. package/src/providers/registry/model-seeds.ts +20 -3
  181. package/src/providers/request-pacing.ts +9 -0
  182. package/src/providers/stale-no-vision-seed-migration.ts +72 -0
  183. package/src/providers/xai-fast-model.ts +1 -1
  184. package/src/responses/compaction.ts +14 -0
  185. package/src/responses/parser.ts +7 -1
  186. package/src/server/auth-cors.ts +42 -0
  187. package/src/server/configured-credentials.ts +194 -0
  188. package/src/server/inbound-body-admission.ts +24 -7
  189. package/src/server/index/link-listener.ts +30 -17
  190. package/src/server/index/link-management.ts +58 -0
  191. package/src/server/index/optional-listeners.ts +5 -1
  192. package/src/server/index/serve-options.ts +21 -2
  193. package/src/server/index.ts +1 -1
  194. package/src/server/lifecycle.ts +33 -5
  195. package/src/server/management/agent-settings-routes.ts +28 -10
  196. package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
  197. package/src/server/management/codex-agent-role-routes.ts +19 -6
  198. package/src/server/management/codex-prompt-routes.ts +136 -5
  199. package/src/server/management/codex-role-auto-assign.ts +1 -1
  200. package/src/server/management/grok-coupon-routes.ts +333 -57
  201. package/src/server/management/link-enrollment.ts +192 -0
  202. package/src/server/management/link-route-state.ts +185 -0
  203. package/src/server/management/link-routes.ts +92 -390
  204. package/src/server/management/model-rows.ts +3 -1
  205. package/src/server/management/provider-overwrite-carry.ts +10 -0
  206. package/src/server/management/provider-routes.ts +1 -0
  207. package/src/server/management/route-registry.ts +7 -5
  208. package/src/server/management/shared.ts +3 -0
  209. package/src/server/management-api.ts +3 -1
  210. package/src/server/messages-native.ts +68 -42
  211. package/src/server/relay-eager.ts +4 -0
  212. package/src/server/relay.ts +18 -7
  213. package/src/server/request-log-failure-attribution.ts +3 -0
  214. package/src/server/request-log-filter.ts +6 -1
  215. package/src/server/request-log-terminal-status.ts +243 -0
  216. package/src/server/request-log.ts +15 -118
  217. package/src/server/responses/adapter-continuation.ts +9 -1
  218. package/src/server/responses/adapter-dispatch.ts +293 -81
  219. package/src/server/responses/code-mode-collab-surface.ts +205 -0
  220. package/src/server/responses/codex-ws-exchange.ts +147 -6
  221. package/src/server/responses/codex-ws-wire.ts +203 -0
  222. package/src/server/responses/collaboration.ts +36 -7
  223. package/src/server/responses/compact.ts +129 -50
  224. package/src/server/responses/context-overflow.ts +13 -1
  225. package/src/server/responses/core-codex-account.ts +105 -1
  226. package/src/server/responses/core-combo-failure.ts +14 -0
  227. package/src/server/responses/core-combo.ts +7 -2
  228. package/src/server/responses/core-errors.ts +29 -1
  229. package/src/server/responses/core-opaque-recovery.ts +5 -5
  230. package/src/server/responses/core-options.ts +2 -0
  231. package/src/server/responses/core.ts +6 -6
  232. package/src/server/responses/deferred-collab-surface.ts +158 -0
  233. package/src/server/responses/deferred-send-reporter.ts +25 -0
  234. package/src/server/responses/empty-completion-guard.ts +18 -2
  235. package/src/server/responses/fetch-helpers.ts +12 -1
  236. package/src/server/responses/jev-model-invoke.ts +1 -1
  237. package/src/server/responses/passthrough-delivery.ts +4 -1
  238. package/src/server/responses/passthrough-dispatch.ts +130 -64
  239. package/src/server/responses/passthrough-execution.ts +3 -3
  240. package/src/server/responses/request-prepare.ts +6 -4
  241. package/src/server/responses/request-transport.ts +131 -13
  242. package/src/server/responses/run-turn-execution.ts +13 -5
  243. package/src/server/responses/sidecar-execution.ts +16 -2
  244. package/src/server/responses/spend-storage-error.ts +33 -0
  245. package/src/server/responses/ws-upstream.ts +9 -1
  246. package/src/server/startup-action-control.ts +22 -2
  247. package/src/server/startup-health-cache.ts +4 -0
  248. package/src/server/system-env-shell.ts +7 -2
  249. package/src/server/system-env.ts +11 -1
  250. package/src/service/cli.ts +5 -1
  251. package/src/service/desktop-startup.ts +147 -34
  252. package/src/service/desktop-supervision.d.mts +27 -1
  253. package/src/service/desktop-supervision.mjs +111 -5
  254. package/src/service/health-sentinel-launch.ts +176 -0
  255. package/src/service/health-sentinel-main.ts +447 -0
  256. package/src/service/health-sentinel-policy.ts +241 -0
  257. package/src/service/health-sentinel-probe.ts +54 -0
  258. package/src/service/health-sentinel-windows.ts +87 -0
  259. package/src/service/linux-linger.ts +20 -0
  260. package/src/types/config.ts +5 -0
  261. package/src/types/request.ts +5 -0
  262. package/src/usage/expected-prices.ts +5 -1
  263. package/src/usage/log.ts +19 -1
  264. package/gui/dist/assets/App-Ds_qL7o8.js +0 -54
  265. package/gui/dist/assets/Tray-BqHF5Trd.js +0 -1
  266. package/gui/dist/assets/index-D3FBCNiP.js +0 -94
@@ -1,4 +1,5 @@
1
1
  import { SendBudgetExhaustedError } from "../../lib/upstream-retry";
2
+ import { anthropicBillingPhysicalFetch } from "../../providers/anthropic-billing-ownership";
2
3
  import { rebindPhysicalSend } from "../../lib/request-execution-budget";
3
4
  import { anthropicModelQuotaFor } from "../../oauth/anthropic-model-quota";
4
5
  import { anthropicRatePolicyFor } from "../../oauth/anthropic-rate-limit-policy";
@@ -53,6 +54,9 @@ import { classifyModelFamilyForQuota } from "../../oauth/account-quota-rank";
53
54
  import { expandInferenceOAuthSendBudget } from "../inference/context";
54
55
  import { stampOAuthAccountLabel, usesApiKeyAccount } from "../../providers/label";
55
56
  import { resolveProviderTransport } from "../../providers/xai-transport";
57
+ import { captureRouteStaticPolicy } from "../../router";
58
+ import { CopilotAutoHttpError, resolveCopilotAuto } from "../../providers/github-copilot-auto";
59
+ import { renameRoutedIdentityInContext } from "../../adapters/identity";
56
60
  import { resolveCopilotApiBaseUrl } from "../../oauth/github-copilot";
57
61
  import {
58
62
  providerApiKeySelectionIsCurrent,
@@ -84,9 +88,16 @@ import {
84
88
  import type { AttemptRecoveryKind } from "../../usage/log";
85
89
  import { bindAttemptDeliveryRecorder } from "../../usage/attempt-delivery";
86
90
  import { resolvePassiveRouteSubjectId } from "../passive-route-linker";
87
- import { clientCancelledResponse } from "./core-errors";
91
+ import { anthropicPoolCooldownResponse, clientCancelledResponse } from "./core-errors";
88
92
  import { acquireAccountLease, KIRO_ACCOUNT_WAIT_MS } from "../../oauth/kiro-account-load";
89
93
 
94
+ /** Return a renewed Chat wire to the native owner before sending inference. */
95
+ export class CopilotAutoAdapterHandoff extends Error {
96
+ constructor(readonly adapter: ProviderAdapter) {
97
+ super("Copilot Auto dispatch requires the Chat adapter");
98
+ }
99
+ }
100
+
90
101
  /** Owns live credential selection and adapter bindings for one request. */
91
102
  export async function prepareResponsesTransport(
92
103
  requestContext: Pick<ResponsesRequestContext, "config" | "logCtx" | "options" | "req">,
@@ -160,6 +171,8 @@ export async function prepareResponsesTransport(
160
171
  || route.providerName === "devin"
161
172
  ) && route.provider.authMode === "oauth";
162
173
  let sentOAuthSnapshot: OAuthAccessSnapshot | undefined;
174
+ // One inference-401 replay belongs to this request, even when Auto changes exchange wire.
175
+ let oauth401ReplayAttempted = false;
163
176
  let replayOAuthCredentialSnapshot: Pick<OAuthAccessSnapshot, "accountId" | "generation"> | undefined;
164
177
  let anthropicPoolAccountId: string | null = null;
165
178
  let anthropicPoolFailovers = 0;
@@ -281,6 +294,84 @@ export async function prepareResponsesTransport(
281
294
  * the case the fence exists for. Stays 0 for every provider without a passive quota.
282
295
  */
283
296
  let passiveQuotaWriterGeneration = 0;
297
+ const copilotRequestedModel = route.modelId;
298
+ /** Project only bounded Copilot refusal metadata; upstream bodies and session tokens never reach the client. */
299
+ const copilotRefusalResponse = (error: unknown): Response | undefined => {
300
+ if (!(error instanceof CopilotAutoHttpError) || ![401, 403, 429].includes(error.status)) return;
301
+ const response = formatErrorResponse(error.status, error.status === 429 ? "rate_limit_error" : "authentication_error",
302
+ error.message);
303
+ if (error.retryAfter) response.headers.set("retry-after", error.retryAfter);
304
+ return response;
305
+ };
306
+ let copilotSessionExpiresAt = Infinity;
307
+ let copilotNegotiationRefreshed = false;
308
+ let copilotAutoAdapter: OcxProviderConfig["adapter"] | undefined;
309
+ /** The operator-configured wire, captured before the first Auto negotiation can rewrite route.provider. */
310
+ let copilotConfiguredWire: { adapter: OcxProviderConfig["adapter"]; responsesPath?: string;
311
+ chatCompletionsPath?: string; apiVersion?: string } | undefined;
312
+ /** Start every negotiation from the configured wire, so a previous credential's Auto adapter,
313
+ * endpoint and session headers never carry over to a credential that keeps the named model. */
314
+ const copilotNegotiationBase = (provider: OcxProviderConfig): OcxProviderConfig => {
315
+ if (!copilotConfiguredWire) return provider;
316
+ const headers = new Headers(provider.headers);
317
+ headers.delete("Copilot-Session-Token");
318
+ if (copilotConfiguredWire.apiVersion === undefined) headers.delete("X-GitHub-Api-Version");
319
+ else headers.set("X-GitHub-Api-Version", copilotConfiguredWire.apiVersion);
320
+ const base: OcxProviderConfig = { ...provider, adapter: copilotConfiguredWire.adapter,
321
+ headers: Object.fromEntries(headers) };
322
+ if (copilotConfiguredWire.responsesPath === undefined) delete base.responsesPath;
323
+ else base.responsesPath = copilotConfiguredWire.responsesPath;
324
+ if (copilotConfiguredWire.chatCompletionsPath === undefined) delete base.chatCompletionsPath;
325
+ else base.chatCompletionsPath = copilotConfiguredWire.chatCompletionsPath;
326
+ return base;
327
+ };
328
+ /** Resolve the request's ephemeral wire selection and recover at most one OAuth negotiation 401. */
329
+ const resolveCopilotSelection = async (requestParsed: OcxParsedRequest, insideDispatch = false): Promise<void> => {
330
+ if (route.providerName !== "github-copilot" || route.provider.authMode === "forward") return;
331
+ copilotConfiguredWire ??= {
332
+ adapter: route.provider.adapter,
333
+ responsesPath: route.provider.responsesPath,
334
+ chatCompletionsPath: route.provider.chatCompletionsPath,
335
+ apiVersion: new Headers(route.provider.headers).get("X-GitHub-Api-Version") ?? undefined,
336
+ };
337
+ /** Fence control-plane sends to the captured credential; reuse an already-held dispatch concurrency lease. */
338
+ const negotiate = () => {
339
+ const binding: DispatchBinding | undefined = oauthSelection && servingOAuthSnapshot
340
+ ? { kind: "oauth", selection: { ...oauthSelection }, snapshot: servingOAuthSnapshot }
341
+ : { kind: "api-key", provider: { ...route.provider } };
342
+ return resolveCopilotAuto(copilotNegotiationBase(route.provider), copilotRequestedModel, requestParsed,
343
+ options.abortSignal ?? req.signal, /** Fence each negotiation send against the captured credential binding before touching the network. */
344
+ () => selectionIsCurrent(binding), !insideDispatch);
345
+ };
346
+ let selected: Awaited<ReturnType<typeof resolveCopilotAuto>>;
347
+ try { selected = await negotiate(); }
348
+ catch (error) {
349
+ if (!(error instanceof CopilotAutoHttpError) || error.status !== 401 || copilotNegotiationRefreshed
350
+ || oauth401ReplayAttempted || route.provider.authMode !== "oauth" || !servingOAuthSnapshot) throw error;
351
+ copilotNegotiationRefreshed = true;
352
+ const refreshed = await refreshResolvedOAuthSelection(servingOAuthSnapshot);
353
+ route.provider = resolveProviderTransport(route.providerName,
354
+ { ...route.provider, apiKey: refreshed.accessToken }, parsed.options.promptCacheKey,
355
+ resolveCopilotApiBaseUrl(refreshed.apiBaseUrl));
356
+ sentOAuthSnapshot = refreshed;
357
+ replayOAuthCredentialSnapshot = { accountId: refreshed.accountId, generation: refreshed.generation };
358
+ selected = await negotiate();
359
+ }
360
+ copilotSessionExpiresAt = selected.expiresAt ?? Infinity;
361
+ route.provider = selected.provider;
362
+ route.modelId = selected.modelId;
363
+ route.staticPolicy = captureRouteStaticPolicy(route.providerName, route.modelId, route.provider,
364
+ route.staticPolicy.effectiveAlias, inboundWire);
365
+ copilotAutoAdapter = selected.auto ? selected.provider.adapter : undefined;
366
+ for (const target of new Set([parsed, requestParsed])) {
367
+ target.modelId = selected.modelId;
368
+ if (target._rawBody && typeof target._rawBody === "object")
369
+ (target._rawBody as { model?: string }).model = selected.modelId;
370
+ target.context = renameRoutedIdentityInContext(target.context, selected.modelId);
371
+ }
372
+ logCtx.model = selected.modelId;
373
+ if (logCtx.activeAttempt) logCtx.activeAttempt.model = selected.modelId;
374
+ };
284
375
  /**
285
376
  * Apply a rotated account's FULL credential snapshot to the live route (#2568d).
286
377
  *
@@ -306,6 +397,7 @@ export async function prepareResponsesTransport(
306
397
  const applyFailoverSnapshot = async (
307
398
  snapshot: OAuthAccessSnapshot,
308
399
  retryParsed: OcxParsedRequest = parsed,
400
+ insideDispatch = false,
309
401
  ): Promise<OAuthAccessSnapshot | null> => {
310
402
  requireAnthropicAdmission();
311
403
  if (snapshot.provider !== route.providerName) return null;
@@ -342,6 +434,9 @@ export async function prepareResponsesTransport(
342
434
  }
343
435
  if (snapshot.projectId) rotatedProvider = { ...rotatedProvider, project: snapshot.projectId };
344
436
  route.provider = rotatedProvider;
437
+ await resolveCopilotSelection(retryParsed, insideDispatch);
438
+ // Negotiation itself may have recovered a 401 with a newer committed credential.
439
+ if (route.providerName === "github-copilot" && servingOAuthSnapshot) snapshot = servingOAuthSnapshot;
345
440
  if (route.providerName === "kiro") {
346
441
  const kiroContext = { ...(snapshot.kiro ?? {}) };
347
442
  // Terminal-guard continuations are rebuilt from a shallow clone. Updating only the
@@ -411,7 +506,9 @@ export async function prepareResponsesTransport(
411
506
  && (anthropicInstance === undefined || !anthropicRouting!.getAnthropicAccountHealthSnapshot(binding.snapshot.accountId) && !anthropicRatePolicy!.anthropicRatePauseUntil(binding.snapshot.accountId) && !anthropicQuota!.anthropicFamilyRejected(binding.snapshot.accountId, route.modelId))
412
507
  && credentialGeneration(row.credential) === binding.snapshot.generation;
413
508
  };
509
+ /** Bind the negotiated wire adapter and its built requests to the serving credential's authority. */
414
510
  const resolveSelectionAdapter = (provider: OcxProviderConfig, retention = config.cacheRetention): ProviderAdapter => {
511
+ if (copilotAutoAdapter) provider = { ...provider, adapter: copilotAutoAdapter };
415
512
  const resolved = resolveAdapter(provider, retention, route.providerName);
416
513
  if (route.provider.authMode === "forward") return resolved;
417
514
  const binding: DispatchBinding | undefined = route.provider.authMode === "oauth"
@@ -468,6 +565,7 @@ export async function prepareResponsesTransport(
468
565
  }
469
566
  return resolved;
470
567
  };
568
+ /** Rebuild a stale queued selection while retaining the caller's dispatch lease and renewing Auto metadata. */
471
569
  const refreshDispatchAdapter = async (requestParsed: OcxParsedRequest): Promise<ProviderAdapter> => {
472
570
  requireAnthropicAdmission();
473
571
  if (route.provider.authMode === "oauth") {
@@ -479,7 +577,7 @@ export async function prepareResponsesTransport(
479
577
  const accountId = await anthropicRouting!.resolveAnthropicDispatchAccountId(config, anthropicSessionKey, anthropicRouteDecision, route.modelId);
480
578
  candidate = await anthropicRouting!.getAnthropicPoolAccessSnapshot(accountId);
481
579
  }
482
- if (!candidate || !await applyFailoverSnapshot(candidate, requestParsed)) {
580
+ if (!candidate || !await applyFailoverSnapshot(candidate, requestParsed, true)) {
483
581
  if (anthropicInstance !== undefined) await anthropicRouting!.resolveAnthropicDispatchAccountId(config, anthropicSessionKey, anthropicRouteDecision, route.modelId);
484
582
  throw new Error("OAuth account selection changed before dispatch");
485
583
  }
@@ -487,6 +585,7 @@ export async function prepareResponsesTransport(
487
585
  const current = resolveCurrentProviderApiKeyTransport(config, route.providerName, route.provider);
488
586
  if (!current) throw new Error("API key selection is unavailable before dispatch");
489
587
  route.provider = current;
588
+ await resolveCopilotSelection(requestParsed, true);
490
589
  }
491
590
  adapter = activeAdapter = runTurnAdapter = resolveSelectionAdapter(
492
591
  resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
@@ -552,13 +651,14 @@ export async function prepareResponsesTransport(
552
651
  throw new Error("Account selection changed repeatedly before turn dispatch");
553
652
  } finally { producer?.close(); }
554
653
  };
555
- const oauthDispatch = (wireRequest: AdapterRequest, requestParsed = parsed): ProviderFetchOptions["dispatchOverride"] => {
654
+ /** Fence the physical send, with bounded credential/session rebuilds and safe negotiated refusals. */
655
+ const oauthDispatch = (wireRequest: AdapterRequest, requestParsed = parsed, owner: "adapter" | "passthrough" = "adapter"): ProviderFetchOptions["dispatchOverride"] => {
556
656
  if (route.provider.authMode === "forward") return undefined;
557
- return async (input, init, execute) => {
657
+ return /** Dispatch with the current binding and session, rebuilding at most three times before returning a safe refusal. */ async (input, init, execute) => {
558
658
  let destination = input;
559
659
  let dispatchInit = init;
560
660
  for (let attempt = 0; attempt < 3; attempt++) {
561
- if (selectionIsCurrent(requestBindings.get(wireRequest))) {
661
+ if (selectionIsCurrent(requestBindings.get(wireRequest)) && copilotSessionExpiresAt > Date.now() + 1000) {
562
662
  const fetchImpl = (route.provider as OcxProviderConfig & { fetch?: typeof globalThis.fetch }).fetch ?? execute;
563
663
  const binding = requestBindings.get(wireRequest);
564
664
  const snapshot = anthropicInstance !== undefined && binding?.kind === "oauth"
@@ -598,7 +698,7 @@ export async function prepareResponsesTransport(
598
698
  }
599
699
  commitKeyAttemptSend();
600
700
  response = await sendWithConnectionPolicy(
601
- fetchImpl,
701
+ anthropicBillingPhysicalFetch(fetchImpl, route.providerName, route.provider),
602
702
  destination,
603
703
  { ...dispatchInit, redirect: "manual" },
604
704
  { providerName: route.providerName, provider: route.provider },
@@ -622,7 +722,16 @@ export async function prepareResponsesTransport(
622
722
  if (!response.ok) await recordKeyAttemptFailure(logCtx, response, dispatchInit.signal ?? options.abortSignal);
623
723
  return response;
624
724
  }
625
- const nextAdapter = await refreshDispatchAdapter(requestParsed);
725
+ let nextAdapter: ProviderAdapter;
726
+ try { nextAdapter = await refreshDispatchAdapter(requestParsed); }
727
+ catch (error) {
728
+ const refusal = copilotRefusalResponse(error);
729
+ if (refusal) return refusal;
730
+ throw error;
731
+ }
732
+ if (owner === "passthrough" && route.providerName === "github-copilot" && nextAdapter.name === "openai-chat") {
733
+ throw new CopilotAutoAdapterHandoff(nextAdapter);
734
+ }
626
735
  // Rebind before rebuilding: the rebuild's bridged-search restore and continuation
627
736
  // restore key on the serving identity, which must be the refreshed route's, not the
628
737
  // credential whose selection just lapsed.
@@ -676,12 +785,9 @@ export async function prepareResponsesTransport(
676
785
  // Route names may resemble account IDs; log only the matched rule position.
677
786
  if (anthropicRouteDecision) console.warn(`[${anthropicInstance}-pool] route:#${anthropicRouteDecision.position} ${selection.reason}; answering locally`);
678
787
  if (selection.reason === "all-cooled") {
679
- const retryAfterSec = anthropicRouting!.getAnthropicPoolRetryAfterSeconds(Date.now(), anthropicRouteDecision, route.modelId);
680
- return formatErrorResponse(
681
- 429,
682
- "rate_limit_error",
683
- anthropicRouteDecision ? "Anthropic OAuth accounts for this model route are temporarily rate-limited" : "All Anthropic OAuth accounts are temporarily rate-limited",
684
- retryAfterSec !== null ? { retryAfter: String(retryAfterSec) } : undefined,
788
+ return anthropicPoolCooldownResponse(
789
+ anthropicRouting!.anthropicPoolCooldownError(Date.now(), anthropicRouteDecision, route.modelId),
790
+ { comboAttempt: options.comboAttempt, logCtx },
685
791
  );
686
792
  }
687
793
  return formatErrorResponse(401, "authentication_error", anthropicRouteDecision ? "No eligible Anthropic OAuth account for this model route" : "No eligible Anthropic OAuth account available");
@@ -876,7 +982,15 @@ export async function prepareResponsesTransport(
876
982
  ? resolveCopilotApiBaseUrl(sentOAuthSnapshot?.apiBaseUrl)
877
983
  : undefined,
878
984
  );
985
+ try { await resolveCopilotSelection(parsed); }
986
+ catch (error) {
987
+ if ((options.abortSignal ?? req.signal).aborted) return clientCancelledResponse();
988
+ const refusal = copilotRefusalResponse(error);
989
+ if (refusal) return refusal;
990
+ return formatErrorResponse(502, "provider_error", "GitHub Copilot Auto negotiation failed; retry or check your Copilot account permissions.");
991
+ }
879
992
  let adapterProvider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy);
993
+ if (copilotAutoAdapter) adapterProvider = { ...adapterProvider, adapter: copilotAutoAdapter };
880
994
  const stripClaudeMainAuth = options.stripClaudeMainAuthForNoncanonicalForward === true
881
995
  && !isCanonicalOpenAiForwardProvider(adapterProvider)
882
996
  && ((adapterProvider.adapter === "openai-responses" && adapterProvider.authMode === "forward")
@@ -1008,6 +1122,8 @@ export async function prepareResponsesTransport(
1008
1122
  set genericFailovers(value: typeof genericFailovers) {
1009
1123
  genericFailovers = value;
1010
1124
  },
1125
+ get oauth401ReplayAttempted(): boolean { return oauth401ReplayAttempted; },
1126
+ set oauth401ReplayAttempted(value: boolean) { oauth401ReplayAttempted = value; },
1011
1127
  get adapter(): ProviderAdapter {
1012
1128
  return adapter;
1013
1129
  },
@@ -1065,6 +1181,8 @@ export async function prepareResponsesTransport(
1065
1181
  applyFailoverSnapshot,
1066
1182
  selectionIsCurrent,
1067
1183
  resolveSelectionAdapter,
1184
+ resolveCopilotSelection,
1185
+ copilotRefusalResponse,
1068
1186
  refreshRunTurnAdapter,
1069
1187
  oauthDispatch,
1070
1188
  noteRoutedAttemptSend,
@@ -1,3 +1,4 @@
1
+ import { mapSpendLedgerStorageError } from "./spend-storage-error";
1
2
  import type { ResponsesRequestContext, ResponsesAdmissionState } from "./core-options";
2
3
  import type { PreparedResponsesRequest } from "./request-prepare";
3
4
  import type { ResponsesTransport } from "./request-transport";
@@ -20,7 +21,7 @@ import type { AttemptRecoveryKind } from "../../usage/log";
20
21
  import { providerFetch } from "./fetch-helpers";
21
22
  import { normalizeLogConversationId } from "../request-log-conversation";
22
23
  import { normalizeDeclaredToolName, type AdapterEvent, type OcxProviderContinuationState } from "../../types";
23
- import { adapterFailureFromMessage, SEND_BUDGET_EXHAUSTED_CODE } from "../../lib/errors";
24
+ import { adapterFailureFromMessage, SEND_BUDGET_EXHAUSTED_CODE, SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE, type OcxErrorPayload } from "../../lib/errors";
24
25
  import { SendBudgetExhaustedError, markResponseNonReplayable } from "../../lib/upstream-retry";
25
26
  import {
26
27
  hasEligibleGenericOAuthFailoverTarget,
@@ -315,7 +316,8 @@ export async function executeResponsesRunTurn(
315
316
  Object.assign(parsed, routeState);
316
317
  }
317
318
  } catch (err) {
318
- emit(err instanceof RequestPacingQueueOverloadError
319
+ const storageRefusal = mapSpendLedgerStorageError(err, logCtx);
320
+ emit(storageRefusal ? { type: "error", ...storageRefusal } : err instanceof RequestPacingQueueOverloadError
319
321
  ? {
320
322
  type: "error",
321
323
  status: 429,
@@ -713,7 +715,8 @@ export async function executeResponsesRunTurn(
713
715
  runTurnAbort.abort();
714
716
  queue.close();
715
717
  const message = preflight.error?.message ?? "Adapter ended before producing a response";
716
- const failure = formatErrorResponse(502, "upstream_error", redactSecretString(message));
718
+ const failure = formatErrorResponse(preflight.error?.code === SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE ? 503 : 502,
719
+ preflight.error?.code === SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE ? SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE : "upstream_error", redactSecretString(message));
717
720
  // A replay-unsafe heartbeat means the adapter already ran a local side effect, so the
718
721
  // combo must not send this turn to another target: the failure stays with this child.
719
722
  if (preflight.replayUnsafe) markResponseNonReplayable(failure);
@@ -912,7 +915,9 @@ export async function executeResponsesRunTurn(
912
915
  const message = classifiedError?.message ?? (firstMeaningful?.type === "error"
913
916
  ? firstMeaningful.message
914
917
  : "Adapter ended before producing a response");
915
- const failure = formatErrorResponse(502, "upstream_error", redactSecretString(message));
918
+ const storageRefusal = firstMeaningful?.type === "error" && firstMeaningful.code === SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE;
919
+ const failure = formatErrorResponse(storageRefusal ? 503 : 502,
920
+ storageRefusal ? SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE : "upstream_error", redactSecretString(message));
916
921
  if (replayUnsafe) markResponseNonReplayable(failure);
917
922
  return failure;
918
923
  }
@@ -956,7 +961,10 @@ export async function executeResponsesRunTurn(
956
961
  commitReasoningReplayServingRoute();
957
962
  }
958
963
  notifyResponseComplete(json);
959
- return new Response(JSON.stringify(json), { headers: { "Content-Type": "application/json" } });
964
+ return new Response(JSON.stringify(json), {
965
+ status: (json.error as OcxErrorPayload | undefined)?.code === SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE ? 503 : 200,
966
+ headers: { "Content-Type": "application/json" },
967
+ });
960
968
  } catch (error) {
961
969
  if (!isTranslatorBudgetExceededError(error)) throw error;
962
970
  runTurnAbort.abort();
@@ -32,7 +32,7 @@ import { classifyKiroRefusal } from "../../adapters/kiro-refusal";
32
32
  import { isNonReplayableResponse } from "../../lib/upstream-retry";
33
33
  import { noteKiroMonthlyRefusal, noteKiroServedSuccess } from "../../providers/kiro-usage";
34
34
  import { persistKiroAccountState } from "../../providers/kiro-account-state-disk";
35
- import { readDisplaySafeErrorText } from "./core-errors";
35
+ import { anthropicPoolCooldownResponse, readDisplaySafeErrorText } from "./core-errors";
36
36
  import {
37
37
  ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST,
38
38
  anthropicRoutingFor,
@@ -79,6 +79,7 @@ export async function executeResponsesSidecars(
79
79
  | "anthropicSessionKey"
80
80
  | "commitResolvedOAuthSelection"
81
81
  | "resolveSelectionAdapter"
82
+ | "resolveCopilotSelection"
82
83
  | "oauthDispatch"
83
84
  | "noteRoutedAttemptSend"
84
85
  | "bindKeyUsageFromBridge"
@@ -103,6 +104,7 @@ export async function executeResponsesSidecars(
103
104
  anthropicSessionKey,
104
105
  commitResolvedOAuthSelection,
105
106
  resolveSelectionAdapter,
107
+ resolveCopilotSelection,
106
108
  oauthDispatch,
107
109
  } = transportState;
108
110
  const {
@@ -196,6 +198,7 @@ export async function executeResponsesSidecars(
196
198
  // A single request may rotate once for a verification refusal. Keep this separate from the
197
199
  // broader OAuth failover count: a sidecar turn can also spend that count on rate-limit recovery.
198
200
  let antigravityValidationRotationAttempted = false;
201
+ /** Admit a replacement credential, or leave the loop's original refusal authoritative. */
199
202
  const rotateSidecarProviderOn429 = async (
200
203
  retryAfter: string | null,
201
204
  responseHeaders?: Headers,
@@ -280,6 +283,8 @@ export async function executeResponsesSidecars(
280
283
  // `applyFailoverSnapshot` has already rebound the route to the sibling identity/project.
281
284
  } else if (rotated) {
282
285
  route.provider = rotated;
286
+ try { await resolveCopilotSelection(retryParsed ?? parsed); }
287
+ catch { return null; } // Keep the sidecar's original refusal when replacement negotiation fails.
283
288
  } else if (
284
289
  // A POSITIVE gate, not an early return. An early `return null` here made every later arm
285
290
  // unreachable: Anthropic never has a genericFailoverAccountId (isGenericFailoverProvider
@@ -550,7 +555,7 @@ export async function executeResponsesSidecars(
550
555
  modelId: route.modelId,
551
556
  ...sidecarBudget.fetchOptions(transportState.adapter),
552
557
  })(input, init)) as typeof globalThis.fetch;
553
- const wsResponse = await runWithWebSearch({
558
+ const loopResponse = await runWithWebSearch({
554
559
  parsed, adapter: transportState.adapter,
555
560
  onIterationEnd: sidecarBudget.releaseUnsentHop,
556
561
  fetchForRequest: (request, iterParsed, adapter) => providerFetch(route.provider, options.codexWsRuntimeIdentity, {
@@ -617,6 +622,15 @@ export async function executeResponsesSidecars(
617
622
  notifyResponseComplete(response);
618
623
  },
619
624
  });
625
+ // A routed Anthropic model whose last pool account refused on spent quota fails the loop's
626
+ // first iteration with a generic 429 before any search runs. Answer the usage limit instead,
627
+ // as the main path does (#6991), so Codex stops rather than retrying the whole turn.
628
+ const poolUsageLimit = loopResponse.status === 429 && anthropicInstance && route.provider.authMode === "oauth"
629
+ && !options.comboAttempt
630
+ ? anthropicRoutingFor(anthropicInstance).anthropicPoolUsageLimitAfterRefusal(Date.now(), transportState.anthropicRouteDecision, route.modelId)
631
+ : null;
632
+ if (poolUsageLimit) void loopResponse.body?.cancel().catch(() => {});
633
+ const wsResponse = poolUsageLimit ? anthropicPoolCooldownResponse(poolUsageLimit, { logCtx }) : loopResponse;
620
634
  // Register the sidecar stream as an active turn so drainAndShutdown waits for (or aborts)
621
635
  // in-flight web-search turns instead of skipping them during graceful shutdown.
622
636
  if (wsResponse.body) {
@@ -0,0 +1,33 @@
1
+ import { formatErrorResponse } from "../../bridge";
2
+ import { SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE } from "../../lib/errors";
3
+ import { markLocalRequestLogRefusal, type RequestLogContext } from "../request-log";
4
+ import { SpendLedgerFileRefusedError } from "../../lib/spend-reservation-ledger";
5
+
6
+ type StorageRefusalLog = Pick<RequestLogContext, "localTerminalReason" | "terminalSource" | "errorCode">;
7
+
8
+ /** Local storage refusal, including retry wrappers: never provider-health evidence. */
9
+ export function mapSpendLedgerStorageError(error: unknown, logCtx?: StorageRefusalLog) {
10
+ const seen = new Set<unknown>();
11
+ while (error instanceof Error && !seen.has(error)) {
12
+ if (error instanceof SpendLedgerFileRefusedError) {
13
+ if (logCtx) {
14
+ markLocalRequestLogRefusal(logCtx, SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE);
15
+ logCtx.errorCode = SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE;
16
+ }
17
+ return {
18
+ status: 503,
19
+ errorType: "server_error",
20
+ code: SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE,
21
+ message: error.message,
22
+ } as const;
23
+ }
24
+ seen.add(error);
25
+ error = error.cause;
26
+ }
27
+ return undefined;
28
+ }
29
+
30
+ export function spendLedgerStorageErrorResponse(error: unknown, logCtx?: StorageRefusalLog): Response | undefined {
31
+ const refusal = mapSpendLedgerStorageError(error, logCtx);
32
+ return refusal && formatErrorResponse(refusal.status, refusal.code, refusal.message);
33
+ }
@@ -23,8 +23,9 @@ import { CODEX_RESPONSES_HTTP_URL, CODEX_RESPONSES_WS_URL, CODEX_WS_FRAME_HEADER
23
23
  import { codexWsExchange } from "./codex-ws-exchange";
24
24
  import { CodexWsSession } from "./codex-ws-session";
25
25
  import { codexWsPool, codexWsReuseIdentity } from "./codex-ws-pool";
26
- import { codexWsCreateFrameExceedsLimit } from "./codex-ws-wire";
26
+ import { codexWsCreateFrameExceedsLimit, type CodexWsPreOutputRefusalHold } from "./codex-ws-wire";
27
27
  import { isLoopbackUrl, rewriteWebSocketDial } from "../../plugins/upstream-hooks";
28
+ import { outboundCredentials } from "../../lib/outbound-credential-registry";
28
29
  export { CODEX_WS_LIVENESS_PING_INTERVAL_MS, CODEX_WS_RESPONSE_PRELUDE_TIMEOUT_MS, MAX_CODEX_WS_FRAME_BYTES, MAX_CODEX_WS_QUEUE_BYTES,
29
30
  MAX_CODEX_WS_CREATE_FRAME_BYTES, CODEX_WS_CREATE_FRAME_LIMIT_BYTES, codexWsCreateFrameExceedsLimit,
30
31
  isCodexWsQuotaObservedResponse, isCodexWsUpstreamResponse, isCodexWsPreludeProjection } from "./codex-ws-wire";
@@ -162,6 +163,8 @@ export async function codexWsUpstreamFetch(
162
163
  nativeControl?: NativeResponseControl,
163
164
  beforeContinuation?: () => Promise<void>,
164
165
  onPhysicalDispatch?: () => void,
166
+ /** Opt-in pre-output refusal hold (#6969); see codexWsExchange. */
167
+ preOutputRefusalHold?: CodexWsPreOutputRefusalHold,
165
168
  ): Promise<Response> {
166
169
  const prepared = prepareCodexWsRequest(url, init);
167
170
  if (!prepared) return sseFallback(url, prepareCodexHttpInit(url, init));
@@ -221,6 +224,10 @@ export async function codexWsUpstreamFetch(
221
224
  // reused for a different destination or with stale plugin headers.
222
225
  const dial = planCodexWsDial(wsUrl, headers, proxy);
223
226
  if (!dial) return sseFallback(url, init);
227
+ // The final dial headers, after plugin rewrites, are what the WebSocket upstream receives; the
228
+ // per-turn headers planning drops from the upgrade still reach it inside the frame metadata.
229
+ outboundCredentials().remember(headers);
230
+ outboundCredentials().remember(dial.headers);
224
231
  const identity = control ? null : codexWsReuseIdentity(url, dial.headers, frameText, dial.proxy, dial.url);
225
232
  const reuseAcrossTurns = identity !== null && codexWsReuseAcrossTurnsEnabled();
226
233
  const pooled = identity
@@ -243,6 +250,7 @@ export async function codexWsUpstreamFetch(
243
250
  session, url, init, prepared, sseFallback, onQuota, beforeDispatch,
244
251
  nativeControl: control,
245
252
  beforeContinuation, onPhysicalDispatch,
253
+ preOutputRefusalHold,
246
254
  bunVersion: typeof runtime === "string" ? runtime : runtime.version,
247
255
  });
248
256
  }
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import { execFile } from "node:child_process";
3
3
  import { join } from "node:path";
4
4
  import { durableBunPath } from "../lib/bun-runtime";
5
+ import { selfLaunchArgv } from "../lib/self-launch-argv";
5
6
  import {
6
7
  WINDOWS_SCHTASKS_CREATE_ACCESS_DENIED_MARKER,
7
8
  isWindowsSchtasksCreateAccessDenied,
@@ -61,6 +62,18 @@ export function setStartupInstallFinalizeForTests(next: FinalizeFn | null): void
61
62
  finalizeImpl = next ?? finalizeWindowsSchedulerServiceRegistration;
62
63
  }
63
64
 
65
+ interface StartupInstallLaunch {
66
+ execFile?: typeof execFile;
67
+ /** Overrides Bun's own standalone detection, which a source-mode test cannot flip. */
68
+ isStandaloneExecutable?: boolean;
69
+ }
70
+ let installLaunch: StartupInstallLaunch = {};
71
+
72
+ /** Test-only seam: observe the installer child's argv and simulate a compiled `ocx`. */
73
+ export function setStartupInstallLaunchForTests(next: StartupInstallLaunch | null): void {
74
+ installLaunch = next ?? {};
75
+ }
76
+
64
77
  /** Snapshot of the install lock for diagnostics and tests. */
65
78
  export function getStartupInstallState(): StartupInstallState {
66
79
  return installState;
@@ -162,12 +175,19 @@ function runCliInstall(
162
175
  ): Promise<{ stdout: string; stderr: string }> {
163
176
  const bun = durableBunPath();
164
177
  const cli = join(import.meta.dir, "..", "cli", "index.ts");
165
- const argv = [cli, ...startupInstallArgv(action, options)];
178
+ // The Desktop app and the Linux deb run a compiled `ocx`, where `process.execPath` is the CLI
179
+ // itself: prefixing the source entrypoint made it an unknown command and every dashboard
180
+ // Install/Repair failed (#6963). Same builder as the startup-health probe.
181
+ const argv = selfLaunchArgv(startupInstallArgv(action, options), {
182
+ sourceEntrypoint: cli,
183
+ isStandaloneExecutable: installLaunch.isStandaloneExecutable,
184
+ });
185
+ const run = installLaunch.execFile ?? execFile;
166
186
  return new Promise((resolve, reject) => {
167
187
  const timeout = process.platform === "win32" && action === "install-service"
168
188
  ? 0
169
189
  : 60_000;
170
- execFile(bun, argv, {
190
+ run(bun, argv, {
171
191
  encoding: "utf8",
172
192
  env: process.env,
173
193
  // A fresh Windows scheduler install now owns its UAC prompt inside this CLI
@@ -9,6 +9,7 @@ import { selfLaunchArgv } from "../lib/self-launch-argv";
9
9
  import { desktopStartupOwnership } from "../service/desktop-startup";
10
10
  import type { OcxConfig } from "../types";
11
11
  import { truncateRetainedUtf8 } from "../lib/admission";
12
+ import { isPassiveRead } from "../lib/passive-read";
12
13
 
13
14
  const CACHE_TTL_MS = 30_000;
14
15
  const PROBE_TIMEOUT_MS = startupHealthProbeBudgetMs();
@@ -69,6 +70,8 @@ export function getStartupHealthSnapshot(
69
70
  ): StartupHealth {
70
71
  const now = deps.now ?? Date.now;
71
72
  if (cached && now() - cached.timestamp < CACHE_TTL_MS) return cached.value;
73
+ // A passive read (a Remote Link Child's, #6959) starts no diagnostic process on this machine.
74
+ if (isPassiveRead()) return staleOrFallback(config);
72
75
  refreshInBackground(config, deps);
73
76
  return staleOrFallback(config);
74
77
  }
@@ -189,6 +192,7 @@ export async function getCachedStartupHealth(
189
192
  ): Promise<StartupHealth> {
190
193
  const now = deps.now ?? Date.now;
191
194
  if (cached && now() - cached.timestamp < CACHE_TTL_MS) return cached.value;
195
+ if (isPassiveRead()) return staleOrFallback(config);
192
196
  refreshInBackground(config, deps);
193
197
  // An expired or empty read is an explicit protection check. Wait for the
194
198
  // isolated probe instead of presenting a synthetic failure while that probe
@@ -1,7 +1,7 @@
1
1
  import { accessSync, constants, readFileSync, writeFileSync, unlinkSync, mkdirSync, statSync } from "node:fs";
2
2
  import { delimiter, join } from "node:path";
3
3
  import { getConfigDir } from "../config";
4
- import { claudeToolSearchEnv, resolveAutoContext, type AutoContextMode } from "../claude/context-windows";
4
+ import { claudeTierDisplayName, claudeToolSearchEnv, isClaudeTierLabelSlot, resolveAutoContext, type AutoContextMode } from "../claude/context-windows";
5
5
  import { PROXY_MARKER, defaultAuthDetectDeps, detectClaudeAuth, ownAdmissionTokens, type AuthDetectDeps } from "../claude/auth-detect";
6
6
  import { resolveClaudeAuthMode } from "../claude/auth-mode";
7
7
  import { ANTHROPIC_PARENT_ENV_SLOTS, trustedNodeLauncherContext, type AnthropicParentEnvSlot } from "../cli/launcher-context";
@@ -115,7 +115,12 @@ export function writeShellEnvFile(
115
115
  }
116
116
  for (const [name, value] of Object.entries(modelEnv)) {
117
117
  if (name === "ANTHROPIC_MODEL") continue;
118
- lines.push(conditional(name, value));
118
+ // An alias tier slot carries its picker label (#6926) in the same branch, so the label is
119
+ // exported only when this file writes the slot and the shell has no label of its own.
120
+ const label = isClaudeTierLabelSlot(name) ? claudeTierDisplayName(value) : undefined;
121
+ lines.push(label === undefined
122
+ ? conditional(name, value)
123
+ : `${conditional(name, value)} && [ -z "\${${name}_NAME+x}" ] && export ${name}_NAME=${shellValue(label)}`);
119
124
  }
120
125
  const maxCtx = config.claudeCode?.maxContextTokens;
121
126
  if (typeof maxCtx === "number" && Number.isFinite(maxCtx) && maxCtx > 0) {
@@ -2,7 +2,7 @@ import { execFileSync } from "node:child_process";
2
2
  import { readFileSync, writeFileSync, unlinkSync, mkdirSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { getConfigDir } from "../config";
5
- import { claudeToolSearchEnv, resolveAutoContext, type AutoContextMode } from "../claude/context-windows";
5
+ import { CLAUDE_TIER_LABEL_SLOTS, claudeTierDisplayName, claudeToolSearchEnv, resolveAutoContext, type AutoContextMode } from "../claude/context-windows";
6
6
  import { PROXY_MARKER } from "../claude/auth-detect";
7
7
  import { isProxyAdmissionSecret } from "./auth-cors";
8
8
  import type { OcxConfig } from "../types";
@@ -30,6 +30,8 @@ const MANAGED_SYSTEM_ENV_NAMES = new Set<string>([
30
30
  "ANTHROPIC_DEFAULT_FABLE_MODEL",
31
31
  "ANTHROPIC_DEFAULT_HAIKU_MODEL",
32
32
  "ANTHROPIC_SMALL_FAST_MODEL",
33
+ // Picker labels paired with an alias tier slot this instance injected (#6926).
34
+ ...CLAUDE_TIER_LABEL_SLOTS.map(slot => `${slot}_NAME`),
33
35
  "CLAUDE_CODE_MAX_CONTEXT_TOKENS",
34
36
  "DISABLE_COMPACT",
35
37
  "CLAUDE_CODE_AUTO_COMPACT_WINDOW",
@@ -315,6 +317,14 @@ export async function injectSystemEnv(
315
317
  if (name === "ANTHROPIC_MODEL") continue; // legacy slot handled by shell file only (back-compat)
316
318
  injectLever(name, value);
317
319
  }
320
+ // Picker labels (#6926) only for alias tier slots this instance owns after the loop above: a
321
+ // launchd slot the user set keeps whatever label it has, and a label nothing produces any
322
+ // more is unset by the produced-key sweep below like any other tracked lever.
323
+ for (const slot of CLAUDE_TIER_LABEL_SLOTS) {
324
+ const value = modelEnv[slot];
325
+ const label = value === undefined ? undefined : claudeTierDisplayName(value);
326
+ if (label !== undefined && injectedKeys.includes(slot)) injectLever(`${slot}_NAME`, label);
327
+ }
318
328
  const maxCtx = config.claudeCode?.maxContextTokens;
319
329
  if (typeof maxCtx === "number" && Number.isFinite(maxCtx) && maxCtx > 0) {
320
330
  injectLever("CLAUDE_CODE_MAX_CONTEXT_TOKENS", String(Math.floor(maxCtx)));
@@ -27,6 +27,8 @@ import { win32 } from "node:path";
27
27
  import { serviceDiagnosticsSummary } from "./diagnostics";
28
28
  import { runServiceClaim } from "./claim";
29
29
  import { createSupervisionLatch, desktopServiceCommandRefusal } from "./desktop-command-guard";
30
+ import { linuxServiceBootHint } from "./linux-linger";
31
+ import { diagnoseDesktopStartup, desktopLoginStatusSummary } from "./desktop-startup";
30
32
 
31
33
  /**
32
34
  * `restart` is NO LONGER folded into `repair`.
@@ -336,7 +338,7 @@ export async function serviceCommand(...args: (string | undefined)[]): Promise<v
336
338
  // and the baked one cannot have diverged yet — unlike `start`, which reads the
337
339
  // installed artifact instead.
338
340
  await reportServiceServing("installed", { port: resolveServiceListenPort() });
339
- if (process.platform === "linux") console.log(" For auto-start on boot: loginctl enable-linger $USER");
341
+ if (process.platform === "linux") console.log(linuxServiceBootHint());
340
342
  // Service users never reach the `ocx start` prompt: the proxy they run is the
341
343
  // supervised child, which always carries OCX_SERVICE=1. This command, though, is
342
344
  // hand-typed in a real terminal, so it is the one interactive moment they get.
@@ -433,6 +435,8 @@ export async function serviceCommand(...args: (string | undefined)[]): Promise<v
433
435
  const lease = ownershipMutationLeaseStatusLine(serviceStatePaths());
434
436
  if (lease) console.log(lease);
435
437
  console.log(`Diagnostics: ${serviceDiagnosticsSummary()}`);
438
+ const desktop = diagnoseDesktopStartup();
439
+ if (desktop) console.log(desktopLoginStatusSummary(desktop));
436
440
  break;
437
441
  }
438
442
  case "uninstall":