@bitkyc08/opencodex 2.64.0-preview.20260923 → 2.65.0-preview.20260925
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.
- package/README.md +28 -25
- package/bin/ocx.mjs +16 -2
- package/gui/dist/assets/App-8NMiZxT0.css +1 -0
- package/gui/dist/assets/App-CsYvvpr3.js +50 -0
- package/gui/dist/assets/Tray-DUvc_Wul.js +1 -0
- package/gui/dist/assets/{index-DiBRuK-d.css → index--EWgGQvZ.css} +1 -1
- package/gui/dist/assets/{index-BEq4OCOz.js → index-BB0iHG8-.js} +12 -12
- package/gui/dist/assets/tray-data-f0lTZ4sF.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -4
- package/src/adapters/anthropic.ts +30 -1
- package/src/adapters/claude-cli/adapter.ts +218 -0
- package/src/adapters/claude-cli/profiles.ts +38 -0
- package/src/adapters/codebuddy/profiles.ts +2 -0
- package/src/adapters/coding-agent/profile.ts +12 -4
- package/src/adapters/coding-agent/turn.ts +16 -6
- package/src/adapters/command-code-tool-text.ts +133 -23
- package/src/adapters/cursor/catalog.ts +4 -8
- package/src/adapters/cursor/discovery.ts +10 -5
- package/src/adapters/cursor/effort-map.ts +2 -6
- package/src/adapters/cursor/envelope-echo.ts +10 -5
- package/src/adapters/cursor/request-builder.ts +9 -4
- package/src/adapters/cursor/thread-continuity.ts +53 -0
- package/src/adapters/cursor.ts +29 -13
- package/src/adapters/devin/cloud-direct/chat.ts +3 -1
- package/src/adapters/google-tool-schema.ts +26 -3
- package/src/adapters/identity.ts +214 -2
- package/src/adapters/openai-chat/passthrough.ts +1 -0
- package/src/adapters/openai-chat/serialized-tool-call-content.ts +569 -0
- package/src/adapters/openai-chat.ts +43 -15
- package/src/adapters/openai-responses/canonical-forward.ts +17 -10
- package/src/adapters/openai-responses/passthrough.ts +19 -4
- package/src/adapters/openai-responses/request-strips.ts +25 -0
- package/src/adapters/openai-responses/web-search.ts +22 -4
- package/src/adapters/qoder/profiles.ts +2 -0
- package/src/adapters/registry.ts +8 -0
- package/src/adapters/run-turn-queue.ts +7 -0
- package/src/bridge/response-json.ts +6 -4
- package/src/bridge/sse.ts +7 -5
- package/src/claude/alias.ts +73 -26
- package/src/claude/context-windows.ts +15 -6
- package/src/claude/desktop-3p-library.ts +3 -2
- package/src/claude/desktop-3p.ts +1 -1
- package/src/claude/desktop-first-party.ts +63 -22
- package/src/claude/desktop-picker-profile.ts +302 -0
- package/src/claude/desktop-picker.ts +365 -0
- package/src/claude/desktop-risk.ts +20 -0
- package/src/claude/gateway-cache.ts +13 -4
- package/src/claude/inbound.ts +5 -2
- package/src/claude/intercept/connect-proxy.ts +106 -29
- package/src/claude/intercept/local-ca.ts +76 -18
- package/src/claude/intercept/model-bindings.ts +145 -0
- package/src/claude/intercept/picker-bootstrap.ts +141 -0
- package/src/claude/intercept/picker-ca.ts +126 -0
- package/src/claude/intercept/picker-listener.ts +213 -0
- package/src/claude/intercept/picker-models.ts +101 -0
- package/src/claude/intercept/picker-runtime.ts +330 -0
- package/src/claude/intercept/picker-trust.ts +125 -0
- package/src/claude/intercept/runtime.ts +149 -2
- package/src/claude/model-info.ts +18 -4
- package/src/cli/agent.ts +16 -2
- package/src/cli/capabilities.ts +71 -2
- package/src/cli/claude-desktop.ts +319 -33
- package/src/cli/claude.ts +15 -7
- package/src/cli/codex-shim-autorestore.ts +1 -0
- package/src/cli/dispatch.ts +29 -23
- package/src/cli/effort.ts +16 -6
- package/src/cli/ensure-desired-integrations.ts +29 -4
- package/src/cli/ready.ts +1 -1
- package/src/cli/registry.ts +8 -0
- package/src/cli/restart-scope.ts +9 -1
- package/src/cli/runtime-api.ts +17 -0
- package/src/cli/system-command.ts +11 -12
- package/src/client/connect.ts +12 -2
- package/src/client/state.ts +39 -1
- package/src/clients/config-export.ts +57 -9
- package/src/codex/auth-context.ts +51 -11
- package/src/codex/catalog/derive-entry.ts +5 -5
- package/src/codex/catalog/gather-capture.ts +25 -6
- package/src/codex/catalog/metadata.ts +3 -3
- package/src/codex/catalog/parsing.ts +39 -1
- package/src/codex/catalog/provider-models.ts +8 -6
- package/src/codex/catalog/retained-sync.ts +17 -7
- package/src/codex/catalog/sync.ts +2 -2
- package/src/codex/desktop-app-restart.ts +8 -0
- package/src/codex/desktop-switches.ts +9 -1
- package/src/codex/home.ts +43 -4
- package/src/codex/inject/config-toml.ts +137 -0
- package/src/codex/inject/plan.ts +23 -0
- package/src/codex/inject/remove.ts +21 -1
- package/src/codex/inject.ts +63 -8
- package/src/codex/journal.ts +36 -0
- package/src/codex/main-account-hard-lock.ts +14 -1
- package/src/codex/main-account-policy-wait.ts +109 -0
- package/src/codex/native-profile-startup.ts +24 -6
- package/src/codex/native-residue.ts +28 -10
- package/src/codex/quota-types.ts +8 -1
- package/src/codex/quota.ts +49 -7
- package/src/codex/runtime.ts +31 -10
- package/src/combos/failover.ts +7 -6
- package/src/combos/resolve.ts +91 -14
- package/src/combos/types.ts +18 -1
- package/src/config/load-degrade.ts +2 -0
- package/src/config/schema/config-schema.ts +21 -2
- package/src/config/schema/leaf-validators.ts +6 -3
- package/src/config/subagent-models.ts +3 -1
- package/src/generated/compatibility-version.json +275 -163
- package/src/images/artifacts.ts +14 -0
- package/src/integrations/owned-refresh.ts +1 -1
- package/src/integrations/ownership-policy.ts +32 -1
- package/src/integrations/state.ts +3 -1
- package/src/integrations/writer.ts +9 -0
- package/src/lib/config-ownership.ts +3 -0
- package/src/lib/errors.ts +87 -0
- package/src/lib/package-tree-integrity.ts +1 -1
- package/src/lib/request-execution-budget.ts +42 -14
- package/src/lib/request-resend-gate.ts +4 -3
- package/src/lib/retry-delay.ts +7 -1
- package/src/lib/tool-envelope-echo-filter.ts +236 -0
- package/src/lib/upstream-retry.ts +20 -12
- package/src/oauth/index.ts +1 -0
- package/src/oauth/kiro-credentials.ts +14 -1
- package/src/oauth/login-cli.ts +1 -0
- package/src/providers/derive.ts +4 -0
- package/src/providers/fast-opt-in.ts +31 -0
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/provider-id-rewrite.ts +2 -0
- package/src/providers/quota/vendor-probes-key.ts +8 -2
- package/src/providers/registry/entries-core.ts +45 -2
- package/src/providers/registry/entries-extended.ts +99 -12
- package/src/providers/registry/model-ids.ts +2 -0
- package/src/providers/registry/types.ts +7 -1
- package/src/providers/resolved-model-policy.ts +11 -4
- package/src/providers/service-tier.ts +10 -3
- package/src/responses/code-mode-helper-compat.ts +3 -0
- package/src/responses/parser.ts +45 -3
- package/src/router.ts +6 -0
- package/src/server/auth-cors.ts +8 -0
- package/src/server/chat-completions.ts +3 -1
- package/src/server/claude-messages.ts +69 -14
- package/src/server/grok-upstream-envelope-echo.ts +120 -0
- package/src/server/index/claude-intercept-lifecycle.ts +24 -0
- package/src/server/index/serve-options.ts +2 -2
- package/src/server/index.ts +7 -0
- package/src/server/management/agent-settings-routes.ts +204 -112
- package/src/server/management/claude-desktop-picker-routes.ts +128 -0
- package/src/server/management/combo-routes.ts +59 -2
- package/src/server/management/config-routes.ts +81 -39
- package/src/server/management/context.ts +3 -0
- package/src/server/management/native-integration-routes.ts +169 -93
- package/src/server/management/provider-routes.ts +46 -4
- package/src/server/management/route-registry.ts +10 -0
- package/src/server/management/routing-profile-routes.ts +9 -0
- package/src/server/management/sidebar-routes.ts +52 -0
- package/src/server/management-api.ts +2 -0
- package/src/server/proxy-liveness.ts +38 -3
- package/src/server/request-log-account-rotation.ts +53 -0
- package/src/server/request-log.ts +3 -16
- package/src/server/responses/adapter-dispatch.ts +17 -1
- package/src/server/responses/agent-task-recovery.ts +49 -26
- package/src/server/responses/codex-ws-exchange.ts +18 -7
- package/src/server/responses/codex-ws-wire.ts +27 -4
- package/src/server/responses/core-combo.ts +21 -3
- package/src/server/responses/core-normalize.ts +7 -0
- package/src/server/responses/core-opaque-recovery.ts +3 -2
- package/src/server/responses/encrypted-payload.ts +15 -14
- package/src/server/responses/fetch-helpers.ts +1 -1
- package/src/server/responses/input-admission.ts +10 -6
- package/src/server/responses/native-response-control.ts +2 -2
- package/src/server/responses/passthrough-delivery.ts +182 -12
- package/src/server/responses/passthrough-dispatch.ts +102 -44
- package/src/server/responses/policy-fallback.ts +3 -0
- package/src/server/responses/policy-refusal.ts +67 -0
- package/src/server/responses/request-send-budget.ts +4 -0
- package/src/server/responses/run-turn-execution.ts +42 -11
- package/src/server/responses/ws-upstream.ts +2 -1
- package/src/server/system-env-shell.ts +0 -1
- package/src/server/system-env.ts +25 -4
- package/src/service/cli.ts +34 -3
- package/src/tray/assets/opencodex-tray-offline-update.ico +0 -0
- package/src/tray/assets/opencodex-tray-online-update.ico +0 -0
- package/src/tray/assets/opencodex-tray-warning-update.ico +0 -0
- package/src/tray/windows-tray.ps1 +188 -7
- package/src/tray/windows.ts +23 -2
- package/src/types/config.ts +52 -12
- package/src/types/provider.ts +22 -5
- package/src/types/tools.ts +11 -5
- package/src/types.ts +1 -0
- package/src/update/async-check.ts +109 -0
- package/src/update/badge.ts +11 -17
- package/src/update/check-types.ts +9 -0
- package/src/update/desktop-badge.ts +102 -0
- package/src/update/index.ts +48 -8
- package/src/update/install-detection.d.mts +29 -2
- package/src/update/install-detection.mjs +213 -7
- package/src/update/job.ts +17 -12
- package/src/update/notify.ts +16 -11
- package/src/update/pnpm-owner-worker.ts +12 -0
- package/src/update/refresh-scheduler.ts +141 -0
- package/src/usage/cost.ts +31 -4
- package/src/usage/expected-prices.ts +57 -0
- package/assets/download-linux.svg +0 -10
- package/assets/download-macos.svg +0 -10
- package/assets/download-windows.svg +0 -10
- package/gui/dist/assets/App-D5eN1TID.js +0 -50
- package/gui/dist/assets/App-I5AnaSLh.css +0 -1
- package/gui/dist/assets/Tray-Bh_ErQDh.js +0 -1
- package/gui/dist/assets/usage-companion-chart-DBG_37kQ.js +0 -1
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
setDebugSettings,
|
|
53
53
|
type DebugFlag,
|
|
54
54
|
} from "../../lib/debug-settings";
|
|
55
|
-
import type { OcxClaudeCodeConfig, OcxComboConfig, OcxConfig, OcxCustomModel, OcxProviderConfig } from "../../types";
|
|
55
|
+
import type { OcxClaudeCodeConfig, OcxComboConfig, OcxConfig, OcxCustomModel, OcxProviderConfig, OcxComboCooldownWaitPolicy } from "../../types";
|
|
56
56
|
import { drainAndShutdown } from "../lifecycle";
|
|
57
57
|
import { reconcileLiveStateStores } from "../../lib/state-store-registrations";
|
|
58
58
|
import { filterRequestLogs, getRequestLogEntries, type RequestLogEntry } from "../request-log";
|
|
@@ -78,12 +78,14 @@ import { COMBO_DEFAULT_WAIT_FOR_COOLDOWN_MS } from "../../combos";
|
|
|
78
78
|
function sparseComboConfig<T extends {
|
|
79
79
|
cooldownMs?: number;
|
|
80
80
|
waitForCooldownMs?: number;
|
|
81
|
+
cooldownWaitPolicy?: OcxComboCooldownWaitPolicy | null;
|
|
81
82
|
imageInput?: "auto" | "disabled";
|
|
82
83
|
reasoningEffortMode?: "strict" | "adaptive";
|
|
83
84
|
defaultEffortMode?: "fallback" | "force";
|
|
84
|
-
}>(combo: T): Omit<T, "cooldownMs" | "waitForCooldownMs" | "imageInput" | "reasoningEffortMode" | "defaultEffortMode"> & {
|
|
85
|
+
}>(combo: T): Omit<T, "cooldownMs" | "waitForCooldownMs" | "cooldownWaitPolicy" | "imageInput" | "reasoningEffortMode" | "defaultEffortMode"> & {
|
|
85
86
|
cooldownMs?: number;
|
|
86
87
|
waitForCooldownMs?: number;
|
|
88
|
+
cooldownWaitPolicy?: OcxComboCooldownWaitPolicy;
|
|
87
89
|
imageInput?: "disabled";
|
|
88
90
|
reasoningEffortMode?: "adaptive";
|
|
89
91
|
defaultEffortMode?: "force";
|
|
@@ -91,6 +93,7 @@ function sparseComboConfig<T extends {
|
|
|
91
93
|
const {
|
|
92
94
|
cooldownMs,
|
|
93
95
|
waitForCooldownMs,
|
|
96
|
+
cooldownWaitPolicy,
|
|
94
97
|
imageInput,
|
|
95
98
|
reasoningEffortMode,
|
|
96
99
|
defaultEffortMode,
|
|
@@ -99,6 +102,9 @@ function sparseComboConfig<T extends {
|
|
|
99
102
|
return {
|
|
100
103
|
...rest,
|
|
101
104
|
...(cooldownMs !== undefined ? { cooldownMs } : {}),
|
|
105
|
+
// #5691: the normalizer yields null for "unset"; persisting that would put a
|
|
106
|
+
// meaningless key in every stored combo. Only the opt-in value is written.
|
|
107
|
+
...(cooldownWaitPolicy ? { cooldownWaitPolicy } : {}),
|
|
102
108
|
...(waitForCooldownMs !== undefined && waitForCooldownMs !== COMBO_DEFAULT_WAIT_FOR_COOLDOWN_MS
|
|
103
109
|
? { waitForCooldownMs }
|
|
104
110
|
: {}),
|
|
@@ -174,6 +180,42 @@ export async function handleComboRoutes(ctx: ManagementContext): Promise<Respons
|
|
|
174
180
|
...(!Object.hasOwn(requestedCombo, "waitForCooldownMs") && previous?.waitForCooldownMs !== undefined
|
|
175
181
|
? { waitForCooldownMs: previous.waitForCooldownMs }
|
|
176
182
|
: {}),
|
|
183
|
+
// Same reason as defaultEffortMode below: the dashboard does not expose the
|
|
184
|
+
// last-resort policy, so a GUI round-trip that omits it must not delete it (#5736).
|
|
185
|
+
...(!Object.hasOwn(requestedCombo, "cooldownWaitPolicy") && previous?.cooldownWaitPolicy !== undefined
|
|
186
|
+
? { cooldownWaitPolicy: previous.cooldownWaitPolicy }
|
|
187
|
+
: {}),
|
|
188
|
+
// #5687: an API or CLI client that omits these must not reset them. The dashboard
|
|
189
|
+
// sends both explicitly, so switching back to auto/strict there still replaces them.
|
|
190
|
+
...(!Object.hasOwn(requestedCombo, "reasoningEffortMode") && previous?.reasoningEffortMode !== undefined
|
|
191
|
+
? { reasoningEffortMode: previous.reasoningEffortMode }
|
|
192
|
+
: {}),
|
|
193
|
+
...(!Object.hasOwn(requestedCombo, "imageInput") && previous?.imageInput !== undefined
|
|
194
|
+
? { imageInput: previous.imageInput }
|
|
195
|
+
: {}),
|
|
196
|
+
// `lastResort` rides on each target, so a GUI that re-sends the target list without the
|
|
197
|
+
// flag would strip it. Carry it over per target, matched on provider+model.
|
|
198
|
+
...(Array.isArray(requestedCombo.targets) && Array.isArray(previous?.targets)
|
|
199
|
+
? {
|
|
200
|
+
targets: requestedCombo.targets.map(target => {
|
|
201
|
+
// A non-record entry stays as the client sent it so comboConfigError reports it.
|
|
202
|
+
// Reading `lastResort` off it here would throw in place of that structured 400.
|
|
203
|
+
if (!isPlainRecord(target)) return target;
|
|
204
|
+
if (Object.hasOwn(target, "lastResort")) return target;
|
|
205
|
+
const rawProvider = target.provider;
|
|
206
|
+
const rawModel = target.model;
|
|
207
|
+
if (typeof rawProvider !== "string" || typeof rawModel !== "string") return target;
|
|
208
|
+
// Identity is trimmed on both sides: the normalizer trims too, so a re-sent
|
|
209
|
+
// " b " must still match the stored "b" instead of losing the flag.
|
|
210
|
+
const provider = rawProvider.trim();
|
|
211
|
+
const model = rawModel.trim();
|
|
212
|
+
const before = previous.targets.find(
|
|
213
|
+
candidate => candidate.provider === provider && candidate.model === model,
|
|
214
|
+
);
|
|
215
|
+
return before?.lastResort ? { ...target, lastResort: true } : target;
|
|
216
|
+
}),
|
|
217
|
+
}
|
|
218
|
+
: {}),
|
|
177
219
|
// The dashboard does not expose this advanced CLI/API policy. Preserve it when
|
|
178
220
|
// a GUI round-trip omits the field instead of silently downgrading to fallback.
|
|
179
221
|
...(!Object.hasOwn(requestedCombo, "defaultEffortMode") && previous?.defaultEffortMode !== undefined
|
|
@@ -198,6 +240,13 @@ export async function handleComboRoutes(ctx: ManagementContext): Promise<Respons
|
|
|
198
240
|
} = sparseComboConfig(normalized);
|
|
199
241
|
const stored: OcxComboConfig = {
|
|
200
242
|
...normalizedBase,
|
|
243
|
+
// The normalizer gives every target an explicit `lastResort: false`; persisting that
|
|
244
|
+
// would add a noise key to every target of every combo, including ones that never use
|
|
245
|
+
// the policy (#5736). Only the opt-in value is stored, matching how the combo-level
|
|
246
|
+
// policy is handled in sparseComboConfig.
|
|
247
|
+
targets: normalizedBase.targets.map(({ lastResort, ...target }) =>
|
|
248
|
+
lastResort ? { ...target, lastResort: true } : target,
|
|
249
|
+
),
|
|
201
250
|
...(normalizedAlias ? { alias: normalizedAlias } : {}),
|
|
202
251
|
...(normalizedNativeAlias ? { nativeAlias: true } : {}),
|
|
203
252
|
...(normalizedDisplayName ? { displayName: normalizedDisplayName } : {}),
|
|
@@ -275,6 +324,14 @@ export async function handleComboRoutes(ctx: ManagementContext): Promise<Respons
|
|
|
275
324
|
Object.entries(claudeCode.modelMap).map(([source, model]) => [source, migrateAgentReference(model)]),
|
|
276
325
|
);
|
|
277
326
|
}
|
|
327
|
+
if (claudeCode.intercept?.modelMap) {
|
|
328
|
+
claudeCode.intercept = {
|
|
329
|
+
...claudeCode.intercept,
|
|
330
|
+
modelMap: Object.fromEntries(
|
|
331
|
+
Object.entries(claudeCode.intercept.modelMap).map(([pickerId, route]) => [pickerId, migrateAgentReference(route)]),
|
|
332
|
+
),
|
|
333
|
+
};
|
|
334
|
+
}
|
|
278
335
|
config.claudeCode = claudeCode;
|
|
279
336
|
}
|
|
280
337
|
}
|
|
@@ -6,7 +6,7 @@ import { readFileSync } from "node:fs";
|
|
|
6
6
|
import type { CatalogModel } from "../../codex/catalog";
|
|
7
7
|
import { catalogModelSlug, invalidateCodexModelsCache, nativeContextLimits, nativeModelRows, uniqueCatalogModelsForPublicList } from "../../codex/catalog";
|
|
8
8
|
import {
|
|
9
|
-
|
|
9
|
+
applyCodexConfigInjection,
|
|
10
10
|
describeCodexDesktopSwitches,
|
|
11
11
|
type CodexDesktopSwitchApply,
|
|
12
12
|
} from "../../codex/desktop-switches";
|
|
@@ -59,7 +59,10 @@ import {
|
|
|
59
59
|
codexQuotaAutoRefreshStatus,
|
|
60
60
|
runCodexQuotaAutoRefresh,
|
|
61
61
|
} from "../../codex/quota-auto-refresh";
|
|
62
|
-
import {
|
|
62
|
+
import {
|
|
63
|
+
getMainAccountHardLockStatus,
|
|
64
|
+
isMainAccountHardLockEnabled,
|
|
65
|
+
} from "../../codex/main-account-hard-lock";
|
|
63
66
|
import {
|
|
64
67
|
codexAccountPickerEnabled,
|
|
65
68
|
initializeDefaultCodexAccountNamespaces,
|
|
@@ -208,40 +211,48 @@ export async function syncEnabledClientIntegrations(
|
|
|
208
211
|
}
|
|
209
212
|
}
|
|
210
213
|
|
|
211
|
-
|
|
214
|
+
const { observeClaudeDesktopMode, resolveClaudeDesktopMode } = await import("../../claude/desktop-first-party");
|
|
215
|
+
// A first-party Desktop must never get a gateway profile written and selected by a sync.
|
|
216
|
+
if (claudeDesktopIntegrationEnabled(config) && resolveClaudeDesktopMode(config, observeClaudeDesktopMode(config)) !== "first-party") {
|
|
212
217
|
try {
|
|
213
218
|
const { writeDesktop3pConfig } = await import("../../claude/desktop-3p");
|
|
214
219
|
const { desktopVisibleNativeSlugs, filterCatalogVisibleModels } = await import("../../codex/catalog");
|
|
215
220
|
const { fetchAllModels } = await import("../management-api");
|
|
216
221
|
const models = await (deps.fetchAllModels ?? fetchAllModels)(config);
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
:
|
|
241
|
-
|
|
242
|
-
|
|
222
|
+
// Serialized with Desktop mode transitions (picker lock): a first-party switch cannot interleave.
|
|
223
|
+
const { runPickerTransition } = await import("./claude-desktop-picker-routes");
|
|
224
|
+
await runPickerTransition(config, async () => {
|
|
225
|
+
// Discovery admits a concurrent OFF or settings edit. Re-read outside C:
|
|
226
|
+
// the writer facade owns L and its final desired-state check under L→C.
|
|
227
|
+
const latest = loadConfig();
|
|
228
|
+
// Discovery awaited: the mode may have changed meanwhile. Re-resolve on the fresh read,
|
|
229
|
+
// immediately before the writer, so a first-party switch during fetchAllModels still wins.
|
|
230
|
+
if (claudeDesktopIntegrationEnabled(latest) && resolveClaudeDesktopMode(latest, observeClaudeDesktopMode(latest)) !== "first-party") {
|
|
231
|
+
const routed = filterCatalogVisibleModels(models, latest)
|
|
232
|
+
.map(model => ({ provider: model.provider, id: model.id, contextWindow: model.contextWindow }));
|
|
233
|
+
const writtenProfile = latest.claudeCode?.desktopProfile;
|
|
234
|
+
const markerBaseline = captureDesktopAppliedMarker(writtenProfile);
|
|
235
|
+
const r = (deps.writeDesktop3pConfig ?? writeDesktop3pConfig)(
|
|
236
|
+
port,
|
|
237
|
+
[...desktopVisibleNativeSlugs(latest)],
|
|
238
|
+
routed,
|
|
239
|
+
latest.apiKeys?.[0]?.key,
|
|
240
|
+
"static",
|
|
241
|
+
writtenProfile,
|
|
242
|
+
nativeContextLimits(latest),
|
|
243
|
+
);
|
|
244
|
+
if (!r.written || !r.fingerprint) {
|
|
245
|
+
out.push({ client: "claude-desktop", ok: false, reason: r.reason ?? "Claude Desktop write failed" });
|
|
246
|
+
} else {
|
|
247
|
+
const marked = commitDesktopAppliedMarker(markerBaseline, r.fingerprint);
|
|
248
|
+
out.push(marked.status === "unavailable"
|
|
249
|
+
? { client: "claude-desktop", ok: false, reason: "Claude Desktop applied marker was not saved (" + marked.reason + ")" }
|
|
250
|
+
: marked.value === false
|
|
251
|
+
? { client: "claude-desktop", ok: false, reason: "Claude Desktop desired profile changed during sync; applied marker skipped" }
|
|
252
|
+
: { client: "claude-desktop", ok: true, changed: true });
|
|
253
|
+
}
|
|
243
254
|
}
|
|
244
|
-
}
|
|
255
|
+
});
|
|
245
256
|
} catch (error) {
|
|
246
257
|
out.push({ client: "claude-desktop", ok: false, reason: error instanceof Error ? error.message : String(error) });
|
|
247
258
|
}
|
|
@@ -340,7 +351,7 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
340
351
|
ultraFastTier: config.ultraFastTier === true,
|
|
341
352
|
// Absent means on by default: the GUI renders a switch enabled unless explicit false.
|
|
342
353
|
fastRows: config.fastRows !== false,
|
|
343
|
-
codexMainAccountHardLock: config
|
|
354
|
+
codexMainAccountHardLock: isMainAccountHardLockEnabled(config),
|
|
344
355
|
mainAccountHardLock: getMainAccountHardLockStatus(config),
|
|
345
356
|
// Absent means the historical auto-open, so the GUI can render the toggle
|
|
346
357
|
// without having to know that `undefined` and `true` mean the same thing.
|
|
@@ -586,8 +597,10 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
586
597
|
else if (body.ultraFastTier === false) deleteConfigTopLevelKey(config, "ultraFastTier");
|
|
587
598
|
if (body.fastRows === false) config.fastRows = false;
|
|
588
599
|
else if (body.fastRows === true) deleteConfigTopLevelKey(config, "fastRows");
|
|
589
|
-
|
|
590
|
-
|
|
600
|
+
// Inverted from the pair above because the default is on (#5694): off is the persisted
|
|
601
|
+
// decision, so it writes `false`, while on deletes the key and returns to the default.
|
|
602
|
+
if (body.codexMainAccountHardLock === false) config.codexMainAccountHardLock = false;
|
|
603
|
+
else if (body.codexMainAccountHardLock === true) deleteConfigTopLevelKey(config, "codexMainAccountHardLock");
|
|
591
604
|
if (body.codexDesktopAuthless === true) config.codexDesktopAuthless = true;
|
|
592
605
|
else if (body.codexDesktopAuthless === false) deleteConfigTopLevelKey(config, "codexDesktopAuthless");
|
|
593
606
|
if (body.codexClientCompaction === true) config.codexClientCompaction = true;
|
|
@@ -673,7 +686,7 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
673
686
|
// because coordinated Codex writes acquire the Codex write lock N before the config mutation
|
|
674
687
|
// lock C — awaiting N while still holding C would invert that order.
|
|
675
688
|
const desktopSwitchApply: CodexDesktopSwitchApply = desktopSwitchesChanged
|
|
676
|
-
? await
|
|
689
|
+
? await applyCodexConfigInjection(config)
|
|
677
690
|
: { applied: false, reason: "not_requested", retryable: false };
|
|
678
691
|
const codexDesktopSwitches = describeCodexDesktopSwitches(config, desktopSwitchApply);
|
|
679
692
|
const catalogRefreshPending = catalogRefresh
|
|
@@ -695,7 +708,7 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
695
708
|
codexClientCompaction: clientCompactionIsEnabled,
|
|
696
709
|
codexDesktopSwitches,
|
|
697
710
|
compactionRouting: config.compactionRouting ?? null,
|
|
698
|
-
codexMainAccountHardLock: config
|
|
711
|
+
codexMainAccountHardLock: isMainAccountHardLockEnabled(config),
|
|
699
712
|
mainAccountHardLock: getMainAccountHardLockStatus(config),
|
|
700
713
|
startupHealth: await readStartupHealth(config),
|
|
701
714
|
});
|
|
@@ -735,12 +748,13 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
735
748
|
}
|
|
736
749
|
|
|
737
750
|
if (url.pathname === "/api/update/check" && req.method === "GET") {
|
|
738
|
-
const {
|
|
751
|
+
const { normalizeUpdateChannel } = await import("../../update/job");
|
|
752
|
+
const { packageRefresh } = await import("../../update/refresh-scheduler");
|
|
739
753
|
const rawTag = url.searchParams.get("tag");
|
|
740
754
|
if (rawTag && rawTag !== "latest" && rawTag !== "preview") {
|
|
741
755
|
return jsonResponse({ error: "tag must be latest or preview" }, 400);
|
|
742
756
|
}
|
|
743
|
-
return jsonResponse(
|
|
757
|
+
return jsonResponse(await (deps.checkPackageUpdate ?? packageRefresh.check)(normalizeUpdateChannel(rawTag)));
|
|
744
758
|
}
|
|
745
759
|
|
|
746
760
|
if (url.pathname === "/api/update/run" && req.method === "POST") {
|
|
@@ -754,7 +768,12 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
754
768
|
return jsonResponse({ error: "restart boolean is required" }, 400);
|
|
755
769
|
}
|
|
756
770
|
try {
|
|
757
|
-
|
|
771
|
+
const channel = normalizeUpdateChannel(body.tag as string | undefined);
|
|
772
|
+
const { packageRefresh } = await import("../../update/refresh-scheduler");
|
|
773
|
+
const checked = await (deps.checkPackageUpdate ?? packageRefresh.check)(channel);
|
|
774
|
+
return jsonResponse({ ok: true, job: startUpdateJob(channel, body.restart !== false, {
|
|
775
|
+
checkForUpdateFn: () => checked,
|
|
776
|
+
}) });
|
|
758
777
|
} catch (err) {
|
|
759
778
|
if (err instanceof UpdateJobError) {
|
|
760
779
|
return jsonResponse({ error: err.message, code: err.code }, err.status);
|
|
@@ -799,7 +818,7 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
799
818
|
if (raw.webSearch !== undefined && !isPlainRecord(raw.webSearch)) return jsonResponse({ error: "webSearch must be an object" }, 400);
|
|
800
819
|
if (raw.vision !== undefined && !isPlainRecord(raw.vision)) return jsonResponse({ error: "vision must be an object" }, 400);
|
|
801
820
|
const body = raw as {
|
|
802
|
-
webSearch?: { model?: unknown; backend?: unknown; reasoning?: unknown; streamRoutedModelOutput?: unknown; exaApiKey?: unknown; xSearch?: unknown };
|
|
821
|
+
webSearch?: { enabled?: unknown; model?: unknown; backend?: unknown; reasoning?: unknown; streamRoutedModelOutput?: unknown; exaApiKey?: unknown; xSearch?: unknown };
|
|
803
822
|
vision?: {
|
|
804
823
|
model?: unknown;
|
|
805
824
|
backend?: unknown;
|
|
@@ -821,6 +840,9 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
821
840
|
&& typeof body.webSearch.streamRoutedModelOutput !== "boolean") {
|
|
822
841
|
return jsonResponse({ error: "webSearch.streamRoutedModelOutput must be a boolean" }, 400);
|
|
823
842
|
}
|
|
843
|
+
if (body.webSearch && body.webSearch.enabled !== undefined && typeof body.webSearch.enabled !== "boolean") {
|
|
844
|
+
return jsonResponse({ error: "webSearch.enabled must be a boolean" }, 400);
|
|
845
|
+
}
|
|
824
846
|
if (body.vision && body.vision.backend !== undefined
|
|
825
847
|
&& body.vision.backend !== null && body.vision.backend !== "openai" && body.vision.backend !== "anthropic"
|
|
826
848
|
&& body.vision.backend !== "routed") {
|
|
@@ -884,6 +906,9 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
884
906
|
: normalizeVisionReasoningForModel(model, sourceReasoning);
|
|
885
907
|
}
|
|
886
908
|
|
|
909
|
+
// Read BEFORE the mutation below: the Codex-side key follows the switch, and a flip is the
|
|
910
|
+
// only case that owes a config.toml rewrite.
|
|
911
|
+
const webSearchEnabledBefore = config.webSearchSidecar?.enabled !== false;
|
|
887
912
|
if (body.webSearch) {
|
|
888
913
|
const pairTouched = body.webSearch.model !== undefined || body.webSearch.backend !== undefined;
|
|
889
914
|
// Validate against the backend the caller SUBMITTED, across the whole
|
|
@@ -979,6 +1004,12 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
979
1004
|
if (body.webSearch.streamRoutedModelOutput) webSearchCandidate.streamRoutedModelOutput = true;
|
|
980
1005
|
else delete webSearchCandidate.streamRoutedModelOutput;
|
|
981
1006
|
}
|
|
1007
|
+
if (typeof body.webSearch.enabled === "boolean") {
|
|
1008
|
+
// `true` is the default — drop the key so a disable/re-enable cycle does not rewrite the
|
|
1009
|
+
// file, exactly like the Vision master switch.
|
|
1010
|
+
if (body.webSearch.enabled) delete webSearchCandidate.enabled;
|
|
1011
|
+
else webSearchCandidate.enabled = false;
|
|
1012
|
+
}
|
|
982
1013
|
config.webSearchSidecar = webSearchCandidate;
|
|
983
1014
|
}
|
|
984
1015
|
if (body.vision) {
|
|
@@ -1009,6 +1040,16 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
1009
1040
|
}
|
|
1010
1041
|
}
|
|
1011
1042
|
saveConfigPreservingClaudeCode(config);
|
|
1043
|
+
// The sidecar's own switch is only half of what "off" means: Codex keeps declaring its native
|
|
1044
|
+
// hosted `web_search` tool until its own mode says otherwise, and the tool a client advertises
|
|
1045
|
+
// is the one the model reaches for — which is why an MCP search server needs the client-side
|
|
1046
|
+
// key off too. The injection owns that root key, and a write that cannot happen is reported
|
|
1047
|
+
// (the Desktop switches' contract) instead of being stored as if it had.
|
|
1048
|
+
const webSearchEnabledChanged = typeof body.webSearch?.enabled === "boolean"
|
|
1049
|
+
&& webSearchEnabledBefore !== (config.webSearchSidecar?.enabled !== false);
|
|
1050
|
+
const codexWebSearch: CodexDesktopSwitchApply = webSearchEnabledChanged
|
|
1051
|
+
? await applyCodexConfigInjection(config)
|
|
1052
|
+
: { applied: false, reason: "not_requested", retryable: false };
|
|
1012
1053
|
const ws = config.webSearchSidecar ?? {};
|
|
1013
1054
|
const vision = await sidecarVisionResponseSettings(config);
|
|
1014
1055
|
const savedWebSearchCandidates = await webSearchCandidateRows(config);
|
|
@@ -1021,6 +1062,7 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
1021
1062
|
streamRoutedModelOutput: ws.streamRoutedModelOutput === true,
|
|
1022
1063
|
...(ws.xSearch ? { xSearch: ws.xSearch } : {}),
|
|
1023
1064
|
},
|
|
1065
|
+
codexWebSearch,
|
|
1024
1066
|
vision: publicVisionSidecarSettings(config, vision),
|
|
1025
1067
|
visionModels: vision.models,
|
|
1026
1068
|
// Echoed for the same reason GET always carries it: the dashboard rebuilds
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { OcxConfig } from "../../types";
|
|
2
|
+
import type { Channel } from "../../update/index";
|
|
3
|
+
import type { UpdateCheckResult } from "../../update/job";
|
|
2
4
|
import type { NativeProfileApiDeps } from "../../codex/native-profile-api";
|
|
3
5
|
import type { CodexLogGuardProtectionDeps } from "../../codex/log-guard/protection";
|
|
4
6
|
import type { CodexLogGuardMaintenanceDeps } from "../../codex/log-guard/maintenance";
|
|
@@ -34,6 +36,7 @@ export type RemoteWorkspaceSessionsApi = Pick<RemoteWorkspaceSessionService,
|
|
|
34
36
|
export interface ManagementApiDeps {
|
|
35
37
|
/** Read-only process-local aggregate metrics; absent keeps the scrape route unavailable. */
|
|
36
38
|
requestMetrics?: RequestMetricsSnapshotter;
|
|
39
|
+
checkPackageUpdate?: (channel: Channel) => Promise<UpdateCheckResult>;
|
|
37
40
|
remoteWorkspaceHub?: RemoteWorkspaceHubApi;
|
|
38
41
|
remoteWorkspaceSessions?: RemoteWorkspaceSessionsApi;
|
|
39
42
|
/** The listener retains and awaits teardown only after this optional subsystem activates. */
|