@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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { recordCommittedDesktopGateway } from "../claude/desktop-gateway-state";
|
|
2
2
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
|
+
import { getConfigDir } from "../config/paths";
|
|
4
5
|
import { loadConfig, mutatePersistedConfig, withConfigMutationLockSync } from "../config";
|
|
5
6
|
import { claudeDesktopIntegrationEnabledNow, setIntegrationEnabled } from "../codex/desired-state";
|
|
6
7
|
import { readClientConnectionState, assertClientConnectionUnchanged, assertNoClientDisconnectPending, type ClientConnectionState } from "../client/state";
|
|
@@ -21,16 +22,24 @@ import {
|
|
|
21
22
|
applyDesktopFirstParty,
|
|
22
23
|
captureDesktopFirstPartyRollback,
|
|
23
24
|
isClaudeDesktopMode,
|
|
25
|
+
observeClaudeDesktopMode,
|
|
24
26
|
recordClaudeDesktopMode,
|
|
25
27
|
removeDesktopFirstParty,
|
|
28
|
+
resolveClaudeDesktopMode,
|
|
26
29
|
resolveClaudeDesktopApplyMode,
|
|
30
|
+
type ClaudeDesktopModeObservation,
|
|
27
31
|
type ClaudeDesktopMode,
|
|
28
32
|
} from "../claude/desktop-first-party";
|
|
33
|
+
import { FIRST_PARTY_ACCOUNT_RISK } from "../claude/desktop-risk";
|
|
34
|
+
import { claudeInterceptEnabled } from "../claude/intercept/runtime";
|
|
35
|
+
import { ensurePickerCa, pickerCaCertPath, pickerCaFingerprints, pickerLeafCertPath } from "../claude/intercept/picker-ca";
|
|
36
|
+
import { inspectPickerTrust, trustPickerCa, untrustPickerCa, type SecurityRunner } from "../claude/intercept/picker-trust";
|
|
37
|
+
import { offlinePickerStatus, removeDesktopPickerArtifacts, type DesktopPickerStatus } from "../claude/desktop-picker";
|
|
29
38
|
import { claudeDesktopPolicyWarning, probeClaudeDesktopPolicy } from "../claude/desktop-policy";
|
|
30
39
|
import { filterCatalogVisibleModels, desktopVisibleNativeSlugs, nativeContextLimits } from "../codex/catalog";
|
|
31
40
|
import { buildClaudeDesktopState, fetchAllModels } from "../server/management-api";
|
|
32
41
|
import { findLiveProxy } from "../server/proxy-liveness";
|
|
33
|
-
import { CliUsageError, runtimeRequest, takeJsonFlag } from "./runtime-api";
|
|
42
|
+
import { CliUsageError, RuntimeApiError, runtimeRequest, takeJsonFlag, type RuntimeApiDeps } from "./runtime-api";
|
|
34
43
|
import { OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
|
|
35
44
|
import type { OcxConfig } from "../types";
|
|
36
45
|
|
|
@@ -43,11 +52,18 @@ function isFamily(value: string | undefined): value is DesktopFamily {
|
|
|
43
52
|
function printDesktopHelp(): void {
|
|
44
53
|
console.log(`Usage:
|
|
45
54
|
ocx claude desktop [apply] [--first-party | --gateway [--static|--hybrid|--discovery-only]]
|
|
46
|
-
--
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
--gateway (default) install the third-party gateway profile for the whole app
|
|
56
|
+
--first-party keep Desktop on claude.ai; route only the Code tab's Claude Code through the
|
|
57
|
+
local intercept proxy via ~/.claude/settings.json env. Account risk: this sends
|
|
58
|
+
Claude subscription traffic through a local interception proxy, and Anthropic
|
|
59
|
+
may suspend the account.
|
|
49
60
|
ocx claude desktop show [--json]
|
|
50
61
|
ocx claude desktop status [--json]
|
|
62
|
+
ocx claude desktop picker on|off|status|trust
|
|
63
|
+
ocx claude desktop bind <picker-model-id> <provider/model|native/slug>
|
|
64
|
+
first-party: serve a Code tab picker model (e.g. claude-sonnet-4-6) with an opencodex model;
|
|
65
|
+
the picker keeps Anthropic's label, and only Claude Code traffic through the local proxy uses it
|
|
66
|
+
ocx claude desktop unbind <picker-model-id>
|
|
51
67
|
ocx claude desktop move <provider/model> <opus|fable|sonnet|haiku> [--default]
|
|
52
68
|
ocx claude desktop default <family> <provider/model|none>
|
|
53
69
|
ocx claude desktop export <path|->
|
|
@@ -62,10 +78,111 @@ export interface ApplyProfileDeps {
|
|
|
62
78
|
postApplyImpl?: (
|
|
63
79
|
mode: Desktop3pConfigMode,
|
|
64
80
|
profile: DesktopProfile,
|
|
65
|
-
) => Promise<{ ok?: boolean; path?: string; error?: string; warning?: string }>;
|
|
81
|
+
) => Promise<{ ok?: boolean; path?: string; error?: string; warning?: string; picker?: DesktopPickerStatus }>;
|
|
82
|
+
runtimeRequestImpl?: typeof runtimeRequest;
|
|
83
|
+
ensurePickerCaImpl?: typeof ensurePickerCa;
|
|
84
|
+
inspectPickerTrustImpl?: typeof inspectPickerTrust;
|
|
85
|
+
trustPickerCaImpl?: typeof trustPickerCa;
|
|
86
|
+
untrustPickerCaImpl?: typeof untrustPickerCa;
|
|
87
|
+
removeDesktopPickerArtifacts?: typeof removeDesktopPickerArtifacts;
|
|
88
|
+
security?: SecurityRunner;
|
|
89
|
+
platform?: NodeJS.Platform;
|
|
66
90
|
probeClaudeDesktopPolicy?: typeof import("../claude/desktop-policy").probeClaudeDesktopPolicy;
|
|
67
91
|
}
|
|
68
92
|
|
|
93
|
+
type DesktopApplyResult = {
|
|
94
|
+
ok: boolean;
|
|
95
|
+
path: string;
|
|
96
|
+
reason?: string;
|
|
97
|
+
warning?: string;
|
|
98
|
+
picker?: DesktopPickerStatus;
|
|
99
|
+
delegated?: boolean;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
type PickerRouteResponse = {
|
|
103
|
+
ok?: boolean;
|
|
104
|
+
code?: string;
|
|
105
|
+
reason?: string;
|
|
106
|
+
hint?: string;
|
|
107
|
+
picker?: DesktopPickerStatus;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
function pickerRuntimeRequest<T>(
|
|
111
|
+
path: string,
|
|
112
|
+
init: RequestInit,
|
|
113
|
+
deps: ApplyProfileDeps,
|
|
114
|
+
): Promise<T> {
|
|
115
|
+
const request = deps.runtimeRequestImpl ?? runtimeRequest;
|
|
116
|
+
const requestDeps: RuntimeApiDeps = deps.findLiveProxyImpl
|
|
117
|
+
? { findLiveProxy: deps.findLiveProxyImpl }
|
|
118
|
+
: {};
|
|
119
|
+
return request<T>(path, init, requestDeps);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function liveDesktopProxy(deps: ApplyProfileDeps): Promise<boolean> {
|
|
123
|
+
return !!await (deps.findLiveProxyImpl ?? findLiveProxy)();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function persistPickerPreference(value: boolean): boolean {
|
|
127
|
+
const outcome = mutatePersistedConfig(current => {
|
|
128
|
+
const claudeCode = current.claudeCode ?? {};
|
|
129
|
+
const intercept = claudeCode.intercept ?? {};
|
|
130
|
+
if (intercept.picker === value) return { changed: false, value: structuredClone(current.claudeCode) };
|
|
131
|
+
current.claudeCode = { ...claudeCode, intercept: { ...intercept, picker: value } };
|
|
132
|
+
return { changed: true, value: structuredClone(current.claudeCode) };
|
|
133
|
+
});
|
|
134
|
+
return outcome.status !== "unavailable";
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function pickerTrustPaths(deps: ApplyProfileDeps, configDir = getConfigDir()): { caPath: string; leafPath: string; sha1: string } {
|
|
138
|
+
const ca = (deps.ensurePickerCaImpl ?? ensurePickerCa)(configDir);
|
|
139
|
+
return {
|
|
140
|
+
caPath: pickerCaCertPath(configDir),
|
|
141
|
+
leafPath: pickerLeafCertPath(configDir),
|
|
142
|
+
sha1: pickerCaFingerprints(ca.certPem).sha1,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function trustPickerLocally(deps: ApplyProfileDeps): Promise<{ ok: true; callerAddedTrust: boolean; caPath: string; sha1: string } | { ok: false; reason: string }> {
|
|
147
|
+
try {
|
|
148
|
+
const configDir = getConfigDir();
|
|
149
|
+
const paths = pickerTrustPaths(deps, configDir);
|
|
150
|
+
const inspect = deps.inspectPickerTrustImpl ?? inspectPickerTrust;
|
|
151
|
+
const before = await inspect(paths.leafPath, paths.sha1, deps.security, deps.platform);
|
|
152
|
+
if (before === "trusted") return { ok: true, callerAddedTrust: false, caPath: paths.caPath, sha1: paths.sha1 };
|
|
153
|
+
const trust = await (deps.trustPickerCaImpl ?? trustPickerCa)(paths.caPath, deps.security, deps.platform);
|
|
154
|
+
if (!trust.ok) return { ok: false, reason: trust.reason ?? "trust_declined" };
|
|
155
|
+
return { ok: true, callerAddedTrust: true, caPath: paths.caPath, sha1: paths.sha1 };
|
|
156
|
+
} catch (error) {
|
|
157
|
+
return { ok: false, reason: error instanceof Error ? error.message : "trust_failed" };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function compensateLocalPickerTrust(
|
|
162
|
+
trust: { callerAddedTrust: boolean; caPath: string; sha1: string },
|
|
163
|
+
deps: ApplyProfileDeps,
|
|
164
|
+
): Promise<void> {
|
|
165
|
+
if (!trust.callerAddedTrust) return;
|
|
166
|
+
await (deps.untrustPickerCaImpl ?? untrustPickerCa)(trust.caPath, trust.sha1, deps.security, deps.platform);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function printPickerStatus(status: DesktopPickerStatus | undefined, json = false): void {
|
|
170
|
+
if (!status) return;
|
|
171
|
+
if (json) console.log(JSON.stringify(status, null, 2));
|
|
172
|
+
else console.log(`picker: ${JSON.stringify(status)}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function isAmbiguousPickerTransport(error: unknown): boolean {
|
|
176
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
177
|
+
return /timeout|timed out|abort|aborted|lost response|socket hang up|reset/i.test(message);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function isAnsweredPickerRefusal(error: unknown): boolean {
|
|
181
|
+
if (!(error instanceof RuntimeApiError)) return false;
|
|
182
|
+
const body = error.body;
|
|
183
|
+
return !!body && typeof body === "object" && ("picker" in body || (body as Record<string, unknown>).code === "picker_proxy_unavailable");
|
|
184
|
+
}
|
|
185
|
+
|
|
69
186
|
/** Persist only the requested local profile, never an await-old whole configuration. */
|
|
70
187
|
function saveLocalDesktopProfile(
|
|
71
188
|
profile: DesktopProfile,
|
|
@@ -174,22 +291,24 @@ export type DesktopApplyTarget =
|
|
|
174
291
|
|
|
175
292
|
/** Parse `ocx claude desktop apply` flags into a target; legacy gateway shape flags imply --gateway. */
|
|
176
293
|
/**
|
|
177
|
-
* Default mode when no flag is given:
|
|
178
|
-
*
|
|
179
|
-
*
|
|
294
|
+
* Default mode when no flag is given: gateway, unless this install is already first-party (saved
|
|
295
|
+
* mode or observed first-party settings). A connected client still uses gateway, because the
|
|
296
|
+
* intercept proxy lives on the hub.
|
|
180
297
|
*/
|
|
181
298
|
export function defaultDesktopApplyMode(
|
|
182
|
-
config: Pick<OcxConfig, "claudeCode" | "runtimeRole">,
|
|
299
|
+
config: Pick<OcxConfig, "claudeCode" | "port" | "runtimeRole">,
|
|
183
300
|
connection: ClientConnectionState = readClientConnectionState(),
|
|
301
|
+
observed: ClaudeDesktopModeObservation = observeClaudeDesktopMode(config),
|
|
184
302
|
): ClaudeDesktopMode {
|
|
185
|
-
const resolved = resolveClaudeDesktopApplyMode(config);
|
|
303
|
+
const resolved = resolveClaudeDesktopApplyMode(config, observed);
|
|
186
304
|
if (resolved === "gateway") return resolved;
|
|
187
305
|
return connection.kind === "connected" ? "gateway" : "first-party";
|
|
188
306
|
}
|
|
189
307
|
|
|
190
308
|
export function parseDesktopApplyArgs(
|
|
191
309
|
flags: string[],
|
|
192
|
-
config: Pick<OcxConfig, "claudeCode" | "runtimeRole">,
|
|
310
|
+
config: Pick<OcxConfig, "claudeCode" | "port" | "runtimeRole">,
|
|
311
|
+
observed?: ClaudeDesktopModeObservation,
|
|
193
312
|
): { target: DesktopApplyTarget } | { error: string } {
|
|
194
313
|
const shapeFlags = flags.filter(arg => ["--static", "--hybrid", "--discovery-only"].includes(arg));
|
|
195
314
|
const wantsFirstParty = flags.includes("--first-party");
|
|
@@ -197,7 +316,9 @@ export function parseDesktopApplyArgs(
|
|
|
197
316
|
if (wantsFirstParty && wantsGateway) return { error: "--first-party cannot be combined with --gateway or gateway shape flags." };
|
|
198
317
|
const unknown = flags.filter(arg => !["--first-party", "--gateway", "--static", "--hybrid", "--discovery-only"].includes(arg));
|
|
199
318
|
if (unknown.length > 0) return { error: `알 수 없는 인자: ${unknown.join(" ")}` };
|
|
200
|
-
const kind: ClaudeDesktopMode = wantsFirstParty
|
|
319
|
+
const kind: ClaudeDesktopMode = wantsFirstParty
|
|
320
|
+
? "first-party"
|
|
321
|
+
: wantsGateway ? "gateway" : defaultDesktopApplyMode(config, readClientConnectionState(), observed ?? observeClaudeDesktopMode(config));
|
|
201
322
|
if (kind === "first-party") return { target: { kind } };
|
|
202
323
|
const parsedMode = parseDesktop3pModeArgs(shapeFlags);
|
|
203
324
|
if ("error" in parsedMode) return parsedMode;
|
|
@@ -205,18 +326,13 @@ export function parseDesktopApplyArgs(
|
|
|
205
326
|
}
|
|
206
327
|
|
|
207
328
|
/**
|
|
208
|
-
*
|
|
329
|
+
* What an apply without a flag says after it lands on gateway, the default.
|
|
209
330
|
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* The fix is not to change the resolution. It is to say, at the moment of the apply, that the
|
|
217
|
-
* other mode exists and what selects it. Returns null when the user asked for gateway explicitly,
|
|
218
|
-
* because they already know, and when first-party is simply unavailable here — a connected client
|
|
219
|
-
* or a disabled intercept cannot run it, so offering it would be advice that fails.
|
|
331
|
+
* It names why gateway was chosen (the default, a saved gateway mode, or a previous gateway apply),
|
|
332
|
+
* that first-party exists and which command selects it, and the account risk that comes with it,
|
|
333
|
+
* so nobody switches without reading it. Returns nothing when the user asked for gateway
|
|
334
|
+
* explicitly, because they already chose, and when first-party cannot run here — a connected client
|
|
335
|
+
* or a disabled intercept — because offering it would be advice that fails.
|
|
220
336
|
*/
|
|
221
337
|
export function gatewayModeExplanation(input: {
|
|
222
338
|
requestedExplicitly: boolean;
|
|
@@ -226,19 +342,20 @@ export function gatewayModeExplanation(input: {
|
|
|
226
342
|
if (input.requestedExplicitly) return [];
|
|
227
343
|
const connection = input.connection ?? readClientConnectionState();
|
|
228
344
|
if (connection.kind === "connected") return [];
|
|
229
|
-
|
|
230
|
-
// first-party cannot run here, and naming an unavailable alternative is advice that fails.
|
|
345
|
+
if (!claudeInterceptEnabled(input.config)) return [];
|
|
231
346
|
const savedMode = input.config.claudeCode?.desktopMode;
|
|
232
347
|
const hasSavedGateway = isClaudeDesktopMode(savedMode) && savedMode === "gateway";
|
|
233
348
|
const hasApplyMarker = input.config.claudeCode?.desktopProfile?.appliedFingerprint !== undefined;
|
|
234
|
-
if (!hasSavedGateway && !hasApplyMarker) return [];
|
|
235
349
|
const reason = hasSavedGateway
|
|
236
|
-
? "this machine has claudeCode.desktopMode saved as gateway"
|
|
237
|
-
:
|
|
350
|
+
? "because this machine has claudeCode.desktopMode saved as gateway; an existing install is never switched for you"
|
|
351
|
+
: hasApplyMarker
|
|
352
|
+
? "because this machine carries a previous gateway apply; an existing install is never switched for you"
|
|
353
|
+
: "because gateway is the default for Claude Desktop";
|
|
238
354
|
return [
|
|
239
|
-
`Applied the gateway profile
|
|
355
|
+
`Applied the gateway profile ${reason}.`,
|
|
240
356
|
"First-party keeps Desktop on your claude.ai account and routes only the Code tab through the local proxy:",
|
|
241
357
|
" ocx claude desktop apply --first-party",
|
|
358
|
+
`Account risk: ${FIRST_PARTY_ACCOUNT_RISK.message}`,
|
|
242
359
|
];
|
|
243
360
|
}
|
|
244
361
|
|
|
@@ -249,11 +366,23 @@ export function gatewayModeExplanation(input: {
|
|
|
249
366
|
*/
|
|
250
367
|
async function applyFirstPartyDesktop(
|
|
251
368
|
deps: ApplyProfileDeps,
|
|
252
|
-
): Promise<
|
|
369
|
+
): Promise<DesktopApplyResult> {
|
|
253
370
|
try { assertNoClientDisconnectPending(); } catch { return { ok: false, path: "", reason: "client_disconnect_pending" }; }
|
|
254
371
|
const connection = readClientConnectionState();
|
|
255
372
|
if (connection.kind === "connected") return { ok: false, path: "", reason: "first_party_requires_local_hub" };
|
|
256
373
|
if (connection.kind !== "disconnected") return { ok: false, path: "", reason: "client_connection_invalid" };
|
|
374
|
+
if (await liveDesktopProxy(deps)) {
|
|
375
|
+
try {
|
|
376
|
+
const applied = await pickerRuntimeRequest<DesktopApplyResult>(
|
|
377
|
+
"/api/claude-desktop/apply",
|
|
378
|
+
{ method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ mode: "first-party" }) },
|
|
379
|
+
deps,
|
|
380
|
+
);
|
|
381
|
+
return { ...applied, path: applied.path ?? "", delegated: true };
|
|
382
|
+
} catch (error) {
|
|
383
|
+
return { ok: false, path: "", reason: error instanceof Error ? error.message : "daemon apply failed" };
|
|
384
|
+
}
|
|
385
|
+
}
|
|
257
386
|
const config = loadConfig();
|
|
258
387
|
const desired = setIntegrationEnabled("claude-desktop", true);
|
|
259
388
|
if (!desired.ok) return { ok: false, path: "", reason: desired.message };
|
|
@@ -279,6 +408,7 @@ async function applyFirstPartyDesktop(
|
|
|
279
408
|
return {
|
|
280
409
|
ok: true,
|
|
281
410
|
path: applied.path,
|
|
411
|
+
picker: offlinePickerStatus(loadConfig(), deps.platform),
|
|
282
412
|
...(saved ? {} : { warning: "desktop mode marker was not saved" }),
|
|
283
413
|
};
|
|
284
414
|
}
|
|
@@ -287,10 +417,11 @@ export async function applyDesktop(
|
|
|
287
417
|
profile: DesktopProfile | undefined,
|
|
288
418
|
target: DesktopApplyTarget,
|
|
289
419
|
deps: ApplyProfileDeps = {},
|
|
290
|
-
): Promise<
|
|
420
|
+
): Promise<DesktopApplyResult> {
|
|
291
421
|
if (target.kind === "first-party") return applyFirstPartyDesktop(deps);
|
|
292
422
|
const result = await applyProfile(profile, target.mode, deps);
|
|
293
423
|
if (!result.ok) return result;
|
|
424
|
+
if (result.delegated) return result;
|
|
294
425
|
const modeSaved = saveDesktopMode("gateway", deps);
|
|
295
426
|
const warning = [result.warning, modeSaved ? "" : "desktop mode marker was not saved"].filter(Boolean).join(" ");
|
|
296
427
|
// The gateway mode is committed before retiring first-party settings.
|
|
@@ -305,7 +436,7 @@ export async function applyProfile(
|
|
|
305
436
|
profile: DesktopProfile | undefined,
|
|
306
437
|
mode: Desktop3pConfigMode,
|
|
307
438
|
deps: ApplyProfileDeps = {},
|
|
308
|
-
): Promise<
|
|
439
|
+
): Promise<DesktopApplyResult> {
|
|
309
440
|
try { assertNoClientDisconnectPending(); } catch { return { ok: false, path: "", reason: "client_disconnect_pending" }; }
|
|
310
441
|
const connection = readClientConnectionState();
|
|
311
442
|
if (connection.kind === "connected") return applyConnectedDesktopProfile(mode, connection, deps);
|
|
@@ -325,7 +456,7 @@ export async function applyProfile(
|
|
|
325
456
|
// serving process installs the map there; a local-only write leaves the
|
|
326
457
|
// daemon unable to decode aliases, and the provider rejects them (400).
|
|
327
458
|
const post = deps.postApplyImpl ?? (async (m: Desktop3pConfigMode, p: DesktopProfile) =>
|
|
328
|
-
runtimeRequest<{ ok?: boolean; path?: string; error?: string; saved?: boolean; warning?: string }>(
|
|
459
|
+
runtimeRequest<{ ok?: boolean; path?: string; error?: string; saved?: boolean; warning?: string; picker?: DesktopPickerStatus }>(
|
|
329
460
|
"/api/claude-desktop/apply",
|
|
330
461
|
// The daemon's config may be older than what we just saved, so the
|
|
331
462
|
// profile travels with the request instead of being re-read there.
|
|
@@ -341,6 +472,8 @@ export async function applyProfile(
|
|
|
341
472
|
return {
|
|
342
473
|
ok: true,
|
|
343
474
|
path: applied.path ?? "",
|
|
475
|
+
picker: applied.picker,
|
|
476
|
+
delegated: true,
|
|
344
477
|
...(warning ? { warning } : partial ? { warning: "applied marker was not saved" } : {}),
|
|
345
478
|
};
|
|
346
479
|
} catch (error) {
|
|
@@ -386,6 +519,130 @@ export async function applyProfile(
|
|
|
386
519
|
};
|
|
387
520
|
}
|
|
388
521
|
|
|
522
|
+
async function handleClaudeDesktopPickerCommand(
|
|
523
|
+
argv: string[],
|
|
524
|
+
config: OcxConfig,
|
|
525
|
+
deps: ApplyProfileDeps,
|
|
526
|
+
): Promise<number> {
|
|
527
|
+
const action = argv[1];
|
|
528
|
+
const rest = argv.slice(2);
|
|
529
|
+
const usage = "Usage: ocx claude desktop picker on|off|status|trust [--json]";
|
|
530
|
+
if (!action || !["on", "off", "status", "trust"].includes(action)) throw new CliUsageError(usage);
|
|
531
|
+
const wantsJson = takeJsonFlag(rest);
|
|
532
|
+
if (rest.length > 0 || (action !== "status" && wantsJson)) throw new CliUsageError(usage);
|
|
533
|
+
|
|
534
|
+
const requestPicker = (body: Record<string, unknown>) => pickerRuntimeRequest<PickerRouteResponse>(
|
|
535
|
+
"/api/claude-desktop/picker",
|
|
536
|
+
{ method: "PUT", headers: { "content-type": "application/json" }, body: JSON.stringify(body) },
|
|
537
|
+
deps,
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
if (action === "status") {
|
|
541
|
+
if (await liveDesktopProxy(deps)) {
|
|
542
|
+
try {
|
|
543
|
+
const response = await pickerRuntimeRequest<{ ok?: boolean; picker?: DesktopPickerStatus }>("/api/claude-desktop/picker", {}, deps);
|
|
544
|
+
printPickerStatus(response.picker, wantsJson);
|
|
545
|
+
} catch (error) {
|
|
546
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
547
|
+
return 1;
|
|
548
|
+
}
|
|
549
|
+
} else {
|
|
550
|
+
printPickerStatus(offlinePickerStatus(config, deps.platform), wantsJson);
|
|
551
|
+
}
|
|
552
|
+
return 0;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (action === "off") {
|
|
556
|
+
if (await liveDesktopProxy(deps)) {
|
|
557
|
+
try {
|
|
558
|
+
const response = await requestPicker({ enabled: false, persist: true });
|
|
559
|
+
printPickerStatus(response.picker, false);
|
|
560
|
+
} catch (error) {
|
|
561
|
+
if (isAnsweredPickerRefusal(error)) {
|
|
562
|
+
const body = (error as RuntimeApiError).body as PickerRouteResponse;
|
|
563
|
+
printPickerStatus(body.picker, false);
|
|
564
|
+
}
|
|
565
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
566
|
+
return 1;
|
|
567
|
+
}
|
|
568
|
+
} else {
|
|
569
|
+
if (!persistPickerPreference(false)) {
|
|
570
|
+
console.error("Could not persist claudeCode.intercept.picker=false");
|
|
571
|
+
return 1;
|
|
572
|
+
}
|
|
573
|
+
const removed = await (deps.removeDesktopPickerArtifacts ?? removeDesktopPickerArtifacts)({ configDir: getConfigDir(), security: deps.security, platform: deps.platform });
|
|
574
|
+
if (!removed.ok) console.error(`picker cleanup incomplete${removed.residual?.length ? `: ${removed.residual.join(", ")}` : ""}`);
|
|
575
|
+
printPickerStatus(offlinePickerStatus(loadConfig(), deps.platform), false);
|
|
576
|
+
if (!removed.ok) return 1;
|
|
577
|
+
}
|
|
578
|
+
console.log("Fully quit and reopen Claude Desktop");
|
|
579
|
+
return 0;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (action === "on" && !(await liveDesktopProxy(deps))) {
|
|
583
|
+
console.error("proxy_unavailable");
|
|
584
|
+
return 1;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
let localTrust: { callerAddedTrust: boolean; caPath: string; sha1: string } | undefined;
|
|
588
|
+
const sendEnable = async (trustedLocally = false): Promise<PickerRouteResponse> => requestPicker({
|
|
589
|
+
enabled: true,
|
|
590
|
+
persist: action === "on",
|
|
591
|
+
...(trustedLocally ? { trustedLocally: true, callerAddedTrust: localTrust?.callerAddedTrust ?? false } : {}),
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
if (action === "trust") {
|
|
595
|
+
const trusted = await trustPickerLocally(deps);
|
|
596
|
+
if (!trusted.ok) {
|
|
597
|
+
console.error(trusted.reason);
|
|
598
|
+
return 1;
|
|
599
|
+
}
|
|
600
|
+
localTrust = trusted;
|
|
601
|
+
if (!(await liveDesktopProxy(deps))) {
|
|
602
|
+
await compensateLocalPickerTrust(localTrust, deps);
|
|
603
|
+
console.error("proxy_unavailable");
|
|
604
|
+
return 1;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
let response: PickerRouteResponse;
|
|
609
|
+
try {
|
|
610
|
+
response = await sendEnable(action === "trust");
|
|
611
|
+
if (response.ok === false) {
|
|
612
|
+
printPickerStatus(response.picker, false);
|
|
613
|
+
console.error(response.reason ?? response.code ?? "picker_enable_refused");
|
|
614
|
+
return 1;
|
|
615
|
+
}
|
|
616
|
+
if (action === "on" && response.picker?.reason === "trust_pending") {
|
|
617
|
+
const trusted = await trustPickerLocally(deps);
|
|
618
|
+
if (!trusted.ok) {
|
|
619
|
+
console.error(trusted.reason);
|
|
620
|
+
return 1;
|
|
621
|
+
}
|
|
622
|
+
localTrust = trusted;
|
|
623
|
+
response = await sendEnable(true);
|
|
624
|
+
}
|
|
625
|
+
printPickerStatus(response.picker, false);
|
|
626
|
+
if (response.picker?.reason === "restart_required") console.log("Fully quit and reopen Claude Desktop");
|
|
627
|
+
const reason = response.picker?.reason;
|
|
628
|
+
return response.ok === false || (reason !== "active" && reason !== "restart_required") ? 1 : 0;
|
|
629
|
+
} catch (error) {
|
|
630
|
+
if (isAnsweredPickerRefusal(error)) {
|
|
631
|
+
const body = (error as RuntimeApiError).body as PickerRouteResponse;
|
|
632
|
+
printPickerStatus(body.picker, false);
|
|
633
|
+
console.error(body.reason ?? body.code ?? (error instanceof Error ? error.message : String(error)));
|
|
634
|
+
return 1;
|
|
635
|
+
}
|
|
636
|
+
if (isAmbiguousPickerTransport(error)) {
|
|
637
|
+
console.error("state unknown - run ocx claude desktop picker status");
|
|
638
|
+
return 1;
|
|
639
|
+
}
|
|
640
|
+
if (localTrust) await compensateLocalPickerTrust(localTrust, deps);
|
|
641
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
642
|
+
return 1;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
389
646
|
export async function handleClaudeDesktopCommand(argv: string[], deps: ApplyProfileDeps = {}): Promise<number> {
|
|
390
647
|
const command = argv[0];
|
|
391
648
|
if (command === "help" || command === "--help" || command === "-h") {
|
|
@@ -421,6 +678,8 @@ export async function handleClaudeDesktopCommand(argv: string[], deps: ApplyProf
|
|
|
421
678
|
if (target.kind === "first-party") {
|
|
422
679
|
console.log(`Claude Desktop first-party 설정을 적용했습니다: ${result.path}`);
|
|
423
680
|
console.log("Desktop 앱 설정은 그대로이며, Code 탭의 Claude Code만 로컬 프록시를 거칩니다.");
|
|
681
|
+
console.warn(`⚠️ ${FIRST_PARTY_ACCOUNT_RISK.message}`);
|
|
682
|
+
printPickerStatus(result.picker, false);
|
|
424
683
|
} else {
|
|
425
684
|
console.log(`Claude Desktop gateway 설정을 적용했습니다: ${result.path}`);
|
|
426
685
|
for (const line of gatewayModeExplanation({
|
|
@@ -454,6 +713,7 @@ export async function handleClaudeDesktopCommand(argv: string[], deps: ApplyProf
|
|
|
454
713
|
console.warn("Local client profile only; connected Desktop apply uses the hub profile.");
|
|
455
714
|
}
|
|
456
715
|
const config = loadConfig();
|
|
716
|
+
if (command === "picker") return await handleClaudeDesktopPickerCommand(argv, config, deps);
|
|
457
717
|
// `status` is API-backed and must NOT build local state first: the whole point of the
|
|
458
718
|
// route the GUI polls (/api/claude-desktop/status) is the applied-vs-desired comparison,
|
|
459
719
|
// including staleness, drift and health, which only the running proxy knows. `show`
|
|
@@ -471,6 +731,32 @@ export async function handleClaudeDesktopCommand(argv: string[], deps: ApplyProf
|
|
|
471
731
|
}
|
|
472
732
|
return 0;
|
|
473
733
|
}
|
|
734
|
+
// Bindings are API-backed for the same reason as `status`: the running proxy routes with
|
|
735
|
+
// its live config, so the change must land there, not only in the file.
|
|
736
|
+
if (command === "bind" || command === "unbind") {
|
|
737
|
+
const [, pickerId, route, ...extra] = argv;
|
|
738
|
+
const usage = command === "bind"
|
|
739
|
+
? "Usage: ocx claude desktop bind <picker-model-id> <provider/model|native/slug>"
|
|
740
|
+
: "Usage: ocx claude desktop unbind <picker-model-id>";
|
|
741
|
+
if (!pickerId || (command === "bind" ? !route || extra.length > 0 : route !== undefined)) throw new CliUsageError(usage);
|
|
742
|
+
const body = command === "bind" ? { set: { [pickerId]: route! } } : { remove: [pickerId] };
|
|
743
|
+
const result = await runtimeRequest<{ modelBindings?: Record<string, string> }>("/api/claude-desktop/first-party-bindings", {
|
|
744
|
+
method: "PUT",
|
|
745
|
+
headers: { "content-type": "application/json" },
|
|
746
|
+
body: JSON.stringify(body),
|
|
747
|
+
});
|
|
748
|
+
const bindings = result.modelBindings ?? {};
|
|
749
|
+
console.log(command === "bind"
|
|
750
|
+
? `Code 탭 피커의 ${pickerId}를 ${route}로 연결했습니다. 다음 요청부터 적용됩니다.`
|
|
751
|
+
: `${pickerId} 연결을 해제했습니다.`);
|
|
752
|
+
const ids = Object.keys(bindings).sort();
|
|
753
|
+
if (ids.length === 0) console.log("현재 연결된 피커 모델이 없습니다.");
|
|
754
|
+
for (const id of ids) console.log(` ${id} -> ${bindings[id]}`);
|
|
755
|
+
if (resolveClaudeDesktopMode(config, observeClaudeDesktopMode(config)) === "gateway") {
|
|
756
|
+
console.warn("⚠️ Desktop이 gateway 모드입니다. 바인딩은 first-party 모드(ocx claude desktop apply --first-party)의 Code 탭과 claude CLI에만 적용됩니다.");
|
|
757
|
+
}
|
|
758
|
+
return 0;
|
|
759
|
+
}
|
|
474
760
|
const state = await buildClaudeDesktopState(config);
|
|
475
761
|
if (command === "show") {
|
|
476
762
|
const rest = argv.slice(1);
|
package/src/cli/claude.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { spawn } from "node:child_process";
|
|
11
11
|
import { loadConfig } from "../config";
|
|
12
12
|
import { injectClaudeAgentDefs } from "../claude/agents-inject";
|
|
13
|
-
import { CLAUDE_ALIAS_PREFIX_V1, CLAUDE_ALIAS_PREFIX_V2 } from "../claude/alias";
|
|
13
|
+
import { CLAUDE_ALIAS_PREFIX_CURRENT, CLAUDE_ALIAS_PREFIX_CURRENT_V2, CLAUDE_ALIAS_PREFIX_V1, CLAUDE_ALIAS_PREFIX_V2 } from "../claude/alias";
|
|
14
14
|
import { claudeToolSearchEnv, effectiveModelEnv, resolveAutoContext } from "../claude/context-windows";
|
|
15
15
|
import { claudeConfigDir, refreshGatewayModelCacheFromProxy } from "../claude/gateway-cache";
|
|
16
16
|
import { commandInvocation } from "../lib/win-exec";
|
|
@@ -30,7 +30,7 @@ import { readServiceApiTokenState, type ServiceApiTokenState } from "../lib/serv
|
|
|
30
30
|
import { DEFAULT_CATALOG_PATH } from "../codex/paths";
|
|
31
31
|
import { readFileSync } from "node:fs";
|
|
32
32
|
import { join } from "node:path";
|
|
33
|
-
import { aliasForNative, aliasForRoute } from "../claude/alias";
|
|
33
|
+
import { aliasForNative, aliasForRoute, legacyAliasForNative, legacyAliasForRoute } from "../claude/alias";
|
|
34
34
|
import { desktop3pAlias } from "../claude/desktop-3p";
|
|
35
35
|
|
|
36
36
|
export interface ClaudeLaunchEnv {
|
|
@@ -373,12 +373,13 @@ export function buildClaudeEnv(
|
|
|
373
373
|
// worse than the problem. So this stays opt-in per config rather than
|
|
374
374
|
// unconditional, and setDefault keeps an operator's own export.
|
|
375
375
|
setDefault("ENABLE_TOOL_SEARCH", claudeToolSearchEnv(config.claudeCode?.toolSearch));
|
|
376
|
-
// Context-window override: the official pair — MAX_CONTEXT_TOKENS alone is ignored
|
|
377
|
-
// for recognized claude-shaped ids unless DISABLE_COMPACT=1 rides along (devlog 135).
|
|
378
376
|
const maxCtx = config.claudeCode?.maxContextTokens;
|
|
379
377
|
if (typeof maxCtx === "number" && Number.isFinite(maxCtx) && maxCtx > 0) {
|
|
380
378
|
setDefault("CLAUDE_CODE_MAX_CONTEXT_TOKENS", String(Math.floor(maxCtx)));
|
|
381
|
-
|
|
379
|
+
// Claude Code 2.1.278 honors this without DISABLE_COMPACT when the model id
|
|
380
|
+
// does not start with "claude-" (gF). Current ocx-claude aliases qualify.
|
|
381
|
+
// A persisted claude-ocx id is still claude-shaped, so that one session keeps
|
|
382
|
+
// the 200k accounting until the picker is moved to the new id.
|
|
382
383
|
}
|
|
383
384
|
// Auto-context (devlog 260712 020): min(believed window, env) inside the CLI means
|
|
384
385
|
// one global env acts as a per-model floor — [1m]-marked models compact here while
|
|
@@ -464,10 +465,15 @@ export function readConnectedClaudeContextWindows(path = DEFAULT_CATALOG_PATH):
|
|
|
464
465
|
const id = slug.slice(slash + 1);
|
|
465
466
|
const routeAlias = aliasForRoute(provider, id);
|
|
466
467
|
if (routeAlias) put(routeAlias, contextWindow);
|
|
468
|
+
// A selector saved under the legacy claude-ocx spelling keeps its window here too.
|
|
469
|
+
const legacyRoute = legacyAliasForRoute(provider, id);
|
|
470
|
+
if (legacyRoute) put(legacyRoute, contextWindow);
|
|
467
471
|
put(desktop3pAlias(provider, id), contextWindow);
|
|
468
472
|
} else {
|
|
469
473
|
const nativeAlias = aliasForNative(slug);
|
|
470
474
|
if (nativeAlias) put(nativeAlias, contextWindow);
|
|
475
|
+
const legacyNative = legacyAliasForNative(slug);
|
|
476
|
+
if (legacyNative) put(legacyNative, contextWindow);
|
|
471
477
|
put(desktop3pAlias("native", slug), contextWindow);
|
|
472
478
|
}
|
|
473
479
|
}
|
|
@@ -485,7 +491,8 @@ export async function ensureProxyForClaude(deps: ClaudeProxyEnsureDeps = {}): Pr
|
|
|
485
491
|
// A proxy that has only just bound can miss a single probe while its event loop
|
|
486
492
|
// is still settling startup work — the same just-started race the stop paths
|
|
487
493
|
// already retry for (#764, SERVICE_STOP_LIVENESS). Only the attempts budget is
|
|
488
|
-
// borrowed here; the probe timeout remains DEFAULT_PROBE_TIMEOUT_MS (750 ms
|
|
494
|
+
// borrowed here; the probe timeout remains DEFAULT_PROBE_TIMEOUT_MS (750 ms unless
|
|
495
|
+
// OCX_PROBE_TIMEOUT_MS raises it).
|
|
489
496
|
// Without this, `ocx claude` can spawn a second proxy while the first is serving.
|
|
490
497
|
const live = await (deps.findLiveProxy ?? findLiveProxy)({ attempts: 3 });
|
|
491
498
|
if (live) return live.port;
|
|
@@ -592,7 +599,8 @@ const DESKTOP_3P_ALIAS = /^claude-opus-4(?:-8)?-[a-z][0-9a-z]{2}$/;
|
|
|
592
599
|
export function isProxyOnlyModelId(value: string, providerNames: readonly string[] = []): boolean {
|
|
593
600
|
const id = value.trim().replace(/\[1m\]$/, "");
|
|
594
601
|
if (!id) return false;
|
|
595
|
-
|
|
602
|
+
const aliasPrefixes = [CLAUDE_ALIAS_PREFIX_CURRENT, CLAUDE_ALIAS_PREFIX_CURRENT_V2, CLAUDE_ALIAS_PREFIX_V1, CLAUDE_ALIAS_PREFIX_V2];
|
|
603
|
+
if (aliasPrefixes.some(prefix => id.startsWith(prefix)) || DESKTOP_3P_ALIAS.test(id)) {
|
|
596
604
|
return true;
|
|
597
605
|
}
|
|
598
606
|
const slash = id.indexOf("/");
|
|
@@ -28,6 +28,7 @@ export function skipsCodexShimAutoRestore(command: string | undefined, args: str
|
|
|
28
28
|
// The entire updater-inspection namespace is zero-effect, including malformed
|
|
29
29
|
// or future actions. A later `apply` implementation must own its preflight.
|
|
30
30
|
if (command === "system" && args[1] === "codex-cli-update") return true;
|
|
31
|
+
if (command === "__update-badge") return true;
|
|
31
32
|
return command === "codex-shim" && ["install", "uninstall", "remove"].includes(args[1] ?? "");
|
|
32
33
|
}
|
|
33
34
|
|