@bitkyc08/opencodex 2.90.0 → 2.91.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 (132) hide show
  1. package/gui/dist/assets/App-DMps4OLk.css +1 -0
  2. package/gui/dist/assets/{App-DgxkE6Md.js → App-nZ34cR5G.js} +18 -18
  3. package/gui/dist/assets/{Tray-DbTNc_88.js → Tray-BuLy7hL1.js} +1 -1
  4. package/gui/dist/assets/{index-TmaHa54u.js → index-DGXS51sp.js} +12 -12
  5. package/gui/dist/assets/{quota-summary-B16x5DZZ.js → quota-summary-DO1Sx5TM.js} +1 -1
  6. package/gui/dist/index.html +1 -1
  7. package/package.json +1 -1
  8. package/src/adapters/anthropic.ts +2 -5
  9. package/src/adapters/claude-cli/usage-admission.ts +14 -0
  10. package/src/adapters/openai-responses/passthrough.ts +2 -1
  11. package/src/adapters/openai-responses/request-strips.ts +32 -0
  12. package/src/adapters/openai-responses/transport.ts +6 -0
  13. package/src/chat/inbound.ts +20 -2
  14. package/src/chat/outbound.ts +36 -4
  15. package/src/chat/reasoning-details.ts +137 -0
  16. package/src/cli/capabilities-agents-routing.ts +50 -0
  17. package/src/cli/capabilities-base.ts +15 -2
  18. package/src/cli/capabilities-observe-system.ts +4 -4
  19. package/src/cli/codex-shim-autorestore.ts +5 -0
  20. package/src/cli/dispatch.ts +30 -0
  21. package/src/cli/fallback.ts +127 -0
  22. package/src/cli/help.ts +1 -0
  23. package/src/cli/index.ts +1 -2
  24. package/src/cli/link-apply-restart.ts +72 -0
  25. package/src/cli/link-child.ts +6 -2
  26. package/src/cli/link-cleanup.ts +95 -0
  27. package/src/cli/link-enrollment.ts +4 -0
  28. package/src/cli/link.ts +84 -9
  29. package/src/cli/provider.ts +44 -2
  30. package/src/cli/registry.ts +13 -1
  31. package/src/cli/restart-failure.ts +4 -0
  32. package/src/cli/runtime-api.ts +1 -1
  33. package/src/cli/status-codex-settings.ts +56 -0
  34. package/src/cli/status-fallback.ts +29 -0
  35. package/src/cli/status-install.ts +52 -0
  36. package/src/cli/status.ts +20 -1
  37. package/src/cli/system-restart-client.ts +32 -1
  38. package/src/cli/update-restart-child.ts +4 -3
  39. package/src/cli/update-restart-home.ts +20 -8
  40. package/src/client/connect.ts +82 -6
  41. package/src/client/home-unreachable.ts +16 -0
  42. package/src/client/hub-relay.ts +8 -2
  43. package/src/client/link-home.ts +119 -6
  44. package/src/client/link-parity-reporter.ts +107 -0
  45. package/src/client/link-relay-transport.ts +8 -1
  46. package/src/client/link-relay.ts +27 -3
  47. package/src/client/machine-listener.ts +83 -1
  48. package/src/client/runtime-restart.ts +127 -0
  49. package/src/client/runtime.ts +137 -25
  50. package/src/codex/auth-context.ts +10 -6
  51. package/src/codex/catalog/parsing.ts +4 -0
  52. package/src/codex/desktop-compatibility/windows-key-protection.ts +18 -6
  53. package/src/codex/quota-history.ts +7 -0
  54. package/src/codex/quota.ts +10 -0
  55. package/src/codex/subagent-model-fallback.ts +24 -1
  56. package/src/combos/index.ts +1 -0
  57. package/src/combos/resolve.ts +12 -0
  58. package/src/config/diagnostics.ts +5 -0
  59. package/src/config/load-degrade.ts +2 -0
  60. package/src/config/schema/config-schema.ts +3 -0
  61. package/src/config/schema/fallback-policy.ts +40 -0
  62. package/src/generated/compatibility-version.json +205 -97
  63. package/src/lib/authorization-scheme.ts +95 -0
  64. package/src/lib/provider-client-headers.ts +1 -0
  65. package/src/lib/redact.ts +2 -2
  66. package/src/lib/system-restart-contract.ts +24 -0
  67. package/src/lib/test-home-guard.ts +78 -11
  68. package/src/link/child-parity.ts +124 -0
  69. package/src/link/codex-settings.ts +37 -0
  70. package/src/link/compatibility.ts +119 -0
  71. package/src/link/management-routes.ts +2 -0
  72. package/src/link/peer-report.ts +100 -0
  73. package/src/link/remote-platform.ts +78 -0
  74. package/src/link/ssh-argv.ts +19 -0
  75. package/src/link/status-projection.ts +24 -2
  76. package/src/oauth/index.ts +1 -1
  77. package/src/oauth/kimi.ts +2 -0
  78. package/src/oauth/nous.ts +4 -1
  79. package/src/oauth/store.ts +2 -1
  80. package/src/protocols/codecs/chat.ts +3 -3
  81. package/src/protocols/encoders/adapter-events.ts +26 -2
  82. package/src/protocols/encoders/chat.ts +55 -3
  83. package/src/providers/mistral-transport.ts +37 -4
  84. package/src/providers/quota/antigravity.ts +2 -8
  85. package/src/quota-pool/assemble.ts +326 -0
  86. package/src/quota-pool/contract.ts +58 -0
  87. package/src/quota-pool/identity.ts +203 -0
  88. package/src/remote/features.ts +74 -0
  89. package/src/remote/protocol.ts +2 -0
  90. package/src/remote/served-by.ts +57 -0
  91. package/src/responses/parser.ts +7 -0
  92. package/src/responses/reasoning-envelope.ts +12 -0
  93. package/src/routing/fallback-policy.ts +290 -0
  94. package/src/routing/fallback-state.ts +179 -0
  95. package/src/server/audio-client.ts +2 -1
  96. package/src/server/audio-dictation.ts +2 -1
  97. package/src/server/audio-upstream.ts +2 -1
  98. package/src/server/auth-cors.ts +6 -5
  99. package/src/server/background-lifecycle.ts +4 -0
  100. package/src/server/chat-completions.ts +8 -1
  101. package/src/server/gui-session.ts +2 -1
  102. package/src/server/index/link-listener.ts +11 -1
  103. package/src/server/index/link-management.ts +47 -0
  104. package/src/server/index/link-relay-sessions.ts +11 -2
  105. package/src/server/index/optional-listeners.ts +7 -2
  106. package/src/server/index/serve-options.ts +19 -2
  107. package/src/server/live.ts +5 -0
  108. package/src/server/management/context.ts +1 -1
  109. package/src/server/management/fallback-routes.ts +75 -0
  110. package/src/server/management/link-enrollment.ts +21 -3
  111. package/src/server/management/link-route-state.ts +2 -0
  112. package/src/server/management/link-routes.ts +12 -1
  113. package/src/server/management/quota-pool-routes.ts +24 -0
  114. package/src/server/management/route-registry.ts +3 -0
  115. package/src/server/management-api.ts +16 -0
  116. package/src/server/management-auth.ts +2 -1
  117. package/src/server/responses/agent-task-recovery.ts +1 -1
  118. package/src/server/responses/compact.ts +2 -2
  119. package/src/server/responses/core-auth.ts +11 -1
  120. package/src/server/responses/core-combo.ts +72 -1
  121. package/src/server/responses/encrypted-payload.ts +3 -3
  122. package/src/server/responses/main-hard-lock-routing.ts +54 -2
  123. package/src/server/responses/policy-fallback.ts +44 -1
  124. package/src/server/responses/request-prepare.ts +9 -1
  125. package/src/server/restart-replacement.ts +50 -2
  126. package/src/server/ws-bridge.ts +5 -0
  127. package/src/service/cli.ts +25 -2
  128. package/src/service/diagnostics.ts +20 -6
  129. package/src/service/status-json.ts +116 -0
  130. package/src/types/config.ts +7 -0
  131. package/src/types/fallback.ts +38 -0
  132. package/gui/dist/assets/App-dnkJ85te.css +0 -1
@@ -38,6 +38,7 @@ import {
38
38
  isCanonicalOpenAiForwardProvider,
39
39
  } from "../providers/openai-tiers";
40
40
  import { routeModel, type RouteResult } from "../router";
41
+ import { guardFallbackHop, type FallbackAdmission } from "../routing/fallback-policy";
41
42
  import { sweepExpiredOnWrite } from "../lib/state-store-sweeper";
42
43
  import { codexAccountNamespaceForModel } from "./account-namespace-match";
43
44
  import { ACCOUNT_GATED_NATIVE_OPENAI_MODELS, NATIVE_MAIN_DRAIN_SENTINEL_MODELS } from "./catalog/native-models";
@@ -629,7 +630,8 @@ export function applySubagentModelFallback(
629
630
  modelEligibleAccountIdsForModel?: SubagentModelEligibleAccountIds,
630
631
  resolvedFallbackChain?: readonly string[] | null,
631
632
  restrictedRouteEligible?: SubagentFallbackRouteEligibility,
632
- ): { from?: string; to?: string; skipped?: string[] } | null {
633
+ admission?: FallbackAdmission,
634
+ ): { from?: string; to?: string; skipped?: string[]; refusal?: Response } | null {
633
635
  if (!isThreadSpawnRequest(headers)) return null;
634
636
  const fallbackChain = resolvedFallbackChain === undefined
635
637
  ? resolveSubagentFallbackChain(parsed, config)
@@ -653,10 +655,31 @@ export function applySubagentModelFallback(
653
655
  ? { from: parsed.modelId, to: parsed.modelId, skipped: selection.skipped }
654
656
  : null;
655
657
  const from = parsed.modelId;
658
+ // OCX-11: the rewrite is a proactive hop away from an unavailable primary. Refused, the spawn
659
+ // keeps its model and the caller answers with the refusal instead of sending anything.
660
+ const hop = guardFallbackHop(config, {
661
+ site: "subagent",
662
+ route: from,
663
+ from: fallbackTargetOf(config, from),
664
+ to: fallbackTargetOf(config, selection.model),
665
+ trigger: "unavailable",
666
+ admission,
667
+ now,
668
+ });
669
+ if (!hop.ok) return { from, to: from, skipped: selection.skipped, refusal: hop.response };
670
+ // The rewrite is this site's admission: the spawn now carries the fallback model.
671
+ hop.commit();
656
672
  rewriteParsedModel(parsed, selection.model);
657
673
  return { from, to: selection.model, skipped: selection.skipped };
658
674
  }
659
675
 
676
+ function fallbackTargetOf(config: OcxConfig, model: string): { provider: string; model: string } {
677
+ const route = tryRouteFallbackModel(config, model);
678
+ if (route) return { provider: route.providerName, model: route.modelId };
679
+ const slash = model.indexOf("/");
680
+ return slash > 0 ? { provider: model.slice(0, slash), model: model.slice(slash + 1) } : { provider: "", model };
681
+ }
682
+
660
683
  /** Resolve the effective fallback chain once for one logical spawn request. */
661
684
  export function resolveSubagentFallbackChain(
662
685
  parsed: OcxParsedRequest,
@@ -23,6 +23,7 @@ export {
23
23
  advanceComboAfterFailure,
24
24
  clearComboSelectionState,
25
25
  comboExclusionReasons,
26
+ comboTargetSkipTrigger,
26
27
  NoAvailableComboTargetsError,
27
28
  noteComboFailure,
28
29
  noteComboSuccess,
@@ -74,6 +74,18 @@ function targetProviderIsUsable(config: OcxConfig, target: OcxComboTarget, now:
74
74
  return providerExclusionReason(config, target, now) === undefined;
75
75
  }
76
76
 
77
+ /**
78
+ * OCX-11: the coarse fallback trigger for a target the picker passed over for a reason other
79
+ * than a cooldown (provider missing or disabled, cached quota spent, ineligible for the request).
80
+ */
81
+ export function comboTargetSkipTrigger(
82
+ config: OcxConfig,
83
+ target: OcxComboTarget,
84
+ now = Date.now(),
85
+ ): "quota_exhausted" | "unavailable" {
86
+ return providerExclusionReason(config, target, now) === "quota exhausted" ? "quota_exhausted" : "unavailable";
87
+ }
88
+
77
89
  /**
78
90
  * Why each target of a combo cannot be picked right now, for a zero-attempt refusal. Only the
79
91
  * configured provider/model and a coarse label are named, never account or request data.
@@ -11,6 +11,7 @@ import { nativeReasoningRetentionConfigError } from "./schema/native-reasoning-r
11
11
  import { desktopCompatibilityConfigError } from "./schema/desktop-compatibility";
12
12
  import { blockedModelRedirectsError } from "./schema/blocked-model-redirects";
13
13
  import { mainAccountHardLockFallbackError } from "./schema/main-account-hard-lock-fallback";
14
+ import { fallbackPolicyError } from "./schema/fallback-policy";
14
15
  import type { OcxConfig } from "../types";
15
16
  import { anthropicAccountPoolConfigError, anthropicOAuthInstanceConfigError, anthropicSidecarConfigError } from "./schema/anthropic-account-pool";
16
17
  import { configReasoningPinsConfigError } from "./provider-validation";
@@ -145,6 +146,7 @@ function validFileConfigDiagnostics(config: OcxConfig, rawParsed: unknown): Conf
145
146
  if (blockedModelRedirectsError(rawParsed)) warnings.push("blockedModelRedirects ignored: expected a map of nonempty model keys to nonempty string targets");
146
147
  if (nativeReasoningRetentionConfigError(rawParsed)) warnings.push("nativeReasoningRetention ignored: expected only optional boolean modelSwitch and accountSwitch fields; both remain off");
147
148
  if (mainAccountHardLockFallbackError(rawParsed)) warnings.push("codexMainAccountHardLockFallback ignored: expected a nonblank model selector and an optional declared reasoningEffort");
149
+ if (fallbackPolicyError(rawParsed)) warnings.push("fallback partly ignored: expected boolean model/provider switches, providers/routes/devices maps of them, and devices.<id>.localFallback; ignored leaves keep today's fallback behavior");
148
150
  const quotaAutoRefreshWarning = degradedCodexQuotaAutoRefreshWarning(rawParsed, normalized);
149
151
  if (quotaAutoRefreshWarning) warnings.push(quotaAutoRefreshWarning);
150
152
  if (rawEffort !== undefined && !isClaudeSubagentEffort(rawEffort)) {
@@ -729,6 +731,9 @@ export function validateConfigCandidate(value: unknown): { ok: true; config: Ocx
729
731
  const anthropicError = anthropicOAuthInstanceConfigError(value) ?? anthropicAccountPoolConfigError(value) ?? anthropicSidecarConfigError(value);
730
732
  if (anthropicError) return { ok: false, error: anthropicError };
731
733
  const boundaryError = blockedModelRedirectsError(value)
734
+ // OCX-11: the read schema degrades a malformed fallback leaf to absent, which would silently
735
+ // drop a refusal on the next whole-config save; the write boundary rejects it instead.
736
+ ?? fallbackPolicyError(value)
732
737
  ?? nativeReasoningRetentionConfigError(value)
733
738
  ?? mainAccountHardLockFallbackError(value)
734
739
  ?? compactionRecoveryConfigError(value) ?? desktopCompatibilityConfigError(value) ?? configReasoningPinsConfigError(value)
@@ -7,6 +7,7 @@ import { nativeReasoningRetentionConfigError } from "./schema/native-reasoning-r
7
7
  import { desktopCompatibilityConfigError } from "./schema/desktop-compatibility";
8
8
  import { blockedModelRedirectsError } from "./schema/blocked-model-redirects";
9
9
  import { mainAccountHardLockFallbackError } from "./schema/main-account-hard-lock-fallback";
10
+ import { fallbackPolicyError } from "./schema/fallback-policy";
10
11
  import { anthropicSidecarConfigError } from "./schema/anthropic-account-pool";
11
12
  import {
12
13
  modelPinnedEffortsConfigError,
@@ -142,6 +143,7 @@ export function warnDegradedTopLevelOptIns(rawParsed: unknown, validated: OcxCon
142
143
  if (desktopCompatibilityConfigError(rawParsed)) console.warn("Invalid desktopCompatibility startup preference ignored; no desktop compatibility service will start automatically.");
143
144
  if (blockedModelRedirectsError(rawParsed)) console.warn("⚠️ invalid blockedModelRedirects ignored; provider routing remains available");
144
145
  if (mainAccountHardLockFallbackError(rawParsed)) console.warn("⚠️ invalid codexMainAccountHardLockFallback ignored; the main-account hard lock still refuses while blocked");
146
+ if (fallbackPolicyError(rawParsed)) console.warn("⚠️ invalid fallback leaves ignored; those scopes keep today's fallback behavior");
145
147
  const chatgptDesktop = chatgptDesktopConfigIssue(rawParsed);
146
148
  if (chatgptDesktop) console.warn(`⚠️ config.json ${chatgptDesktop} — the whole chatgptDesktop block is ignored, so the ChatGPT desktop integration reads as off`);
147
149
  warnDegradedStreamMode(rawParsed, validated);
@@ -8,6 +8,7 @@ import { nativeReasoningRetentionSchema } from "./native-reasoning-retention";
8
8
  import { desktopCompatibilitySchema } from "./desktop-compatibility";
9
9
  import { blockedModelRedirectsSchema } from "./blocked-model-redirects";
10
10
  import { mainAccountHardLockFallbackSchema } from "./main-account-hard-lock-fallback";
11
+ import { fallbackPolicySchema } from "./fallback-policy";
11
12
  import {
12
13
  agentTaskRecoverySchema,
13
14
  catalogAutoRefreshSchema,
@@ -241,6 +242,8 @@ export const configSchema = z.object({
241
242
  // A malformed fallback section disables only the reroute, never the lock's refusal
242
243
  // behavior; the management write boundary still rejects the bad value.
243
244
  codexMainAccountHardLockFallback: mainAccountHardLockFallbackSchema.optional().catch(undefined),
245
+ // OCX-11: a malformed section reads as absent, which keeps today's fallback behavior.
246
+ fallback: fallbackPolicySchema.optional().catch(undefined),
244
247
  // #2999: a malformed hand edit falls back to auto detection rather than rejecting the config.
245
248
  nativeMainRefresh: z.enum(["codex", "opencodex"]).optional().catch(undefined),
246
249
  // Future versions remain opaque through passthrough-compatible whole-config saves.
@@ -0,0 +1,40 @@
1
+ import * as z from "zod/v4";
2
+
3
+ /**
4
+ * Read schema of `fallback` (OCX-11). A malformed leaf degrades to absent, which is today's
5
+ * behavior, and config diagnostics name it; the management write boundary rejects it instead.
6
+ * Map keys are checked by the resolver's normalization, so one bad key never drops its siblings.
7
+ */
8
+ const switchValue = z.boolean().optional().catch(undefined);
9
+ const switchesSchema = z.object({ model: switchValue, provider: switchValue });
10
+ const deviceSchema = switchesSchema.extend({ localFallback: switchValue });
11
+
12
+ export const fallbackPolicySchema = z.object({
13
+ model: switchValue,
14
+ provider: switchValue,
15
+ providers: z.record(z.string(), switchesSchema.catch({})).optional().catch(undefined),
16
+ routes: z.record(z.string(), switchesSchema.catch({})).optional().catch(undefined),
17
+ devices: z.record(z.string(), deviceSchema.catch({})).optional().catch(undefined),
18
+ });
19
+
20
+ export const FALLBACK_DEVICE_ID = /^(?:lnk_[0-9a-f]{16}|[A-Za-z0-9][A-Za-z0-9_.:-]{0,255})$/;
21
+ export const FALLBACK_SELECTOR = /^[^\s]{1,256}$/;
22
+
23
+ const strictSwitches = z.object({ model: z.boolean().optional(), provider: z.boolean().optional() }).strict();
24
+ const strictPolicy = z.object({
25
+ model: z.boolean().optional(),
26
+ provider: z.boolean().optional(),
27
+ providers: z.record(z.string().regex(FALLBACK_SELECTOR), strictSwitches).optional(),
28
+ routes: z.record(z.string().regex(FALLBACK_SELECTOR), strictSwitches).optional(),
29
+ devices: z.record(z.string().regex(FALLBACK_DEVICE_ID), strictSwitches.extend({ localFallback: z.boolean().optional() }).strict()).optional(),
30
+ }).strict();
31
+
32
+ /** Diagnostics text for a section the read schema would partly ignore, or undefined. */
33
+ export function fallbackPolicyError(rawConfig: unknown): string | undefined {
34
+ if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) return undefined;
35
+ const raw = (rawConfig as Record<string, unknown>).fallback;
36
+ if (raw === undefined) return undefined;
37
+ return strictPolicy.safeParse(raw).success
38
+ ? undefined
39
+ : "schema_invalid: fallback accepts boolean model/provider switches, providers/routes/devices maps of them, and devices.<id>.localFallback";
40
+ }