@bitkyc08/opencodex 2.90.0 → 2.92.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 (250) hide show
  1. package/bin/ocx.mjs +37 -3
  2. package/gui/dist/assets/App-DMps4OLk.css +1 -0
  3. package/gui/dist/assets/{App-DgxkE6Md.js → App-sUf_8afJ.js} +18 -18
  4. package/gui/dist/assets/{Tray-DbTNc_88.js → Tray-BSsxznDm.js} +1 -1
  5. package/gui/dist/assets/index-BlFE_VB_.js +94 -0
  6. package/gui/dist/assets/quota-summary-BRlbXgw0.js +1 -0
  7. package/gui/dist/index.html +1 -1
  8. package/gui/dist/provider-icons/workbuddy-color.svg +29 -0
  9. package/package.json +4 -3
  10. package/src/adapters/anthropic.ts +2 -5
  11. package/src/adapters/claude-cli/usage-admission.ts +14 -0
  12. package/src/adapters/google-vertex-endpoint.ts +9 -1
  13. package/src/adapters/google-vertex-key-project.ts +223 -0
  14. package/src/adapters/google.ts +13 -3
  15. package/src/adapters/openai-chat/tool-name-registry.ts +3 -1
  16. package/src/adapters/openai-chat.ts +2 -2
  17. package/src/adapters/openai-responses/passthrough.ts +2 -1
  18. package/src/adapters/openai-responses/request-strips.ts +92 -0
  19. package/src/adapters/openai-responses/transport.ts +6 -0
  20. package/src/adapters/registry.ts +5 -0
  21. package/src/adapters/workbuddy-schema.ts +61 -0
  22. package/src/adapters/workbuddy.ts +68 -0
  23. package/src/bridge/response-json.ts +1 -1
  24. package/src/bridge/sse.ts +1 -1
  25. package/src/chat/inbound.ts +20 -2
  26. package/src/chat/outbound.ts +47 -8
  27. package/src/chat/reasoning-details.ts +137 -0
  28. package/src/cli/capabilities-agents-routing.ts +53 -3
  29. package/src/cli/capabilities-base.ts +15 -2
  30. package/src/cli/capabilities-observe-system.ts +4 -4
  31. package/src/cli/codex-cli-update.ts +8 -0
  32. package/src/cli/codex-shim-autorestore.ts +5 -0
  33. package/src/cli/combo-result.ts +6 -0
  34. package/src/cli/combo-stats.ts +3 -0
  35. package/src/cli/combo.ts +66 -42
  36. package/src/cli/dispatch.ts +30 -0
  37. package/src/cli/fallback.ts +127 -0
  38. package/src/cli/help.ts +1 -0
  39. package/src/cli/index.ts +1 -2
  40. package/src/cli/launcher-context.ts +49 -2
  41. package/src/cli/link-apply-restart.ts +72 -0
  42. package/src/cli/link-child.ts +6 -2
  43. package/src/cli/link-cleanup.ts +95 -0
  44. package/src/cli/link-enrollment.ts +4 -0
  45. package/src/cli/link.ts +123 -35
  46. package/src/cli/provider.ts +46 -3
  47. package/src/cli/registry.ts +13 -1
  48. package/src/cli/restart-failure.ts +4 -0
  49. package/src/cli/runtime-api.ts +1 -1
  50. package/src/cli/status-codex-settings.ts +56 -0
  51. package/src/cli/status-fallback.ts +29 -0
  52. package/src/cli/status-install.ts +52 -0
  53. package/src/cli/status.ts +20 -1
  54. package/src/cli/system-restart-client.ts +32 -1
  55. package/src/cli/update-restart-child.ts +4 -3
  56. package/src/cli/update-restart-home.ts +20 -8
  57. package/src/cli/usage-compare.ts +68 -12
  58. package/src/client/connect.ts +82 -6
  59. package/src/client/home-unreachable.ts +16 -0
  60. package/src/client/hub-relay.ts +8 -2
  61. package/src/client/link-home.ts +119 -6
  62. package/src/client/link-ingress.ts +1 -1
  63. package/src/client/link-parity-reporter.ts +107 -0
  64. package/src/client/link-readiness.ts +97 -0
  65. package/src/client/link-relay-transport.ts +84 -9
  66. package/src/client/link-relay.ts +98 -15
  67. package/src/client/link-tunnel.ts +45 -18
  68. package/src/client/machine-listener.ts +103 -3
  69. package/src/client/runtime-restart.ts +127 -0
  70. package/src/client/runtime.ts +155 -25
  71. package/src/codex/account-store.ts +17 -3
  72. package/src/codex/app-server-restart-service.ts +6 -5
  73. package/src/codex/auth-context.ts +10 -6
  74. package/src/codex/catalog/effort.ts +9 -2
  75. package/src/codex/catalog/gather-capture.ts +4 -2
  76. package/src/codex/catalog/parsing.ts +4 -0
  77. package/src/codex/catalog/provider-models.ts +14 -8
  78. package/src/codex/chatgpt-refresh-failure.ts +6 -2
  79. package/src/codex/cli-install-provenance.ts +418 -0
  80. package/src/codex/cli-installation-targets.ts +5 -1
  81. package/src/codex/cli-update-plan.ts +43 -16
  82. package/src/codex/desktop-app/picker-freshness.ts +10 -8
  83. package/src/codex/desktop-compatibility/usage-activation.ts +2 -1
  84. package/src/codex/desktop-compatibility/usage-controller.ts +7 -1
  85. package/src/codex/desktop-compatibility/windows-key-protection.ts +18 -6
  86. package/src/codex/pool-refresh-backoff.ts +3 -1
  87. package/src/codex/quota-history.ts +7 -0
  88. package/src/codex/quota.ts +10 -0
  89. package/src/codex/subagent-model-fallback.ts +24 -1
  90. package/src/codex/user-identity.ts +35 -6
  91. package/src/codex/windows-current-sid.ts +162 -0
  92. package/src/combos/index.ts +1 -0
  93. package/src/combos/jev-quota-activation.ts +5 -1
  94. package/src/combos/jev-quota-route.ts +1 -1
  95. package/src/combos/jev-quota.ts +9 -1
  96. package/src/combos/jev-session-pin-config.ts +51 -0
  97. package/src/combos/jev.ts +6 -0
  98. package/src/combos/resolve.ts +12 -0
  99. package/src/combos/types.ts +19 -0
  100. package/src/config/diagnostics.ts +5 -0
  101. package/src/config/load-degrade.ts +2 -0
  102. package/src/config/provider-validation.ts +7 -0
  103. package/src/config/schema/config-schema.ts +3 -0
  104. package/src/config/schema/fallback-policy.ts +40 -0
  105. package/src/config/schema/leaf-validators.ts +1 -0
  106. package/src/generated/compatibility-version.json +375 -195
  107. package/src/lib/authorization-scheme.ts +95 -0
  108. package/src/lib/decision-quota-sink.ts +10 -0
  109. package/src/lib/provider-client-headers.ts +1 -0
  110. package/src/lib/redact.ts +2 -2
  111. package/src/lib/system-restart-contract.ts +24 -0
  112. package/src/lib/test-home-guard.ts +78 -11
  113. package/src/lib/translator-budget.ts +4 -4
  114. package/src/lib/upstream-retry.ts +4 -1
  115. package/src/link/child-parity.ts +124 -0
  116. package/src/link/codex-settings.ts +37 -0
  117. package/src/link/compatibility.ts +119 -0
  118. package/src/link/diagnostics.ts +193 -0
  119. package/src/link/management-routes.ts +2 -0
  120. package/src/link/peer-report.ts +100 -0
  121. package/src/link/relay-auth.ts +6 -0
  122. package/src/link/remote-platform.ts +78 -0
  123. package/src/link/ssh-argv.ts +19 -0
  124. package/src/link/status-projection.ts +63 -2
  125. package/src/link/supervisor.ts +45 -12
  126. package/src/link/tunnel-history.ts +109 -0
  127. package/src/link/tunnel-state.ts +80 -17
  128. package/src/messaging/remote-files.ts +24 -1
  129. package/src/oauth/generic-account-failover.ts +91 -7
  130. package/src/oauth/index.ts +16 -1
  131. package/src/oauth/kimi.ts +2 -0
  132. package/src/oauth/model-discovery-auth.ts +5 -0
  133. package/src/oauth/nous.ts +4 -1
  134. package/src/oauth/store.ts +6 -2
  135. package/src/oauth/token-guardian.ts +7 -0
  136. package/src/oauth/types.ts +9 -0
  137. package/src/oauth/workbuddy.ts +139 -0
  138. package/src/protocols/codecs/chat.ts +3 -3
  139. package/src/protocols/encoders/adapter-events.ts +26 -2
  140. package/src/protocols/encoders/chat.ts +55 -3
  141. package/src/providers/antigravity-quota-identity.ts +37 -0
  142. package/src/providers/base-url-choices.ts +8 -5
  143. package/src/providers/derive.ts +23 -3
  144. package/src/providers/mistral-transport.ts +37 -4
  145. package/src/providers/model-discovery.ts +2 -0
  146. package/src/providers/model-rename-fields.ts +1 -0
  147. package/src/providers/quota/account-cache.ts +3 -2
  148. package/src/providers/quota/antigravity.ts +27 -22
  149. package/src/providers/quota/report-cache.ts +13 -4
  150. package/src/providers/quota/vendor-probes-key.ts +60 -0
  151. package/src/providers/quota/workbuddy.ts +83 -0
  152. package/src/providers/quota-decision-antigravity.ts +64 -0
  153. package/src/providers/quota-decision-publication.ts +11 -0
  154. package/src/providers/quota-decision-snapshot.ts +21 -4
  155. package/src/providers/quota-types.ts +2 -0
  156. package/src/providers/quota.ts +3 -1
  157. package/src/providers/registry/entries-core.ts +9 -0
  158. package/src/providers/registry/entries-extended.ts +20 -7
  159. package/src/providers/registry/model-ids.ts +3 -0
  160. package/src/providers/registry/model-seeds.ts +27 -28
  161. package/src/providers/registry/types.ts +19 -1
  162. package/src/providers/resolved-model-policy.ts +4 -4
  163. package/src/providers/routed-config.ts +15 -0
  164. package/src/providers/stale-model-roster-migration.ts +83 -15
  165. package/src/providers/workbuddy-credit-balance.ts +81 -0
  166. package/src/providers/workbuddy-models.ts +37 -0
  167. package/src/providers/workbuddy.ts +60 -0
  168. package/src/quota-pool/assemble.ts +326 -0
  169. package/src/quota-pool/contract.ts +58 -0
  170. package/src/quota-pool/identity.ts +203 -0
  171. package/src/remote/features.ts +74 -0
  172. package/src/remote/protocol.ts +2 -0
  173. package/src/remote/served-by.ts +57 -0
  174. package/src/responses/parser.ts +7 -0
  175. package/src/responses/reasoning-envelope.ts +12 -0
  176. package/src/responses/reasoning-replay-cache.ts +18 -14
  177. package/src/router.ts +2 -0
  178. package/src/routing/analytics.ts +2 -3
  179. package/src/routing/fallback-policy.ts +290 -0
  180. package/src/routing/fallback-state.ts +179 -0
  181. package/src/server/audio-client.ts +2 -1
  182. package/src/server/audio-dictation.ts +2 -1
  183. package/src/server/audio-upstream.ts +2 -1
  184. package/src/server/auth-cors.ts +7 -5
  185. package/src/server/background-lifecycle.ts +4 -0
  186. package/src/server/chat-completions.ts +22 -2
  187. package/src/server/chat-native-reasoning.ts +10 -3
  188. package/src/server/chat-native.ts +13 -2
  189. package/src/server/claude-messages.ts +3 -0
  190. package/src/server/gui-session.ts +2 -1
  191. package/src/server/index/link-listener.ts +11 -1
  192. package/src/server/index/link-management.ts +47 -0
  193. package/src/server/index/link-relay-sessions.ts +20 -7
  194. package/src/server/index/optional-listeners.ts +11 -2
  195. package/src/server/index/serve-options.ts +19 -2
  196. package/src/server/live.ts +5 -0
  197. package/src/server/local-management-read-client.ts +3 -1
  198. package/src/server/management/combo-routes.ts +10 -4
  199. package/src/server/management/context.ts +1 -1
  200. package/src/server/management/fallback-routes.ts +75 -0
  201. package/src/server/management/link-enrollment.ts +21 -3
  202. package/src/server/management/link-route-state.ts +2 -0
  203. package/src/server/management/link-routes.ts +16 -2
  204. package/src/server/management/provider-routes.ts +1 -1
  205. package/src/server/management/quota-pool-routes.ts +24 -0
  206. package/src/server/management/route-registry.ts +3 -0
  207. package/src/server/management-api.ts +16 -0
  208. package/src/server/management-auth.ts +2 -1
  209. package/src/server/port-reclaim.ts +51 -9
  210. package/src/server/request-log-conversation.ts +49 -0
  211. package/src/server/request-log.ts +2 -0
  212. package/src/server/responses/agent-task-recovery.ts +1 -1
  213. package/src/server/responses/anthropic-thinking-recovery.ts +1 -0
  214. package/src/server/responses/compact.ts +12 -4
  215. package/src/server/responses/core-auth.ts +11 -1
  216. package/src/server/responses/core-combo.ts +125 -32
  217. package/src/server/responses/core-lifetime.ts +2 -2
  218. package/src/server/responses/core-normalize.ts +6 -3
  219. package/src/server/responses/core-opaque-recovery.ts +1 -0
  220. package/src/server/responses/core-options.ts +5 -0
  221. package/src/server/responses/core-replay.ts +2 -1
  222. package/src/server/responses/encrypted-payload.ts +3 -3
  223. package/src/server/responses/jev-combo-choice.ts +39 -0
  224. package/src/server/responses/jev-session-pin.ts +203 -0
  225. package/src/server/responses/main-hard-lock-routing.ts +54 -2
  226. package/src/server/responses/passthrough-delivery.ts +4 -0
  227. package/src/server/responses/policy-fallback.ts +44 -1
  228. package/src/server/responses/request-prepare.ts +19 -6
  229. package/src/server/responses/request-transport.ts +25 -6
  230. package/src/server/responses/run-turn-execution.ts +2 -4
  231. package/src/server/responses/sidecar-execution.ts +2 -4
  232. package/src/server/responses-phase-inference.ts +267 -0
  233. package/src/server/restart-replacement.ts +50 -2
  234. package/src/server/sse-payload-rewrite.ts +14 -1
  235. package/src/server/ws-bridge.ts +5 -0
  236. package/src/service/cli.ts +25 -2
  237. package/src/service/definition.ts +20 -8
  238. package/src/service/diagnostics.ts +20 -6
  239. package/src/service/status-json.ts +116 -0
  240. package/src/types/config.ts +13 -0
  241. package/src/types/fallback.ts +38 -0
  242. package/src/types/provider.ts +9 -1
  243. package/src/types/request.ts +1 -0
  244. package/src/types/wire.ts +1 -0
  245. package/src/update/codex-cli-update-launch-policy.d.mts +9 -0
  246. package/src/update/codex-cli-update-launch-policy.mjs +30 -0
  247. package/src/usage/jev-stats.ts +54 -14
  248. package/gui/dist/assets/App-dnkJ85te.css +0 -1
  249. package/gui/dist/assets/index-TmaHa54u.js +0 -94
  250. package/gui/dist/assets/quota-summary-B16x5DZZ.js +0 -1
@@ -0,0 +1,116 @@
1
+ import { diagnoseService, type ServiceDiagnostic } from "./diagnostics";
2
+ import { desktopLoginStatusSummary, diagnoseDesktopStartup } from "./desktop-startup";
3
+ import { confirmServiceServing } from "./health";
4
+ import { ownershipMutationLeaseStatusLine } from "./ownership-mutation-lease.mjs";
5
+ import { LABEL, TASK, serviceLogPath, serviceStatePaths } from "./state";
6
+ import { WINSW_SERVICE_ID } from "../lib/winsw";
7
+ import { runPassiveRead } from "../lib/passive-read";
8
+
9
+ /** The service manager holding the registration; `none` when nothing is registered. */
10
+ export type ServiceStatusBackend = "launchd" | "systemd" | "schtasks" | "winsw" | "none";
11
+
12
+ /**
13
+ * `ocx service status --json`. Additive within `schemaVersion: 1`.
14
+ *
15
+ * `running` is the service manager's own verdict — the value `ocx status --json` already
16
+ * reports as `startup.serviceRunning` — and `serving` says separately whether a proxy
17
+ * answers on the installed port, because a loaded job that never bound is the failure the
18
+ * text report exists to show.
19
+ */
20
+ export interface ServiceStatusJson {
21
+ schemaVersion: 1;
22
+ platform: NodeJS.Platform;
23
+ supported: boolean;
24
+ backend: ServiceStatusBackend;
25
+ label: string | null;
26
+ installed: boolean;
27
+ enabled: boolean;
28
+ running: boolean;
29
+ viable: boolean;
30
+ stale: boolean;
31
+ conflict: boolean;
32
+ serving: { probed: boolean; ok: boolean; port: number | null };
33
+ summary: string;
34
+ logPath: string;
35
+ ownershipLease: string | null;
36
+ desktopLogin: string | null;
37
+ }
38
+
39
+ export interface ServiceStatusJsonDeps {
40
+ platform?: NodeJS.Platform;
41
+ diagnose?: () => ServiceDiagnostic;
42
+ serving?: () => Promise<{ ok: boolean; port: number }>;
43
+ logPath?: () => string;
44
+ leaseLine?: () => string | null;
45
+ desktopLogin?: () => string | null;
46
+ }
47
+
48
+ export function serviceStatusBackend(diag: ServiceDiagnostic): ServiceStatusBackend {
49
+ if (!diag.supported || !diag.installed) return "none";
50
+ switch (diag.backend) {
51
+ case "launchd": return "launchd";
52
+ case "systemd": return "systemd";
53
+ case "scheduler": return "schtasks";
54
+ case "native": return "winsw";
55
+ default: return "none";
56
+ }
57
+ }
58
+
59
+ const BACKEND_LABEL: Record<ServiceStatusBackend, string | null> = {
60
+ launchd: LABEL,
61
+ systemd: `${TASK}.service`,
62
+ schtasks: TASK,
63
+ winsw: WINSW_SERVICE_ID,
64
+ none: null,
65
+ };
66
+
67
+ /**
68
+ * Passive by construction: it performs the reads `ocx service status` already performs
69
+ * (manager diagnosis, the loopback serving probe only when a registration exists, the
70
+ * ownership-lease and Desktop login lines) and nothing else. The caller answers before
71
+ * `platformOps()` is resolved, so no install, start, stop or repair path is reachable.
72
+ * The whole collection runs in a passive-read scope: the serving probe resolves the port
73
+ * through loadConfig(), which outside that scope hardens config modes and backs up a
74
+ * malformed config — writes an inventory read must not make.
75
+ */
76
+ export function collectServiceStatusJson(deps: ServiceStatusJsonDeps = {}): Promise<ServiceStatusJson> {
77
+ return runPassiveRead(() => collectPassively(deps));
78
+ }
79
+
80
+ async function collectPassively(deps: ServiceStatusJsonDeps): Promise<ServiceStatusJson> {
81
+ const diag = (deps.diagnose ?? diagnoseService)();
82
+ const backend = serviceStatusBackend(diag);
83
+ let serving: ServiceStatusJson["serving"] = { probed: false, ok: false, port: null };
84
+ if (diag.installed) {
85
+ // A probe that throws (config load, port resolution) must not cost the caller the whole
86
+ // document; it is reported as probed, not serving, with no port.
87
+ try {
88
+ const result = await (deps.serving ?? (() => confirmServiceServing({ timeoutMs: 1_500 })))();
89
+ serving = { probed: true, ok: result.ok, port: result.port };
90
+ } catch {
91
+ serving = { probed: true, ok: false, port: null };
92
+ }
93
+ }
94
+ const desktopLogin = deps.desktopLogin ?? (() => {
95
+ const desktop = diagnoseDesktopStartup();
96
+ return desktop ? desktopLoginStatusSummary(desktop) : null;
97
+ });
98
+ return {
99
+ schemaVersion: 1,
100
+ platform: deps.platform ?? process.platform,
101
+ supported: diag.supported,
102
+ backend,
103
+ label: BACKEND_LABEL[backend],
104
+ installed: diag.installed,
105
+ enabled: diag.enabled,
106
+ running: diag.running,
107
+ viable: diag.viable,
108
+ stale: diag.stale,
109
+ conflict: diag.conflict,
110
+ serving,
111
+ summary: diag.summary,
112
+ logPath: (deps.logPath ?? serviceLogPath)(),
113
+ ownershipLease: (deps.leaseLine ?? (() => ownershipMutationLeaseStatusLine(serviceStatePaths())))() ?? null,
114
+ desktopLogin: desktopLogin(),
115
+ };
116
+ }
@@ -2,7 +2,9 @@ import type { OcxProviderConfig } from "./provider";
2
2
  import type { CodexAccount } from "./accounts";
3
3
  import type { AnthropicAccountPoolConfig, OcxAccountPoolRotationStrategy } from "./anthropic-account-pool";
4
4
  import type { AnthropicInstanceId } from "../providers/anthropic-instance-id";
5
+ import type { OcxFallbackConfig } from "./fallback";
5
6
  export type { AnthropicAccountPoolConfig, AnthropicModelRoute, OcxAccountPoolRotationStrategy, OcxAccountPoolQuotaWindow } from "./anthropic-account-pool";
7
+ export type { OcxFallbackConfig, OcxFallbackDeviceConfig, OcxFallbackKind, OcxFallbackSwitches } from "./fallback";
6
8
 
7
9
  /** Public inference API exposure. Responses and Chat Completions are always served. */
8
10
  export interface OcxApiSurfacesConfig {
@@ -1361,6 +1363,11 @@ export interface OcxConfig {
1361
1363
  tokenGuardian?: OcxTokenGuardianConfig;
1362
1364
  /** Additional exact origins allowed for CORS (e.g. HTTPS or chrome-extension://<id>). Loopback origins are always allowed. */
1363
1365
  corsAllowOrigins?: string[];
1366
+ /**
1367
+ * OCX-11 fallback off: refuse model or provider fallback per scope, and record per-device local
1368
+ * fallback permission. Absent keeps every chain as it was. See src/routing/fallback-policy.ts.
1369
+ */
1370
+ fallback?: OcxFallbackConfig;
1364
1371
  }
1365
1372
 
1366
1373
  export type OcxComboStrategy = "failover" | "round-robin" | "random" | "least-used" | "reset-window" | "jev";
@@ -1489,6 +1496,12 @@ export interface OcxComboConfig {
1489
1496
  decisionQuotaSignals?: boolean | null;
1490
1497
  /** Partial 0..100 tier overrides; defaults limited 70/nearlyExhausted 90. null clears. */
1491
1498
  decisionQuotaTiers?: import("../combos/jev-quota-config").JevQuotaTiers | null;
1499
+ /**
1500
+ * `strategy: "jev"` only, default off: keep an explicit client session on the target JEV chose
1501
+ * for it while the session stays active, so the provider prompt cache stays warm (#7525).
1502
+ * `ttlMs` is the idle window after the last successful turn (60000..86400000). null clears.
1503
+ */
1504
+ sessionPin?: import("../combos/jev-session-pin-config").JevSessionPinConfig | null;
1492
1505
  }
1493
1506
 
1494
1507
  export type OcxRoutingUnknownEvidenceMode = "allow" | "penalize" | "exclude";
@@ -0,0 +1,38 @@
1
+ /**
2
+ * OCX-11 fallback policy (`fallback` in config.json). Every leaf is optional and absence keeps
3
+ * today's behavior: model and provider fallback allowed, local fallback not granted.
4
+ *
5
+ * Contract and precedence: `src/routing/fallback-policy.ts` and `structure/fallback-policy.md`.
6
+ */
7
+
8
+ /** A fallback kind this policy governs. Intra-pool account or key rotation is not one of them. */
9
+ export type OcxFallbackKind = "model" | "provider";
10
+
11
+ export interface OcxFallbackSwitches {
12
+ /** false refuses a hop to another model of the same provider. */
13
+ model?: boolean;
14
+ /** false refuses a hop to another provider. */
15
+ provider?: boolean;
16
+ }
17
+
18
+ export interface OcxFallbackDeviceConfig extends OcxFallbackSwitches {
19
+ /**
20
+ * Permission only: true tells GOAT it may move this device's work to the device's own
21
+ * providers while the Home is unreachable. OpenCodex itself never serves a Child or Hub client
22
+ * from local providers; absent or false means no permission.
23
+ */
24
+ localFallback?: boolean;
25
+ }
26
+
27
+ export interface OcxFallbackConfig extends OcxFallbackSwitches {
28
+ /**
29
+ * Keyed by the provider a hop leaves: the resolved provider of the target that just failed or
30
+ * is known unavailable. In combo [a, b, c] with providers.b.provider false, a to b is allowed and
31
+ * b to c is refused.
32
+ */
33
+ providers?: Record<string, OcxFallbackSwitches>;
34
+ /** Keyed by an exact public selector: combo/<id>, policy/<id>, provider/model or an alias. */
35
+ routes?: Record<string, OcxFallbackSwitches>;
36
+ /** Keyed by a Remote Link id (lnk_…) or a Hub-client data key id. Read on the Home. */
37
+ devices?: Record<string, OcxFallbackDeviceConfig>;
38
+ }
@@ -871,6 +871,14 @@ export interface OcxProviderConfig {
871
871
  * SSE/JSON; raw inspection state remains authoritative.
872
872
  */
873
873
  responsesSnapshotRepair?: boolean;
874
+ /**
875
+ * Infer the commentary/final_answer phase on assistant messages for Responses passthrough
876
+ * providers whose upstream omits it (e.g. third-party gateways). Codex desktop collapses
877
+ * commentary into progress rows; without this every narration renders fully expanded.
878
+ * Disabled by default. Applies in the client-facing SSE rewrite chain, including
879
+ * buffered JSON reconstructed from upstream SSE; ordinary upstream JSON is unchanged.
880
+ */
881
+ messagePhaseInference?: boolean;
874
882
  /**
875
883
  * Opt-in hosted-web-search bridge for this KEY-auth Responses passthrough provider (#3761).
876
884
  * Absent or disabled leaves the passthrough byte-identical to today.
@@ -1116,7 +1124,7 @@ export interface OcxProviderConfig {
1116
1124
  googleMode?: "ai-studio" | "vertex" | "cloud-code-assist";
1117
1125
  /** Google tool-schema compatibility policy. Omitted preserves compatible report-only behavior. */
1118
1126
  googleToolSchemaPolicy?: "compatible" | "reject-lossy";
1119
- /** Vertex AI GCP project id. API keys use it to select the project endpoint; ADC falls back to GOOGLE_CLOUD_PROJECT / GCLOUD_PROJECT env. */
1127
+ /** Vertex AI GCP project id. API keys use it to select the project endpoint; "auto" resolves each API key's own project. ADC falls back to GOOGLE_CLOUD_PROJECT / GCLOUD_PROJECT env. */
1120
1128
  project?: string;
1121
1129
  /** Vertex AI location, e.g. "us-central1" or "global". API-key project endpoints default to "global"; ADC falls back to GOOGLE_CLOUD_LOCATION env. */
1122
1130
  location?: string;
@@ -148,6 +148,7 @@ export interface OcxParsedRequest {
148
148
  _kiroAuthContext?: Pick<KiroOAuthMetadata, "profileArn" | "apiRegion" | "ssoRegion" | "authType">;
149
149
  /** Account-scoped Zed user identity paired with the long-lived access token. */
150
150
  _zedAuthContext?: { userId: string };
151
+ _workbuddyAuthContext?: import("../oauth/types").WorkBuddyOAuthMetadata;
151
152
  /** Provider-private continuation metadata resolved from the Responses previous_response_id chain. */
152
153
  _providerContinuation?: OcxProviderContinuationState;
153
154
  /** Persisted continuation considered only after the final physical route is known. */
package/src/types/wire.ts CHANGED
@@ -101,6 +101,7 @@ function normalizedEndpoint(value: unknown): string | undefined {
101
101
  * URL. The empty prefix covers every model id.
102
102
  */
103
103
  const CREDENTIAL_PINNED_ADAPTERS: Readonly<Record<string, Readonly<Record<string, string>>>> = Object.freeze({
104
+ "workbuddy-ai": Object.freeze({ "": "workbuddy-ai" }),
104
105
  "dsh-account": Object.freeze({ "": "dsh-account" }),
105
106
  });
106
107
 
@@ -16,3 +16,12 @@ export const CODEX_CLI_VERSION_MANAGER_ROOT_ENV_SLOTS: readonly [
16
16
  "VOLTA_HOME",
17
17
  ];
18
18
  export function isCodexCliUpdateInspectionArgv(argv: readonly string[]): boolean;
19
+ export const NODE_LAUNCH_CONTEXT_MAX_CHARS: number;
20
+ export function codexCliInspectionLookupSeeds(
21
+ platform: string,
22
+ home: string | null,
23
+ nodePath: string | null,
24
+ serializedContextChars: number,
25
+ ): { home: string | null; nodePath: string | null };
26
+ export const CODEX_CLI_TRANSPORT_ENV_KEYS: readonly string[];
27
+ export function captureCodexCliTransportEnv(env: Readonly<NodeJS.ProcessEnv>): Record<string, string>;
@@ -16,6 +16,16 @@ export const CODEX_CLI_VERSION_MANAGER_ROOT_ENV_SLOTS = Object.freeze([
16
16
  "VOLTA_HOME",
17
17
  ]);
18
18
 
19
+ export const CODEX_CLI_TRANSPORT_ENV_KEYS = Object.freeze([
20
+ "http_proxy", "https_proxy", "all_proxy", "no_proxy", "node_extra_ca_certs",
21
+ ]);
22
+
23
+ /** Preserve spelling, aliases and empty values before Bun loads project dotenv. */
24
+ export function captureCodexCliTransportEnv(env) {
25
+ return Object.fromEntries(Object.entries(env).filter(([name, value]) =>
26
+ CODEX_CLI_TRANSPORT_ENV_KEYS.includes(name.toLowerCase()) && typeof value === "string"));
27
+ }
28
+
19
29
  /**
20
30
  * Detect the read-only Codex CLI updater inspection namespace before Bun loads.
21
31
  * Keep this exact and argument-position based: malformed actions still inherit
@@ -28,3 +38,23 @@ export function isCodexCliUpdateInspectionArgv(argv) {
28
38
  const args = argv.slice(2).filter(value => !value.startsWith("--ocx-internal-launch-proof="));
29
39
  return args[0] === "system" && args[1] === "codex-cli-update";
30
40
  }
41
+
42
+ /** Serialized OCX_NODE_LAUNCH_CONTEXT ceiling; src/cli/launcher-context.ts refuses above it. */
43
+ export const NODE_LAUNCH_CONTEXT_MAX_CHARS = 64 * 1024;
44
+
45
+ /**
46
+ * The HOME/NODE_PATH seeds the update inspection's modeled Node lookup needs,
47
+ * bounded against the launch-context budget. They are POSIX-only: a Windows
48
+ * inspection never models the global lookup, so the seeds are not sent at all
49
+ * (and their env copies stay inherited for the child's own use). If carrying
50
+ * them would push the serialized context past the reader's limit they are
51
+ * dropped instead of taking the whole snapshot down with them — absent seeds
52
+ * leave only the global lane unverifiable, which fails the content
53
+ * observation closed while the selection context survives.
54
+ */
55
+ export function codexCliInspectionLookupSeeds(platform, home, nodePath, serializedContextChars) {
56
+ if (platform === "win32" || serializedContextChars > NODE_LAUNCH_CONTEXT_MAX_CHARS) {
57
+ return { home: null, nodePath: null };
58
+ }
59
+ return { home: home ?? null, nodePath: nodePath ?? null };
60
+ }
@@ -1,4 +1,5 @@
1
1
  import { normalizeJevQuotaSummary, type JevQuotaDecisionSummary } from "../combos/jev-quota-config";
2
+ import { JEV_SESSION_PIN_REASONS, type JevSessionPinReason } from "../combos/jev-session-pin-config";
2
3
  import type { OcxComboDefaultEffort } from "../types";
3
4
  import type { PersistedUsageEntry } from "./log";
4
5
  import { usageDisplayTotalTokens } from "./totals";
@@ -48,6 +49,10 @@ export interface PersistedJevDecisionV1 {
48
49
  outputTokens: number;
49
50
  totalTokens: number;
50
51
  };
52
+ /** The turn reused its session pin (#7525); no decision request was sent for it. */
53
+ pinned?: true;
54
+ /** `session` with `pinned`; otherwise why an existing session pin was released. */
55
+ pinReason?: JevSessionPinReason;
51
56
  }
52
57
 
53
58
  export interface JevStatsModelRow {
@@ -88,6 +93,8 @@ export interface JevStatsResponse {
88
93
  decisions: number;
89
94
  appliedDecisions: number;
90
95
  failOpenDecisions: number;
96
+ /** Requests that reused a session pin; they are not decisions and add only physical attempts. */
97
+ pinnedRequests: number;
91
98
  successfulRequests: number;
92
99
  requestsWithModelFallback: number;
93
100
  modelAttempts: number;
@@ -188,6 +195,13 @@ export function normalizePersistedJevDecision(value: unknown): PersistedJevDecis
188
195
  const backend = value.backend === "typesafe" || value.backend === "systemone" || value.backend === "model"
189
196
  ? value.backend
190
197
  : undefined;
198
+ const pinReason = typeof value.pinReason === "string"
199
+ && (JEV_SESSION_PIN_REASONS as readonly string[]).includes(value.pinReason)
200
+ ? value.pinReason as JevSessionPinReason
201
+ : undefined;
202
+ // A pinned turn always says so with `session`; any other pairing is not a record this writes.
203
+ const pinned = value.pinned === true && pinReason === "session";
204
+ if ((value.pinned === true || pinReason === "session") && !pinned) return undefined;
191
205
  return {
192
206
  version: 1,
193
207
  comboId,
@@ -203,6 +217,8 @@ export function normalizePersistedJevDecision(value: unknown): PersistedJevDecis
203
217
  ...(confidence !== undefined ? { confidence } : {}),
204
218
  ...(chosenProbability !== undefined ? { chosenProbability } : {}),
205
219
  ...(usage ? { usage } : {}),
220
+ ...(pinned ? { pinned: true as const } : {}),
221
+ ...(pinReason ? { pinReason } : {}),
206
222
  };
207
223
  }
208
224
 
@@ -210,6 +226,24 @@ interface MutableModelRow extends Omit<JevStatsModelRow, "efforts"> {
210
226
  effortCounts: Map<OcxComboDefaultEffort | null, number>;
211
227
  }
212
228
 
229
+ interface SentAttempt {
230
+ attempt: NonNullable<PersistedUsageEntry["attempts"]>[number];
231
+ provider: string;
232
+ model: string;
233
+ sendCount: number;
234
+ }
235
+
236
+ /** Attempts that physically sent, with bounded identities; zero-send rows never count. */
237
+ function sentAttempts(entry: PersistedUsageEntry): SentAttempt[] {
238
+ return (entry.attempts ?? []).flatMap(attempt => {
239
+ const sendCount = boundedCount(attempt.sendCount) ?? 0;
240
+ if (sendCount === 0) return [];
241
+ const provider = boundedIdentity(attempt.provider, MAX_TARGET_IDENTITY_CHARS);
242
+ const model = boundedIdentity(attempt.model, MAX_TARGET_IDENTITY_CHARS);
243
+ return provider && model ? [{ attempt, provider, model, sendCount }] : [];
244
+ });
245
+ }
246
+
213
247
  interface JevStatsAccumulatorOptions {
214
248
  comboId?: string | null;
215
249
  since?: number | null;
@@ -291,6 +325,7 @@ class StreamingJevStatsAccumulator implements JevStatsAccumulator {
291
325
  private snapshotWindowStart: number | null = null;
292
326
  private snapshotWindowEnd: number | null = null;
293
327
  private decisions = 0;
328
+ private pinnedRequests = 0;
294
329
  private appliedDecisions = 0;
295
330
  private successfulRequests = 0;
296
331
  private requestsWithModelFallback = 0;
@@ -358,6 +393,21 @@ class StreamingJevStatsAccumulator implements JevStatsAccumulator {
358
393
  || (this.until !== undefined && entry.timestamp > this.until)) return;
359
394
  const decision = normalizePersistedJevDecision(entry.jevDecision);
360
395
  if (!decision || (this.comboId !== null && decision.comboId !== this.comboId)) return;
396
+ const attempts = sentAttempts(entry);
397
+ // Request-level outcomes count every JEV-routed request, pinned or decided.
398
+ if (entry.status >= 200 && entry.status < 400) {
399
+ this.successfulRequests = saturatingAdd(this.successfulRequests, 1);
400
+ }
401
+ if (attempts.some(({ provider, model }) => provider !== decision.selected.provider
402
+ || model !== decision.selected.model)) {
403
+ this.requestsWithModelFallback = saturatingAdd(this.requestsWithModelFallback, 1);
404
+ }
405
+ this.addAttempts(attempts);
406
+ if (decision.pinned) {
407
+ // A session-pinned turn sent no decision request, so it stays out of every decision total.
408
+ this.pinnedRequests = saturatingAdd(this.pinnedRequests, 1);
409
+ return;
410
+ }
361
411
 
362
412
  const backend = decision.backend ?? "unknown";
363
413
  const backendRow = this.backends.get(backend)
@@ -369,9 +419,6 @@ class StreamingJevStatsAccumulator implements JevStatsAccumulator {
369
419
  this.averageLatencyMs = nextMean(this.averageLatencyMs, this.decisions, decision.latencyMs);
370
420
  this.decisions = saturatingAdd(this.decisions, 1);
371
421
  if (decision.gate === "apply") this.appliedDecisions = saturatingAdd(this.appliedDecisions, 1);
372
- if (entry.status >= 200 && entry.status < 400) {
373
- this.successfulRequests = saturatingAdd(this.successfulRequests, 1);
374
- }
375
422
  this.gates.set(decision.gate, saturatingAdd(this.gates.get(decision.gate) ?? 0, 1));
376
423
  if (decision.confidence !== undefined) {
377
424
  this.confidenceTotal = saturatingAdd(this.confidenceTotal, decision.confidence);
@@ -396,18 +443,9 @@ class StreamingJevStatsAccumulator implements JevStatsAccumulator {
396
443
  decision.selected.effort,
397
444
  saturatingAdd(selected.effortCounts.get(decision.selected.effort) ?? 0, 1),
398
445
  );
446
+ }
399
447
 
400
- const attempts = (entry.attempts ?? []).flatMap(attempt => {
401
- const sendCount = boundedCount(attempt.sendCount) ?? 0;
402
- if (sendCount === 0) return [];
403
- const provider = boundedIdentity(attempt.provider, MAX_TARGET_IDENTITY_CHARS);
404
- const model = boundedIdentity(attempt.model, MAX_TARGET_IDENTITY_CHARS);
405
- return provider && model ? [{ attempt, provider, model, sendCount }] : [];
406
- });
407
- if (attempts.some(({ provider, model }) => provider !== decision.selected.provider
408
- || model !== decision.selected.model)) {
409
- this.requestsWithModelFallback = saturatingAdd(this.requestsWithModelFallback, 1);
410
- }
448
+ private addAttempts(attempts: readonly SentAttempt[]): void {
411
449
  for (const { attempt, provider, model: modelId, sendCount } of attempts) {
412
450
  const model = this.rowFor(provider, modelId);
413
451
  model.attempts = saturatingAdd(model.attempts, sendCount);
@@ -449,6 +487,7 @@ class StreamingJevStatsAccumulator implements JevStatsAccumulator {
449
487
  cloned.snapshotWindowStart = this.snapshotWindowStart;
450
488
  cloned.snapshotWindowEnd = this.snapshotWindowEnd;
451
489
  cloned.decisions = this.decisions;
490
+ cloned.pinnedRequests = this.pinnedRequests;
452
491
  cloned.appliedDecisions = this.appliedDecisions;
453
492
  cloned.successfulRequests = this.successfulRequests;
454
493
  cloned.requestsWithModelFallback = this.requestsWithModelFallback;
@@ -492,6 +531,7 @@ class StreamingJevStatsAccumulator implements JevStatsAccumulator {
492
531
  decisions: this.decisions,
493
532
  appliedDecisions: this.appliedDecisions,
494
533
  failOpenDecisions: this.decisions - this.appliedDecisions,
534
+ pinnedRequests: this.pinnedRequests,
495
535
  successfulRequests: this.successfulRequests,
496
536
  requestsWithModelFallback: this.requestsWithModelFallback,
497
537
  modelAttempts: this.modelAttempts,
@@ -1 +0,0 @@
1
- .pws-completion-test{align-items:start;gap:var(--space-2);margin-top:var(--space-4);grid-template-columns:minmax(0,1fr) auto;display:grid}.pws-completion-test>h4,.pws-completion-result{grid-column:1/-1}.pws-completion-test>.custom-select{min-width:0}.pws-completion-result{overflow-wrap:anywhere}@media (width<=640px){.pws-completion-test{grid-template-columns:minmax(0,1fr)}}.codex-account-controls{flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:12px;min-width:0;display:flex}.codex-account-auto-switch{flex:none;align-items:center;gap:7px;min-width:0;display:flex}.codex-account-auto-switch-label{font-size:var(--text-label);color:var(--muted);font-weight:var(--weight-medium);white-space:nowrap}.codex-account-auto-switch-input-wrap{align-items:stretch;display:inline-flex;position:relative}.codex-account-auto-switch-input{width:76px;height:30px;font-size:var(--text-label);text-align:right;padding:4px 38px 4px 8px}.codex-account-auto-switch-unit{color:var(--muted);font-size:var(--text-label);pointer-events:none;position:absolute;top:50%;right:25px;transform:translateY(-50%)}.codex-account-auto-switch-input-wrap .ocx-stepper{width:22px}.codex-account-auto-switch-toggle{transform-origin:50%;margin-inline:-3px;transform:scale(.8)}.quota-row--codex-credits .quota-val{white-space:nowrap}.quota-compact:has(>.quota-row--codex-credits){grid-template-columns:minmax(34px,max-content) max-content minmax(34px,max-content) minmax(38px,max-content) minmax(58px,1fr) minmax(30px,max-content);column-gap:8px}.quota-compact:has(>.quota-row--codex-credits)>*{grid-column:1/-1}.quota-compact:has(>.quota-row--codex-credits)>.quota-row:not(.quota-row--credits){grid-template-columns:subgrid}.codex-auth-credits-toggle{align-items:center;gap:var(--space-2);white-space:nowrap;display:inline-flex}.codex-auth-credits-toggle__label{font-size:var(--text-label);color:var(--muted)}.codex-credit-spend{align-items:center;gap:var(--space-2);white-space:nowrap;display:inline-flex}.codex-credit-spend .toggle[aria-pressed=mixed] .toggle-knob{transform:translate(8px,-50%)}.codex-account-credits{align-items:center;gap:7px;display:inline-flex}.codex-account-credits__label{font-size:var(--text-label);color:var(--muted);font-weight:var(--weight-medium);white-space:nowrap}.codex-account-credits__toggle{transform-origin:50%;margin-inline:-3px;transform:scale(.8)}.anthropic-pool-card__native-messages{align-items:flex-start;gap:var(--space-2);margin-top:var(--space-3);cursor:pointer;display:flex}.anthropic-pool-card__native-messages>input{margin:var(--space-0-5) 0 0;accent-color:var(--accent)}.anthropic-pool-card__native-messages>input:focus-visible{outline:var(--space-0-5) solid var(--accent);outline-offset:var(--space-0-5)}.anthropic-pool-card__native-messages-copy{gap:var(--space-1);max-width:var(--prose-measure);display:grid}.anthropic-pool-card__native-messages:has(input:disabled){cursor:default}.anthropic-pool-card__save-warning{margin:var(--space-2) var(--space-4);color:var(--amber);font-size:var(--text-label);line-height:var(--leading-body)}.kiro-device-dialog{max-width:var(--prose-measure);border:1px solid var(--border);border-radius:var(--radius-lg);background:var(--surface);width:100%;color:var(--text);padding:var(--space-6)}.kiro-device-dialog::backdrop{background:var(--glass-rail)}.kiro-login-choices{gap:var(--space-2);margin-block:var(--space-4);display:grid}.kiro-login-choices .btn{min-height:var(--control-touch);justify-content:flex-start}.kiro-device-code{align-items:center;gap:var(--space-3);margin-block:var(--space-4);flex-wrap:wrap;display:flex}.kiro-device-code-value{font-family:var(--font-code);font-size:var(--text-title);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-wide)}.kiro-device-destination{overflow-wrap:anywhere;-webkit-user-select:all;user-select:all}.kiro-device-actions{justify-content:flex-end;gap:var(--space-2);margin-top:var(--space-4);display:flex}.model-price-dialog{flex-direction:column;max-height:calc(100dvh - 16vh);display:flex}.model-price-dialog>.modal-head,.model-price-dialog>.modal-actions{flex:none}.model-price-body{overscroll-behavior:contain;flex:auto;min-height:0;padding:2px;overflow-y:auto}.model-price-body .input{margin-bottom:6px}.model-price-body>.small{text-wrap:balance}.model-price-rates-grid{gap:var(--space-1-5) var(--space-3);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.model-price-rate-field{min-width:0}.model-price-rate-field .field-label{margin-bottom:var(--space-1)}.model-price-rate-field .input{margin-bottom:0}.model-price-mode{border:0;min-width:0;margin:12px 0 0;padding:0}.model-price-mode>legend{margin-bottom:5px;padding:0}.model-price-mode-option{cursor:pointer;align-items:flex-start;gap:8px;padding:6px 0;display:flex}.model-price-mode-option input{accent-color:var(--accent);margin-top:3px}.model-price-mode-option>span{gap:2px;min-width:0;display:grid}.model-price-band{border-top:1px solid var(--border);margin-top:12px;padding-top:12px}.model-price-band-title{font-weight:var(--weight-medium);margin-bottom:2px}.model-price-custom-controls{gap:var(--space-2);grid-template-columns:minmax(0,1fr) minmax(0,1fr);display:grid}.model-price-custom-controls .field-label{min-height:2.5em}.model-price-dialog .model-display-name-error{margin-top:8px}@media (width<=760px){.model-price-mode-option{min-height:44px}}@media (width<=560px){.model-price-rates-grid{grid-template-columns:1fr}.model-price-custom-controls{grid-template-columns:1fr;gap:4px}.model-price-custom-controls .field-label{min-height:0}}.advisor-editor-row{align-items:center;gap:var(--space-3);margin:var(--space-2) 0;display:flex}.advisor-editor-label{min-width:11rem}.advisor-editor-row>input,.advisor-editor-row>select{min-width:0;max-width:100%}.advisor-activity-table th,.advisor-activity-table td{white-space:nowrap}@media (width<=760px){.advisor-editor-row{align-items:flex-start;gap:var(--space-1);flex-direction:column}.advisor-editor-label{min-width:0}.advisor-editor-row>input,.advisor-editor-row>select{width:100%;min-height:44px}}.logs-stack-end.logs-decode-rate{white-space:normal;display:flex}.logs-decode-rate>span:first-child{white-space:nowrap}.logs-decode-rate .text-caption{font-family:var(--font-ui);overflow-wrap:break-word}.log-detail-grid.log-detail-performance-grid{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}.log-detail-performance-grid>span{overflow-wrap:break-word;min-width:0}.logs-decode-basis-hint{grid-column:1/-1}.main-inner:has(.claude-page .claudecode-workspace-shell){max-width:980px}.claude-panel-lead{color:var(--muted);font-size:var(--text-label);line-height:var(--leading-body);max-width:var(--prose-measure);margin:0}.claudecode-connection-card{margin-bottom:var(--space-4)}.claudecode-workspace-shell .setting-controls>.custom-select{text-align:right}.claude-page .claude-status-bar{z-index:8;box-shadow:var(--shadow-sm);flex-wrap:wrap;padding:6px 8px 6px 14px;position:sticky;top:12px}.claude-page .claude-status-bar>.claude-status-health{margin-left:0}.claude-status-actions{align-items:center;gap:var(--space-3);margin-left:auto;display:flex}.claude-desktop-roles{margin-bottom:var(--space-4);overflow:hidden}.claude-desktop-section-title{font-size:var(--text-control);font-weight:var(--weight-semibold);line-height:var(--leading-ui);color:var(--text);align-items:baseline;gap:8px;margin:0;display:flex}.claude-desktop-section-title .count{color:var(--muted);font-weight:var(--weight-medium);font-family:var(--font-code);font-size:var(--text-label)}.claude-desktop-roles>.claude-desktop-section-title{padding:12px 16px 4px}.claude-desktop-roles-scope{color:var(--muted);font-size:var(--text-label);margin:0;padding:0 16px 8px}.claude-desktop-roles .setting-controls>.custom-select{text-align:right}.claude-desktop-roles .setting-label{flex:12rem}.claude-desktop-roles .setting-controls{flex:0 auto;min-width:0;max-width:min(60%,28rem)}.claude-desktop-roles .setting-controls>.custom-select,.claude-desktop-roles .select-trigger{max-width:100%}.claude-desktop-roles .select-trigger>span:first-child{text-overflow:ellipsis;min-width:0;overflow:hidden}.claude-desktop-roles .select-trigger>svg{flex:none}.claude-role-option{vertical-align:bottom;min-width:0;max-width:100%;display:inline-flex}.claude-role-option__route{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.claude-role-option__status{white-space:pre;color:var(--muted);flex:none}@media (width<=760px){.claude-desktop-roles .setting-controls{max-width:100%}.claude-desktop-roles .setting-controls>.custom-select,.claude-desktop-roles .select-trigger{width:100%}.claude-desktop-roles .select-trigger{justify-content:space-between}}.claude-desktop-list{gap:var(--space-2);margin-bottom:var(--space-4);flex-direction:column;display:flex}.claude-desktop-list-head{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px;display:flex}.claude-desktop-list-hint{color:var(--muted);font-size:var(--text-label);margin:0}.claude-desktop-list-rows{margin:0;padding:0;list-style:none;overflow:hidden}.claude-desktop-list-row{border-top:1px solid var(--border-soft,var(--border));align-items:center;gap:10px;min-width:0;padding:8px 14px;display:flex}.claude-desktop-list-row:first-child{border-top:none}.claude-desktop-list-names{flex:1;align-items:baseline;gap:8px;min-width:0;display:flex}.claude-desktop-list-names strong,.claude-desktop-list-names code{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.claude-desktop-list-names strong{font-weight:var(--weight-medium)}.claude-desktop-list-names code{color:var(--muted);font-size:11px}.claude-row-quick{color:var(--accent)}.claude-desktop-advanced{margin-bottom:var(--space-4);border:1px solid var(--border);border-radius:var(--radius);background:var(--surface)}.claude-desktop-advanced>summary{cursor:pointer;flex-direction:column;gap:8px;padding:12px 14px;list-style:none;display:flex}.claude-desktop-advanced>summary::-webkit-details-marker{display:none}.claude-desktop-advanced-title{font-weight:var(--weight-semibold);color:var(--text);align-items:center;gap:6px;display:flex}.claude-desktop-advanced-chips{flex-wrap:wrap;gap:6px;display:flex}.claude-desktop-advanced-chip{border-radius:var(--radius-pill);background:var(--raised);max-width:100%;color:var(--muted);font-size:var(--text-label);align-items:baseline;gap:6px;padding:2px 10px;display:inline-flex}.claude-desktop-advanced-chip b{color:var(--text);font-weight:var(--weight-medium)}.claude-desktop-advanced-chip code{text-overflow:ellipsis;white-space:nowrap;max-width:220px;font-size:11px;overflow:hidden}.claude-desktop-advanced-count{font-family:var(--font-code);color:var(--faint);font-size:11px}.claude-desktop-advanced-desc{color:var(--muted);font-size:var(--text-label);margin:0;padding:0 14px 10px}.claude-desktop-advanced>.ocx-group-stack{padding:0 10px 10px}.remote-link-page{gap:var(--space-4);display:grid}.remote-link-page .page-head{align-items:flex-start}.remote-link-page .page-sub{max-width:var(--prose-measure);margin:0}.remote-link-panel{gap:var(--space-4);display:grid}.remote-link-off-preview{min-height:260px;position:relative;overflow:hidden}.remote-link-off-preview:after{content:"";pointer-events:none;-webkit-backdrop-filter:blur(7px);backdrop-filter:blur(7px);background:color-mix(in srgb, var(--bg) 24%, transparent);position:absolute;inset:0}.remote-link-preview-content{gap:var(--space-3);filter:grayscale();opacity:.64;-webkit-user-select:none;user-select:none;display:grid}.remote-link-preview-row{justify-content:space-between;align-items:center;gap:var(--space-3);padding:var(--space-3);border:1px solid var(--border-soft);border-radius:var(--radius-sm);background:var(--raised);display:flex}.remote-link-preview-lines{gap:var(--space-2);width:min(55%,300px);display:grid}.remote-link-preview-lines span{border-radius:var(--radius-pill);background:var(--border);height:8px;display:block}.remote-link-switch-row{z-index:1;justify-content:space-between;align-items:center;gap:var(--space-4);display:flex;position:relative}.remote-link-switch{width:var(--control-touch);height:var(--control-md);border:1px solid var(--border);border-radius:var(--radius-pill);background:var(--toggle-off-bg);cursor:pointer;padding:3px}.remote-link-switch:after{content:"";background:var(--toggle-dot-color);width:24px;height:24px;transition:transform var(--motion-fast) ease;border-radius:50%;display:block}.remote-link-switch[aria-checked=true]{background:var(--toggle-on-bg)}.remote-link-switch[aria-checked=true]:after{transform:translate(16px)}.remote-link-switch:focus-visible,.remote-link-role-card:focus-visible,.remote-link-sheet button:focus-visible,.remote-link-sheet a:focus-visible,.remote-link-row button:focus-visible{outline:2px solid var(--accent-ring);outline-offset:2px}.remote-link-role-grid{gap:var(--space-3);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.remote-link-role-card{min-height:132px;padding:var(--space-4);border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--raised);color:var(--text);text-align:left;cursor:pointer}.remote-link-role-card:hover{background:var(--raised-hover)}.remote-link-role-card[aria-disabled=true]{color:var(--muted);cursor:not-allowed;opacity:.72}.remote-link-role-card[aria-disabled=true]:hover{background:var(--raised)}.remote-link-role-card[aria-checked=true]{border-color:var(--text);box-shadow:0 0 0 1px var(--text)}.remote-link-role-card strong,.remote-link-role-card span{display:block}.remote-link-role-card span{margin-top:var(--space-2);color:var(--muted);font-size:var(--text-label)}.remote-link-toolbar{justify-content:space-between;align-items:center;gap:var(--space-3);display:flex}.remote-link-status{align-items:center;gap:var(--space-2);color:var(--muted);font-size:var(--text-label);display:inline-flex}.remote-link-status:before{content:"";background:var(--faint);border-radius:50%;flex:none;width:8px;height:8px}.remote-link-status[data-state=connected]:before{background:var(--green)}.remote-link-status[data-state=reconnecting]:before{background:var(--amber)}.remote-link-status[data-state=failed]:before{background:var(--red)}.remote-link-status[data-state=connecting]:before{background:var(--amber)}.remote-link-children{gap:var(--space-2);display:grid}.remote-link-row{gap:var(--space-3);padding:var(--space-3);border:1px solid var(--border-soft);border-radius:var(--radius-sm);background:var(--raised);grid-template-columns:minmax(0,1fr) auto;align-items:center;display:grid}.remote-link-row-main{gap:var(--space-2);min-width:0;display:grid}.remote-link-row-meta{gap:var(--space-3);color:var(--muted);font-size:var(--text-caption);flex-wrap:wrap;display:flex}.remote-link-row .btn{min-height:var(--control-touch)}.remote-link-error{color:var(--red)}.remote-link-hint{margin-top:var(--space-1);color:var(--muted);font-size:var(--text-label);overflow-wrap:anywhere;display:block}.remote-link-info{color:var(--muted);font-size:var(--text-label)}.remote-link-restart{gap:var(--space-2);padding:var(--space-4);border:1px solid var(--border-soft);border-radius:var(--radius-sm);background:var(--raised);display:grid}.remote-link-restart p{color:var(--muted);margin:0}.remote-link-fingerprint{padding:var(--space-3);overflow-wrap:anywhere;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--raised);font-family:var(--font-code);font-size:var(--text-label);margin:0}.remote-link-workspace-card{justify-content:space-between;align-items:center;gap:var(--space-4);display:flex}.remote-link-workspace-card p{margin:var(--space-1) 0 0;color:var(--muted);font-size:var(--text-label)}.remote-link-sheet{width:min(620px, calc(100vw - var(--space-4)));max-height:min(760px, calc(100dvh - var(--space-4)));padding:var(--space-6);border:1px solid var(--border);border-radius:var(--radius-lg);background:var(--bg);color:var(--text);box-shadow:var(--shadow);margin:auto;overflow:auto}.remote-link-sheet::backdrop{background:color-mix(in srgb, var(--text) 24%, transparent)}.remote-link-sheet-head{justify-content:space-between;align-items:flex-start;gap:var(--space-3);display:flex}.remote-link-sheet-head h3{font-size:var(--text-title);margin:0}.remote-link-sheet-body{gap:var(--space-4);margin-top:var(--space-5);display:grid}.remote-link-sheet pre{white-space:pre-wrap;overflow-wrap:anywhere;font-family:var(--font-code);font-size:var(--text-label)}.remote-link-candidates{gap:var(--space-2);display:grid}.remote-link-candidate{justify-content:space-between;align-items:center;gap:var(--space-3);min-height:var(--control-touch);padding:var(--space-2) var(--space-3);border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--raised);color:var(--text);cursor:pointer;text-align:left;display:flex}.remote-link-candidate:hover{background:var(--raised-hover)}.remote-link-form{gap:var(--space-2);display:grid}.remote-link-form .field-label{margin-bottom:0}.remote-link-sheet-actions{justify-content:flex-end;gap:var(--space-2);flex-wrap:wrap;display:flex}.remote-link-confirm-dialog{width:min(460px, calc(100vw - var(--space-4)));padding:var(--space-5);border:1px solid var(--border);border-radius:var(--radius);background:var(--bg);color:var(--text);box-shadow:var(--shadow);margin:auto}.remote-link-confirm-dialog::backdrop{background:color-mix(in srgb, var(--text) 24%, transparent)}.remote-link-confirm-dialog h3{font-size:var(--text-subtitle);margin:0}.remote-link-confirm-dialog p{color:var(--muted)}@media (width<=620px){.remote-link-role-grid{grid-template-columns:1fr}.remote-link-workspace-card,.remote-link-switch-row{flex-direction:column;align-items:stretch}.remote-link-switch{align-self:flex-start}.remote-link-sheet{width:calc(100vw - var(--space-2));border-radius:var(--radius-lg) var(--radius-lg) 0 0;max-height:88dvh;margin:auto auto 0}}@media (prefers-reduced-motion:reduce){.remote-link-page *,.remote-link-page :before,.remote-link-page :after{scroll-behavior:auto!important;transition:none!important;animation:none!important}}.quota-summary-bar{z-index:var(--z-sticky);border-bottom:1px solid var(--border);background:var(--bg);min-height:34px;font-size:var(--text-label);align-items:center;gap:12px;padding:4px 16px;display:flex;position:sticky;top:0}.quota-summary-scroller{flex:1;align-items:center;gap:2px;min-width:0;display:flex}.quota-summary-list{overscroll-behavior-x:contain;scrollbar-width:none;flex-wrap:nowrap;flex:auto;align-items:center;gap:4px;min-width:0;margin:0;padding:3px;list-style:none;display:flex;overflow:auto hidden}.quota-summary-list::-webkit-scrollbar{display:none}.quota-summary-item{flex:none}.quota-summary-scroll{border:1px solid var(--border);background:var(--surface);width:22px;height:22px;color:var(--text);font:inherit;cursor:pointer;border-radius:999px;flex:none;place-items:center;padding:0;font-weight:600;line-height:1;display:inline-grid}.quota-summary-scroll:hover:not([aria-disabled=true]){background:var(--bg)}.quota-summary-scroll[aria-disabled=true]{color:var(--muted);opacity:.45;cursor:default}.quota-summary-scroll:focus-visible{outline:2px solid var(--text);outline-offset:1px}.quota-summary-chip{color:var(--text);font:inherit;white-space:nowrap;cursor:pointer;background:0 0;border:1px solid #0000;border-radius:999px;align-items:center;gap:6px;padding:3px 8px;text-decoration:none;display:inline-flex}.quota-summary-chip:hover,.quota-summary-chip--open{border-color:var(--border)}.quota-summary-chip:focus-visible{outline:2px solid var(--text);outline-offset:1px}.quota-summary-name{color:var(--muted)}.quota-summary-pct{font-variant-numeric:tabular-nums;font-weight:600}.quota-summary-flag{width:14px;height:14px;color:var(--bg);font-size:var(--text-micro);border-radius:50%;place-items:center;font-weight:700;display:inline-grid}.quota-summary-item--warn .quota-summary-chip{background:var(--amber-soft)}.quota-summary-item--warn .quota-summary-pct{color:var(--amber)}.quota-summary-item--warn .quota-summary-flag{background:var(--amber)}.quota-summary-item--critical .quota-summary-chip{border-color:var(--red);background:var(--red-soft)}.quota-summary-item--critical .quota-summary-pct{color:var(--red)}.quota-summary-item--critical .quota-summary-flag{background:var(--red)}.quota-summary-popover{top:calc(var(--qs-pop-top,0) * 1px);left:calc(var(--qs-pop-left,0) * 1px);z-index:var(--z-popover);border:1px solid var(--border);background:var(--surface);border-radius:8px;min-width:260px;max-width:calc(100% - 16px);padding:10px 12px;position:fixed;box-shadow:0 8px 24px #00000024}.quota-summary-popover:before{content:"";left:calc(var(--qs-bridge-left,0) * 1px - 1px);right:calc(var(--qs-bridge-right,0) * 1px - 1px);height:calc((var(--qs-pop-gap,4) + 1) * 1px);position:absolute;bottom:100%}.quota-summary-popover-head{justify-content:space-between;align-items:center;gap:12px;margin-bottom:6px;display:flex}.quota-summary-badge{font-size:var(--text-caption);border-radius:999px;padding:1px 6px}.quota-summary-badge--warn{color:var(--amber);background:var(--amber-soft)}.quota-summary-badge--critical{color:var(--red);background:var(--red-soft)}.quota-summary-table{border-collapse:collapse;width:100%}.quota-summary-table th,.quota-summary-table td{text-align:left;white-space:nowrap;padding:3px 0;font-weight:400}.quota-summary-table td.quota-summary-row-pct{font-variant-numeric:tabular-nums;text-align:right;white-space:nowrap;padding:3px 12px;font-weight:600}.quota-summary-row-reset{color:var(--muted);white-space:nowrap}.quota-summary-table th[scope=row]{white-space:normal;overflow-wrap:anywhere}.quota-summary-row--warn .quota-summary-row-pct{color:var(--amber)}.quota-summary-row--critical .quota-summary-row-pct{color:var(--red)}.quota-summary-popover-foot{color:var(--muted);font-size:var(--text-caption);flex-wrap:wrap;justify-content:space-between;gap:4px 12px;margin-top:6px;display:flex}.quota-summary-updated{color:var(--muted);font-size:var(--text-caption);white-space:nowrap;flex-shrink:0}.quota-summary-popover-link{color:var(--text);text-underline-offset:2px;text-decoration:underline}.quota-summary-popover-link:focus-visible{outline:2px solid var(--text);outline-offset:1px}.quota-summary-updated--stale{color:var(--amber)}.main:has(>.quota-summary-bar):has(>.main-inner--combos .combos-workspace-shell){flex-direction:column;height:100dvh;display:flex}.main:has(>.quota-summary-bar)>.main-inner.main-inner--combos:has(.combos-workspace-shell){flex:auto;height:auto;min-height:0}@media (width<=760px){.quota-summary-bar{padding:4px 10px;position:static}.quota-summary-popover{top:calc(var(--qs-pop-top,0) * 1px);min-width:0;left:8px;right:8px}.main:has(>.quota-summary-bar):has(>.main-inner--combos .combos-workspace-shell){height:100%;min-height:0}}.section-switcher{border:1px solid var(--border);border-radius:var(--radius-pill);background:var(--surface);flex-wrap:wrap;gap:2px;max-width:100%;margin:0 0 16px;padding:2px;display:inline-flex}.section-switcher-btn{appearance:none;color:var(--muted);border-radius:var(--radius-pill);cursor:pointer;font:inherit;font-size:var(--text-control);white-space:nowrap;background:0 0;border:none;align-items:center;padding:5px 14px;display:inline-flex}.section-switcher-btn:hover{color:var(--text)}.section-switcher-btn[aria-current=page]{background:var(--raised);color:var(--text);font-weight:var(--weight-semibold)}.section-switcher-btn:focus-visible{outline:2px solid var(--accent-ring);outline-offset:1px}@media (width<=760px){.section-switcher{display:flex}.section-switcher-btn{min-height:var(--control-touch);flex:auto;justify-content:center}}.app{--titlebar-h:40px;--tl-inset:80px;--chrome-clear:52px}.app--macos{--chrome-clear:124px;--sidebar-column:max(232px, calc(var(--tl-inset) + 52px))}.sidebar-top{z-index:calc(var(--z-overlay,30) + 1);width:232px;height:var(--titlebar-h,40px);border-bottom:1px solid var(--border);background:var(--glass-rail);-webkit-backdrop-filter:var(--glass-blur);-webkit-user-select:none;user-select:none;align-items:center;padding:0 10px 0 14px;display:flex;position:fixed;top:0;left:0}.app--macos .sidebar-top{width:var(--sidebar-column);padding-left:var(--tl-inset,80px)}.sidebar{padding-top:calc(var(--titlebar-h,40px) + 18px)}.sidebar-collapse{border-radius:var(--radius-sm);width:28px;height:28px;color:var(--muted);cursor:pointer;transition:background var(--motion-fast), color var(--motion-fast);background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.sidebar-collapse:hover{background:var(--accent-soft);color:var(--text)}.sidebar-collapse:focus-visible{outline:2px solid var(--accent-ring);outline-offset:2px}.sidebar-collapse svg{width:18px;height:18px}.main-top{z-index:var(--z-sticky);height:var(--titlebar-h,40px);background:var(--bg);border-bottom:1px solid var(--border);-webkit-user-select:none;user-select:none;align-items:stretch;display:flex;position:sticky;top:0}.main-top>.quota-summary-bar{border-bottom:none;flex:auto;min-width:0;min-height:0;padding:0 16px;position:static}.main:has(>.main-top):has(>.main-inner--combos .combos-workspace-shell){flex-direction:column;height:100dvh;display:flex}.main:has(>.main-top)>.main-inner.main-inner--combos:has(.combos-workspace-shell){flex:auto;height:auto;min-height:0}@media (width>=761px){.app{transition:grid-template-columns .16s}.app--macos.app--reduced-zoom{transition:none}.app--macos:not(.app--nav-collapsed){grid-template-columns:var(--sidebar-column) minmax(0, 1fr)}.app.app--nav-collapsed{grid-template-columns:0 1fr}.app--nav-collapsed .sidebar{display:none}.app--nav-collapsed .main{grid-column:2}.app--nav-collapsed .sidebar-top{-webkit-backdrop-filter:none;background:0 0;border-bottom:none;width:auto;margin:0;padding:0 12px}.app--macos.app--nav-collapsed .sidebar-top{padding-left:var(--tl-inset,80px)}.app--nav-collapsed .main-top,.app--nav-collapsed .main>.quota-summary-bar{padding-left:var(--chrome-clear,52px)}}@media (width<=760px){.sidebar-top{display:none}.main-top{z-index:auto;position:static}.main:has(>.main-top):has(>.main-inner--combos .combos-workspace-shell){height:100%;min-height:0}.app--macos .mobile-topbar{padding-left:var(--tl-inset,80px)}.app--macos .sidebar.open{padding-top:calc(var(--titlebar-h,40px) + 18px)}}@media (width<=320px){.app--macos .mobile-topbar{flex-wrap:wrap}.app--macos .mobile-topbar-actions{width:calc(100% + var(--tl-inset,80px));margin-left:calc(-1 * var(--tl-inset,80px));justify-content:flex-end}}