@bitkyc08/opencodex 2.68.0-preview.20260927 → 2.69.0-preview.20260928

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 (175) hide show
  1. package/bin/ocx.mjs +25 -17
  2. package/gui/dist/assets/App-cDPyCb_V.js +51 -0
  3. package/gui/dist/assets/{Tray-DFnMiyD0.js → Tray-H1IAB8mN.js} +1 -1
  4. package/gui/dist/assets/index-BF9QnKO_.css +1 -0
  5. package/gui/dist/assets/index-CI6YTp8z.js +86 -0
  6. package/gui/dist/assets/{quota-summary-i84eOU3o.js → quota-summary-CutOlPu2.js} +1 -1
  7. package/gui/dist/index.html +2 -2
  8. package/gui/dist/provider-icons/factory-droid.svg +8 -0
  9. package/package.json +1 -1
  10. package/src/adapters/anthropic-output-schema.ts +109 -11
  11. package/src/adapters/coding-agent/protocol.ts +106 -11
  12. package/src/adapters/coding-agent/turn.ts +23 -18
  13. package/src/adapters/google-errors.ts +16 -1
  14. package/src/adapters/google-http.ts +14 -0
  15. package/src/adapters/openai-chat/passthrough.ts +2 -0
  16. package/src/adapters/openai-chat/summary-budget.ts +72 -0
  17. package/src/adapters/openai-chat/tool-call-id-remint.ts +11 -2
  18. package/src/adapters/openai-chat.ts +6 -9
  19. package/src/bridge/response-json.ts +3 -1
  20. package/src/bridge/sse.ts +3 -1
  21. package/src/claude/desktop-picker.ts +6 -2
  22. package/src/claude/desktop-system-proxy.ts +155 -0
  23. package/src/claude/inbound-model-options.ts +10 -2
  24. package/src/claude/inbound.ts +1 -1
  25. package/src/claude/intercept/picker-ca-cleanup.ts +31 -0
  26. package/src/claude/intercept/picker-ca.ts +283 -83
  27. package/src/claude/intercept/picker-models.ts +4 -1
  28. package/src/claude/intercept/picker-trust.ts +15 -2
  29. package/src/claude/intercept/runtime.ts +99 -32
  30. package/src/cli/account-api.ts +5 -2
  31. package/src/cli/account-extended.ts +47 -11
  32. package/src/cli/account-target.ts +12 -7
  33. package/src/cli/account.ts +47 -3
  34. package/src/cli/capabilities.ts +33 -7
  35. package/src/cli/claude-agent-startup-sync.ts +2 -0
  36. package/src/cli/claude-desktop.ts +37 -13
  37. package/src/cli/cross-home-owner.ts +138 -0
  38. package/src/cli/dispatch.ts +1 -1
  39. package/src/cli/doctor.ts +18 -8
  40. package/src/cli/ensure-desired-integrations.ts +5 -0
  41. package/src/cli/export-command.ts +6 -6
  42. package/src/cli/help.ts +1 -1
  43. package/src/cli/index.ts +15 -18
  44. package/src/cli/integrations.ts +8 -1
  45. package/src/cli/models-runtime-subcommands.ts +1 -0
  46. package/src/cli/models-runtime.ts +106 -4
  47. package/src/cli/models.ts +6 -1
  48. package/src/cli/opencode.ts +2 -83
  49. package/src/cli/registry.ts +3 -2
  50. package/src/cli/status.ts +86 -7
  51. package/src/client/connect.ts +24 -10
  52. package/src/client/link-ingress.ts +3 -1
  53. package/src/client/link-join.ts +76 -9
  54. package/src/client/link-relay-transport.ts +184 -0
  55. package/src/client/link-relay.ts +16 -4
  56. package/src/client/machine-listener.ts +2 -0
  57. package/src/clients/config-export/constants.ts +8 -0
  58. package/src/clients/config-export/contracts.ts +13 -1
  59. package/src/clients/config-export/droid.ts +71 -0
  60. package/src/clients/config-export/kilo.ts +116 -0
  61. package/src/clients/config-export.ts +33 -2
  62. package/src/codex/auth-api/routes.ts +1 -1
  63. package/src/codex/catalog/access-programs.ts +14 -2
  64. package/src/codex/catalog/provider-models.ts +1 -1
  65. package/src/codex/catalog-auto-refresh.ts +122 -0
  66. package/src/codex/config-drift-heal.ts +80 -0
  67. package/src/codex/desktop-switches.ts +7 -5
  68. package/src/codex/inject/bounded-config-reader.ts +69 -0
  69. package/src/codex/inject/config-toml.ts +31 -3
  70. package/src/codex/inject/routing-target.ts +6 -1
  71. package/src/codex/inject.ts +4 -0
  72. package/src/codex/model-entitlements.ts +32 -5
  73. package/src/codex/native-profile-startup.ts +17 -5
  74. package/src/codex/project-config-warnings.ts +72 -21
  75. package/src/codex/quota-auto-refresh-state.ts +8 -2
  76. package/src/codex/quota-auto-refresh.ts +80 -9
  77. package/src/codex/sync.ts +10 -0
  78. package/src/combos/failover.ts +4 -1
  79. package/src/combos/jev.ts +15 -4
  80. package/src/combos/types.ts +16 -0
  81. package/src/config/diagnostics.ts +19 -1
  82. package/src/config/load-degrade.ts +29 -0
  83. package/src/config/macos-system-proxy.ts +114 -0
  84. package/src/config/proxy-env.ts +67 -20
  85. package/src/config/schema/config-schema.ts +15 -0
  86. package/src/config/schema/leaf-validators.ts +23 -0
  87. package/src/config.ts +2 -0
  88. package/src/generated/compatibility-version.json +220 -144
  89. package/src/integrations/catalog-refresh.ts +1 -1
  90. package/src/integrations/config-io.ts +12 -3
  91. package/src/integrations/droid-settings.ts +65 -0
  92. package/src/integrations/kilo-candidates.ts +30 -0
  93. package/src/integrations/mutation-plan.ts +62 -7
  94. package/src/integrations/native/ownership-preflight.ts +46 -12
  95. package/src/integrations/registry.ts +136 -7
  96. package/src/integrations/state.ts +52 -4
  97. package/src/integrations/target.ts +3 -0
  98. package/src/integrations/writer.ts +55 -14
  99. package/src/lib/account-selection-events.ts +14 -0
  100. package/src/lib/jsonc.ts +110 -0
  101. package/src/lib/standalone.ts +9 -1
  102. package/src/lib/windows-system-proxy.ts +101 -1
  103. package/src/link/relay-auth.ts +29 -0
  104. package/src/link/ssh-argv.ts +6 -3
  105. package/src/link/ssh-runner.ts +5 -5
  106. package/src/oauth/generic-account-failover.ts +52 -8
  107. package/src/oauth/index.ts +37 -14
  108. package/src/oauth/store.ts +96 -11
  109. package/src/oauth/token-guardian.ts +7 -3
  110. package/src/oauth/types.ts +2 -0
  111. package/src/plugins/loader.ts +5 -3
  112. package/src/providers/fastwire.ts +3 -0
  113. package/src/providers/key-failover.ts +13 -5
  114. package/src/providers/kiro-model-catalog.ts +4 -3
  115. package/src/providers/model-rename-fields.ts +1 -0
  116. package/src/providers/openai-tiers-destination.ts +9 -0
  117. package/src/providers/quota/account-cache.ts +17 -3
  118. package/src/providers/quota/vendor-probes-oauth.ts +2 -0
  119. package/src/providers/quota.ts +2 -2
  120. package/src/responses/code-mode-helper-compat.ts +14 -1
  121. package/src/responses/custom-tool-compat.ts +1 -1
  122. package/src/server/auth-cors.ts +1 -0
  123. package/src/server/images.ts +70 -20
  124. package/src/server/index/link-listener.ts +20 -3
  125. package/src/server/index/link-relay-sessions.ts +138 -0
  126. package/src/server/index/optional-listeners.ts +11 -1
  127. package/src/server/index/serve-options.ts +12 -0
  128. package/src/server/inference/client-encoder-delivery.ts +1 -0
  129. package/src/server/local-management-read-client.ts +21 -2
  130. package/src/server/management/agent-settings-routes.ts +19 -5
  131. package/src/server/management/combo-routes.ts +5 -3
  132. package/src/server/management/config-routes.ts +32 -6
  133. package/src/server/management/model-routes.ts +232 -7
  134. package/src/server/management/model-rows.ts +199 -1
  135. package/src/server/management/oauth-account-routes.ts +63 -3
  136. package/src/server/management/route-registry.ts +2 -0
  137. package/src/server/port-reclaim.ts +170 -32
  138. package/src/server/responses/adapter-delivery.ts +6 -4
  139. package/src/server/responses/adapter-dispatch.ts +72 -2
  140. package/src/server/responses/core-combo.ts +6 -1
  141. package/src/server/responses/core-normalize.ts +19 -4
  142. package/src/server/responses/core-options.ts +2 -0
  143. package/src/server/responses/memory-models.ts +169 -0
  144. package/src/server/responses/passthrough-delivery.ts +4 -0
  145. package/src/server/responses/passthrough-dispatch.ts +10 -0
  146. package/src/server/responses/request-prepare.ts +86 -4
  147. package/src/server/responses/request-transport.ts +4 -0
  148. package/src/server/responses/run-turn-execution.ts +5 -2
  149. package/src/server/responses/shadow-target-availability.ts +17 -4
  150. package/src/server/responses/sidecar-execution.ts +18 -2
  151. package/src/server/responses/skills-snapshot.ts +239 -0
  152. package/src/server/responses-undeclared-tool-guard.ts +43 -6
  153. package/src/server/search.ts +1 -1
  154. package/src/service/guards.ts +4 -5
  155. package/src/service/state.ts +39 -2
  156. package/src/service.ts +2 -2
  157. package/src/types/config.ts +46 -4
  158. package/src/types/provider.ts +9 -0
  159. package/src/types/request.ts +6 -0
  160. package/src/types/tools.ts +38 -8
  161. package/src/types.ts +3 -0
  162. package/src/update/async-check.ts +5 -1
  163. package/src/update/index.ts +30 -12
  164. package/src/update/job.ts +3 -1
  165. package/src/update/pnpm-read-policy.d.mts +4 -0
  166. package/src/update/pnpm-read-policy.mjs +44 -0
  167. package/src/update/worker-launch.ts +152 -11
  168. package/src/usage/cost.ts +6 -3
  169. package/src/usage/log.ts +4 -0
  170. package/src/web-search/backends.ts +2 -2
  171. package/src/web-search/devin-executor.ts +1 -1
  172. package/src/web-search/sidecar-providers.ts +5 -5
  173. package/gui/dist/assets/App-B5-cAEd2.js +0 -51
  174. package/gui/dist/assets/index-BDUBS8PW.css +0 -1
  175. package/gui/dist/assets/index-BcXblvem.js +0 -86
@@ -0,0 +1,114 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { isIP } from "node:net";
3
+
4
+ export type MacOSProxyReader = () => string | null;
5
+ export type MacOSSystemProxyResult =
6
+ | { kind: "proxy"; httpUrl?: string; httpsUrl?: string; exceptions: string[]; droppedLinkLocal: boolean }
7
+ | { kind: "disabled" | "unreadable" | "unsafe-exceptions" };
8
+
9
+ function readScutilProxy(): string {
10
+ return execFileSync("/usr/sbin/scutil", ["--proxy"], {
11
+ encoding: "utf8",
12
+ stdio: ["ignore", "pipe", "ignore"],
13
+ timeout: 2_000,
14
+ maxBuffer: 64 * 1024,
15
+ });
16
+ }
17
+
18
+ function proxyUrl(host: string | undefined, port: string | undefined): string | undefined {
19
+ if (!host || !port || !/^\d+$/.test(port) || +port < 1 || +port > 65535) return undefined;
20
+ const bareHost = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
21
+ if (!isIP(bareHost) && !/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?\.?$/i.test(host)) return undefined;
22
+ try {
23
+ return new URL(`http://${isIP(bareHost) === 6 ? `[${bareHost}]` : host}:${port}`).origin;
24
+ } catch {
25
+ return undefined;
26
+ }
27
+ }
28
+
29
+ // Bun matches a leading-dot entry at DNS-label boundaries and also bypasses the
30
+ // bare apex. Translating "*.local" to ".local" therefore widens only to "local";
31
+ // other glob shapes are refused. Bun cannot represent the default link-local
32
+ // CIDRs, so they are dropped with a diagnostic instead of blocking discovery.
33
+ // null means one of those exact ranges was dropped; undefined refuses discovery.
34
+ function translateException(value: string): string | null | undefined {
35
+ if (value === "*") return value;
36
+ if (value === "169.254/16" || value === "169.254.0.0/16") return null;
37
+ const ipv6Range = /^(?:\[([0-9a-f:]+)\]|([0-9a-f:]+))\/10$/i.exec(value);
38
+ const ipv6Base = ipv6Range?.[1] ?? ipv6Range?.[2];
39
+ if (ipv6Base && isIP(ipv6Base) === 6
40
+ && new URL(`http://[${ipv6Base}]`).hostname === "[fe80::]") return null;
41
+ if (value.startsWith("*.")) {
42
+ const domain = value.slice(2);
43
+ if (domain.length > 253 || !domain.split(".").every(label => label.length <= 63
44
+ && /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test(label))) return undefined;
45
+ return `.${domain.toLowerCase()}`;
46
+ }
47
+ if (isIP(value) === 4) {
48
+ const canonical = new URL(`http://${value}`).hostname;
49
+ return value === canonical ? value : undefined;
50
+ }
51
+ const bare = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
52
+ return isIP(bare) === 6 ? new URL(`http://[${bare}]`).hostname : undefined;
53
+ }
54
+
55
+ /** Read only the global dictionary; scoped service dictionaries do not apply globally. */
56
+ export function readMacOSSystemProxy(reader: MacOSProxyReader = readScutilProxy): MacOSSystemProxyResult {
57
+ try {
58
+ const output = reader();
59
+ if (!output || output.length > 64 * 1024 || !/^\s*<dictionary>\s*\{/.test(output)) return { kind: "unreadable" };
60
+ const values = new Map<string, string>();
61
+ const exceptions: string[] = [];
62
+ let depth = 0;
63
+ let inExceptions = false;
64
+ for (const row of output.split(/\r?\n/)) {
65
+ const line = row.trim();
66
+ if (line.endsWith("{")) {
67
+ if (inExceptions) return { kind: "unreadable" };
68
+ if (depth === 1) {
69
+ inExceptions = /^ExceptionsList\s*:\s*<array>\s*\{$/.test(line);
70
+ if (line.startsWith("ExceptionsList") && !inExceptions) return { kind: "unreadable" };
71
+ }
72
+ depth++;
73
+ } else if (line === "}") {
74
+ if (--depth < 0) return { kind: "unreadable" };
75
+ if (depth === 1) inExceptions = false;
76
+ } else {
77
+ const entry = /^([^:]+)\s*:\s*(.*?)\s*$/.exec(line);
78
+ if (!entry) {
79
+ if (inExceptions) return { kind: "unreadable" };
80
+ continue;
81
+ }
82
+ if (depth === 1) {
83
+ if (entry[1]!.trim() === "ExceptionsList") return { kind: "unreadable" };
84
+ values.set(entry[1]!.trim(), entry[2]!);
85
+ }
86
+ if (depth === 2 && inExceptions) {
87
+ if (!/^\d+$/.test(entry[1]!.trim())) return { kind: "unreadable" };
88
+ exceptions.push(entry[2]!);
89
+ }
90
+ }
91
+ }
92
+ if (depth !== 0) return { kind: "unreadable" };
93
+ if (values.get("ExcludeSimpleHostnames") === "1" || values.get("ProxyAutoConfigEnable") === "1"
94
+ || values.get("ProxyAutoDiscoveryEnable") === "1") return { kind: "unsafe-exceptions" };
95
+ for (const key of ["HTTPEnable", "HTTPSEnable", "ExcludeSimpleHostnames", "ProxyAutoConfigEnable", "ProxyAutoDiscoveryEnable"]) {
96
+ const value = values.get(key);
97
+ if (value !== undefined && value !== "0" && value !== "1") return { kind: "unreadable" };
98
+ }
99
+ const translated = exceptions.map(translateException);
100
+ if (translated.some(value => value === undefined)) return { kind: "unsafe-exceptions" };
101
+ const httpEnabled = values.get("HTTPEnable") === "1";
102
+ const httpsEnabled = values.get("HTTPSEnable") === "1";
103
+ const httpUrl = httpEnabled ? proxyUrl(values.get("HTTPProxy"), values.get("HTTPPort")) : undefined;
104
+ const httpsUrl = httpsEnabled ? proxyUrl(values.get("HTTPSProxy"), values.get("HTTPSPort")) : undefined;
105
+ if ((httpEnabled && !httpUrl) || (httpsEnabled && !httpsUrl)) return { kind: "unreadable" };
106
+ return httpUrl || httpsUrl
107
+ ? { kind: "proxy", httpUrl, httpsUrl,
108
+ exceptions: translated.filter((value): value is string => typeof value === "string"),
109
+ droppedLinkLocal: translated.includes(null) }
110
+ : { kind: "disabled" };
111
+ } catch {
112
+ return { kind: "unreadable" };
113
+ }
114
+ }
@@ -5,6 +5,7 @@ import { DEFAULT_SUBAGENT_MODELS, SUBAGENT_MODELS_VERSION } from "./subagent-mod
5
5
  import { MULTI_AGENT_SURFACE_ADVISORY_VERSION } from "./multi-agent-surface";
6
6
  import { DEFAULT_APP_OWNED_MEMORY_BUDGET_BYTES } from "../lib/app-owned-memory";
7
7
  import { describeProxyForLog, readWindowsSystemProxy, type WindowsProxyRegistryReader } from "../lib/windows-system-proxy";
8
+ import { readMacOSSystemProxy, type MacOSProxyReader } from "./macos-system-proxy";
8
9
  import { OPENAI_PROVIDER_TIER_VERSION, type OcxConfig } from "../types";
9
10
  import type { OcxRuntimeRole } from "../types/config";
10
11
 
@@ -152,6 +153,23 @@ function mergeNoProxyEntries(configured: readonly string[] = [], loopback: reado
152
153
  }
153
154
  }
154
155
 
156
+ function configuredNoProxyEntries(config: OcxConfig): string[] {
157
+ const raw = config.noProxy;
158
+ let entries: string[];
159
+ if (Array.isArray(raw)) {
160
+ if (raw.some(entry => typeof entry !== "string")) warnProxyConfigDiscardOnce("noProxyElements");
161
+ entries = raw.filter((entry): entry is string => typeof entry === "string");
162
+ } else if (typeof raw === "string") {
163
+ const resolved = resolveEnvValue(raw);
164
+ if (raw && resolved === undefined) warnProxyConfigDiscardOnce("noProxy");
165
+ entries = (resolved ?? "").split(",");
166
+ } else {
167
+ if (raw !== undefined) warnProxyConfigDiscardOnce("noProxy");
168
+ entries = [];
169
+ }
170
+ return entries.map(entry => entry.trim()).filter(Boolean);
171
+ }
172
+
155
173
  /**
156
174
  * Mirror `config.proxy` into HTTP(S)_PROXY env vars. Bun fetch consumes them natively; transports
157
175
  * such as the ChatGPT upstream WebSocket select the same environment explicitly. User-set HTTP(S)_PROXY
@@ -172,7 +190,7 @@ export function applyProxyEnv(config: OcxConfig, announce = false): void {
172
190
  /** Test seam for `proxy: "auto"`: the registry reader and platform are injectable. */
173
191
  export function applyProxyEnvWith(
174
192
  config: OcxConfig,
175
- auto: { reader?: WindowsProxyRegistryReader; platform?: NodeJS.Platform } = {},
193
+ auto: { reader?: WindowsProxyRegistryReader; macOSReader?: MacOSProxyReader; platform?: NodeJS.Platform } = {},
176
194
  ): void {
177
195
  // `proxy` and `noProxy` are not declared in the top-level schema, which ends in
178
196
  // `.passthrough()`, so whatever is on disk arrives here verbatim. A non-string value
@@ -194,6 +212,52 @@ export function applyProxyEnvWith(
194
212
  return;
195
213
  }
196
214
  if (proxy.trim().toLowerCase() === "auto") {
215
+ if ((auto.platform ?? process.platform) === "darwin") {
216
+ // An inherited scheme or ALL_PROXY route owns both its proxy and bypass
217
+ // variables. Combining it with system exceptions would change that route.
218
+ if (["HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy", "ALL_PROXY", "all_proxy"]
219
+ .some(key => process.env[key]?.trim())) {
220
+ console.log('[opencodex] proxy "auto": existing proxy environment wins; macOS system proxy not consulted');
221
+ configureSocks5Fetch();
222
+ return;
223
+ }
224
+ const found = readMacOSSystemProxy(auto.macOSReader);
225
+ if (found.kind !== "proxy") {
226
+ const reason = found.kind === "unsafe-exceptions"
227
+ ? "macOS exceptions cannot be safely translated; discovery refused"
228
+ : found.kind === "disabled"
229
+ ? "macOS system proxy is disabled"
230
+ : "macOS proxy settings could not be read";
231
+ console.log(`[opencodex] proxy "auto": ${reason}; proxy environment unchanged`);
232
+ return;
233
+ }
234
+ const configured = configuredNoProxyEntries(config);
235
+ const inheritedLowercase = process.env.no_proxy?.trim();
236
+ if (inheritedLowercase && configured.some(host => /^localhost\.?$/i.test(host))) {
237
+ console.log('[opencodex] proxy "auto": configured noProxy "localhost" cannot be represented exactly for Bun while an inherited no_proxy is set; discovery refused');
238
+ return;
239
+ }
240
+ const origins = [
241
+ found.httpUrl && `HTTP ${describeProxyForLog(found.httpUrl)}`,
242
+ found.httpsUrl && `HTTPS ${describeProxyForLog(found.httpsUrl)}`,
243
+ ].filter(Boolean).join(", ");
244
+ console.log(`[opencodex] proxy "auto": using macOS system proxy ${origins}`);
245
+ if (found.droppedLinkLocal) {
246
+ console.log('[opencodex] proxy "auto": link-local IP literals use the proxy; macOS link-local range exceptions are not expressible');
247
+ }
248
+ if (found.httpUrl) process.env.HTTP_PROXY = found.httpUrl;
249
+ if (found.httpsUrl) process.env.HTTPS_PROXY = found.httpsUrl;
250
+ // Bun gives non-empty lowercase no_proxy priority over NO_PROXY. Before
251
+ // discovery there was no proxy, so an ordinary configured name and its
252
+ // subdomains can stay direct in both paths. Bare localhost is refused
253
+ // above when lowercase is inherited: Bun cannot match it exactly there.
254
+ mergeNoProxyEntries([...configured, ...found.exceptions], LOOPBACK_ADDRESS_NO_PROXY);
255
+ if (process.env.no_proxy?.trim()) {
256
+ process.env.no_proxy = withNoProxyEntries(process.env.no_proxy, [...configured, ...found.exceptions], LOOPBACK_ADDRESS_NO_PROXY);
257
+ }
258
+ configureSocks5Fetch();
259
+ return;
260
+ }
197
261
  // #1525 slice 1: one startup read of the Windows static proxy. Never copy the literal
198
262
  // "auto" into HTTP_PROXY; every non-proxy outcome leaves outbound routing as it was.
199
263
  if (process.env.HTTP_PROXY?.trim() || process.env.http_proxy?.trim()
@@ -213,7 +277,7 @@ export function applyProxyEnvWith(
213
277
  proxy = undefined;
214
278
  } else {
215
279
  const reason = found.kind === "unsupported"
216
- ? "only Windows system proxy discovery is supported; using direct egress on this OS"
280
+ ? "only Windows and macOS system proxy discovery is supported; using direct egress on this OS"
217
281
  : found.kind === "disabled"
218
282
  ? "Windows system proxy is disabled; using direct egress"
219
283
  : found.kind === "socks-only"
@@ -240,23 +304,6 @@ export function applyProxyEnvWith(
240
304
  }
241
305
  // Configured entries first, then loopback: loopback is unconditional, so appending it last
242
306
  // keeps it present even when the operator lists a loopback host themselves.
243
- const raw = config.noProxy;
244
- let configuredEntries: string[];
245
- if (Array.isArray(raw)) {
246
- // One unusable element must not discard the operator's other entries.
247
- if (raw.some(entry => typeof entry !== "string")) warnProxyConfigDiscardOnce("noProxyElements");
248
- configuredEntries = raw.filter((entry): entry is string => typeof entry === "string");
249
- } else if (typeof raw === "string") {
250
- const resolved = resolveEnvValue(raw);
251
- if (raw && resolved === undefined) warnProxyConfigDiscardOnce("noProxy");
252
- configuredEntries = (resolved ?? "").split(",");
253
- } else {
254
- if (raw !== undefined) warnProxyConfigDiscardOnce("noProxy");
255
- configuredEntries = [];
256
- }
257
- const configured = configuredEntries
258
- .map(entry => entry.trim())
259
- .filter(Boolean);
260
- mergeNoProxyEntries(configured);
307
+ mergeNoProxyEntries(configuredNoProxyEntries(config));
261
308
  configureSocks5Fetch();
262
309
  }
@@ -16,6 +16,7 @@ import {
16
16
  remoteGuiConfigSchema,
17
17
  runtimeRoleSchema,
18
18
  spendSchema,
19
+ skillsConfigSchema,
19
20
  configuredCodexPoolAccountIds,
20
21
  apiKeyEntrySchema,
21
22
  asideProfileSyncSchema,
@@ -24,6 +25,8 @@ import {
24
25
  codexAccountNamespacesSchema,
25
26
  modelPinnedEffortsSchema,
26
27
  compactionRoutingSchema,
28
+ memoryModelSettingSchema,
29
+ memoryModelsSchema,
27
30
  modelPreferHostedToolsConfigError,
28
31
  providerModelCostsConfigError,
29
32
  providerRelativeSendPathConfigError,
@@ -78,6 +81,7 @@ export const configSchema = z.object({
78
81
  // A malformed privacy block must never be read as "unmask": .catch(undefined) drops it and
79
82
  // emailMaskingEnabled then falls back to masked, which is also what an absent block means.
80
83
  privacy: z.object({ maskEmails: z.boolean().optional() }).strict().optional().catch(undefined),
84
+ skills: skillsConfigSchema.optional().catch(undefined),
81
85
  // Malformed hand edits disable this opt-in exporter. Live writes reject them in diagnostics.ts.
82
86
  metricsExport: z.object({ enabled: z.boolean().optional() }).strict().optional().catch(undefined),
83
87
  // Kept raw on purpose: `.catch(undefined)` would turn a mistyped `enabled` into "inherit",
@@ -158,6 +162,17 @@ export const configSchema = z.object({
158
162
  modelPinnedEfforts: modelPinnedEffortsSchema.optional(),
159
163
  compactionRouting: compactionRoutingSchema.optional().catch(undefined),
160
164
  compactionRecovery: compactionRecoverySchema.optional().catch(undefined),
165
+ // A hand-edited malformed phase disables only that phase instead of rejecting
166
+ // providers/apiKeys, matching the load-time degradation notice; the management write
167
+ // boundary (validateConfigCandidate) still refuses the bad value through the shared,
168
+ // catch-free memoryModelsSchema.
169
+ memoryModels: z
170
+ .object({
171
+ extract: memoryModelSettingSchema.optional().catch(undefined),
172
+ consolidation: memoryModelSettingSchema.optional().catch(undefined),
173
+ })
174
+ .optional()
175
+ .catch(undefined),
161
176
  defaultProvider: z.string().min(1).default("openai"),
162
177
  defaultModelAliases: z.boolean().optional(),
163
178
  // Malformed hand edits disable this opt-in projection without rejecting providers.
@@ -55,6 +55,21 @@ export const compactionRoutingSchema = z.object({
55
55
  .optional(),
56
56
  }).strict();
57
57
 
58
+ /**
59
+ * One phase of Codex's memory pipeline. A present phase must name a model: the GUI's "Off"
60
+ * removes the phase instead of blanking it, so an empty entry would only ever come from a
61
+ * hand-edited file, where failing the write is the honest answer.
62
+ */
63
+ export const memoryModelSettingSchema = z.object({
64
+ model: z.string().trim().min(1),
65
+ reasoningEffort: z.string().refine(value => pinnedReasoningEffortConfigError(value) === null).optional(),
66
+ }).strict();
67
+
68
+ export const memoryModelsSchema = z.object({
69
+ extract: memoryModelSettingSchema.optional(),
70
+ consolidation: memoryModelSettingSchema.optional(),
71
+ }).strict();
72
+
58
73
  /**
59
74
  * Bounds for the opt-in same-target 429 wait-and-retry policy. Single source of truth
60
75
  * shared by the config schema, the load-time sanitizer, and the management write
@@ -297,6 +312,7 @@ export const providerConfigSchema = z.object({
297
312
  annotateEmptyToolOutputs: z.boolean().optional(),
298
313
  foldDeveloperRoleToSystem: z.boolean().optional(),
299
314
  fastWire: fastWireSchema.nullable().optional(),
315
+ responseTierAuthoritative: z.boolean().optional(),
300
316
  fastEnabled: z.boolean().optional(),
301
317
  supportsServiceTier: z.boolean().optional(),
302
318
  modelSupportsServiceTier: z.record(z.string().min(1), z.boolean()).optional(),
@@ -1055,3 +1071,10 @@ export const spendSchema = z.object({
1055
1071
  pool: spendScopeSchema.optional(),
1056
1072
  retentionDays: z.number().int().min(1).max(365).optional(),
1057
1073
  }).strict();
1074
+
1075
+ /**
1076
+ * Runtime skills catalog configuration (#5569).
1077
+ */
1078
+ export const skillsConfigSchema = z.object({
1079
+ catalog_refresh: z.enum(["per_session", "per_turn"]).optional(),
1080
+ }).strict();
package/src/config.ts CHANGED
@@ -267,6 +267,7 @@ export function loadConfig(): OcxConfig {
267
267
  warnConfigRepaired(configPath, result.error);
268
268
  const config = normalizeApiKeyIds(retryResult.data as OcxConfig);
269
269
  warnInheritedFastWireConflicts(configPath, config);
270
+ warnDegradedTopLevelOptIns(parsed, config);
270
271
  warnDegradedHostname(parsed, config);
271
272
  warnDegradedListeners(parsed, config);
272
273
  warnDegradedApiKeys(parsed, config);
@@ -296,6 +297,7 @@ export function loadConfig(): OcxConfig {
296
297
  warnDroppedConfigSections(configPath, salvaged.dropped, salvaged.issues);
297
298
  const config = normalizeApiKeyIds(salvaged.parsed);
298
299
  warnInheritedFastWireConflicts(configPath, config);
300
+ warnDegradedTopLevelOptIns(parsed, config);
299
301
  warnDegradedHostname(parsed, config);
300
302
  warnDegradedListeners(parsed, config);
301
303
  warnDegradedApiKeys(parsed, config);