@bitkyc08/opencodex 2.10.2 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -0
- package/bin/ocx.mjs +10 -0
- package/gui/dist/assets/index-Bk-PN-70.css +1 -0
- package/gui/dist/assets/index-BynIEIV-.js +70 -0
- package/gui/dist/index.html +2 -2
- package/package.json +4 -2
- package/src/adapters/cursor/effort-map.ts +11 -0
- package/src/adapters/cursor/live-transport.ts +11 -0
- package/src/adapters/cursor/native-exec-fs.ts +9 -6
- package/src/adapters/cursor/native-exec.ts +4 -2
- package/src/adapters/cursor/protobuf-events.ts +176 -4
- package/src/adapters/cursor/request-builder.ts +15 -4
- package/src/adapters/cursor/tool-definitions.ts +118 -2
- package/src/adapters/google.ts +15 -5
- package/src/adapters/openai-chat.ts +24 -2
- package/src/adapters/openai-responses.ts +2 -1
- package/src/bridge.ts +9 -5
- package/src/chat/outbound.ts +4 -3
- package/src/claude/desktop-3p.ts +222 -2
- package/src/claude/outbound.ts +15 -6
- package/src/cli/account-api.ts +4 -0
- package/src/cli/account-extended.ts +112 -0
- package/src/cli/account.ts +23 -6
- package/src/cli/claude-desktop.ts +26 -3
- package/src/cli/config-command.ts +9 -0
- package/src/cli/help.ts +18 -2
- package/src/cli/index.ts +277 -55
- package/src/cli/models.ts +5 -1
- package/src/cli/provider.ts +8 -2
- package/src/cli/ready.ts +301 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/clients/config-export.ts +12 -19
- package/src/codex/account-lifecycle.ts +3 -0
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/auth-api.ts +83 -0
- package/src/codex/auth-context.ts +5 -2
- package/src/codex/catalog/provider-fetch.ts +11 -0
- package/src/codex/catalog/sync.ts +23 -1
- package/src/codex/codex-write-lock.ts +16 -4
- package/src/codex/desired-state.ts +37 -4
- package/src/codex/history-job.ts +15 -5
- package/src/codex/history-provider.ts +31 -14
- package/src/codex/history-worker.ts +28 -4
- package/src/codex/inject-coordination.ts +13 -1
- package/src/codex/inject.ts +360 -66
- package/src/codex/internal/history-writer.ts +1 -1
- package/src/codex/native-main-lock-file.ts +5 -1
- package/src/codex/native-main-owner.ts +17 -3
- package/src/codex/native-profile-manager.ts +19 -0
- package/src/codex/native-profile-startup.ts +8 -0
- package/src/codex/native-residue.ts +140 -27
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/refresh.ts +7 -0
- package/src/codex/routing.ts +177 -36
- package/src/codex/subagent-model-fallback.ts +34 -4
- package/src/codex/sync.ts +61 -0
- package/src/codex/upstream-host-health.ts +329 -31
- package/src/combos/request.ts +2 -0
- package/src/config.ts +221 -2
- package/src/images/loop.ts +1 -1
- package/src/integrations/native/ownership-preflight.ts +39 -2
- package/src/lib/bun-stream-caps.ts +3 -3
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/windows-secret-acl.ts +141 -39
- package/src/lib/windows-user-principal.ts +283 -0
- package/src/lib/winsw.ts +18 -2
- package/src/oauth/key-providers.ts +12 -0
- package/src/providers/derive.ts +54 -2
- package/src/providers/free-directory.ts +6 -5
- package/src/providers/model-discovery.ts +9 -3
- package/src/providers/quota.ts +592 -0
- package/src/providers/registry.ts +316 -13
- package/src/responses/parser.ts +26 -10
- package/src/responses/reasoning-replay-cache.ts +1 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +2 -1
- package/src/server/auth-cors.ts +44 -13
- package/src/server/chat-completions.ts +0 -4
- package/src/server/claude-messages.ts +73 -15
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/index.ts +328 -111
- package/src/server/lifecycle.ts +36 -0
- package/src/server/management/agent-settings-routes.ts +147 -56
- package/src/server/management/config-routes.ts +7 -2
- package/src/server/management/context.ts +4 -0
- package/src/server/management/native-integration-routes.ts +199 -20
- package/src/server/management/provider-routes.ts +41 -0
- package/src/server/management/routing-profile-routes.ts +234 -5
- package/src/server/management/system-restart.ts +12 -10
- package/src/server/management/system-routes.ts +20 -0
- package/src/server/management-auth.ts +51 -3
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +129 -4
- package/src/server/readiness.ts +99 -0
- package/src/server/relay.ts +113 -97
- package/src/server/request-log.ts +10 -4
- package/src/server/responses/compact.ts +107 -12
- package/src/server/responses/core.ts +220 -39
- package/src/server/responses-item-id-repair.ts +22 -3
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +25 -14
- package/src/server/ws-bridge.ts +27 -22
- package/src/service-manager-probe.ts +520 -10
- package/src/service.ts +134 -2
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +74 -9
- package/src/types.ts +68 -2
- package/src/update/index.ts +12 -0
- package/src/update/job.ts +392 -18
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/usage/log.ts +1 -1
- package/src/vision/index.ts +77 -2
- package/src/web-search/loop.ts +1 -1
- package/src/web-search/parse.ts +4 -1
- package/gui/dist/assets/index-BKVqyYqT.js +0 -70
- package/gui/dist/assets/index-Ca_3269W.css +0 -1
package/src/cli/account.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { loadConfig } from "../config";
|
|
3
3
|
import { providerCodexAccountMode } from "../providers/registry";
|
|
4
4
|
import type { OcxConfig } from "../types";
|
|
5
|
-
import { cmdAddKey, cmdAlias, cmdAutoSwitch, cmdClearCooldown, cmdRefresh, cmdRemove } from "./account-extended";
|
|
5
|
+
import { cmdAddKey, cmdAlias, cmdAutoSwitch, cmdClearCooldown, cmdPriority, cmdRefresh, cmdRemove } from "./account-extended";
|
|
6
6
|
import { apiError, apiJson, classifyAccount, fetchRows, proxyUnreachable, resolveBaseUrl, type AccountDeps, type AccountRow, type AccountType, type ApiResult }
|
|
7
7
|
from "./account-api";
|
|
8
8
|
|
|
@@ -22,6 +22,7 @@ const ACCOUNT_USAGE = `Usage:
|
|
|
22
22
|
ocx account refresh <provider> [--json]
|
|
23
23
|
ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]
|
|
24
24
|
ocx account alias <provider> <account-or-key-id> <display-name|-> [--json]
|
|
25
|
+
ocx account priority <provider> <account-id|main> [<-100..100|first|earlier|normal|later|last|reset>] [--json]
|
|
25
26
|
ocx account remove <provider> <account-or-key-id|main> --yes [--json]
|
|
26
27
|
ocx account clear-cooldown <provider> <account-id|main> [--json]
|
|
27
28
|
ocx account add-key <provider> [--label <label>] [--json]
|
|
@@ -67,11 +68,24 @@ function statusText(row: AccountRow): string {
|
|
|
67
68
|
return parts.join(" ");
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
/** Signed so the sort direction reads off the column; "-" where ordering does not apply. */
|
|
72
|
+
function priorityText(row: AccountRow): string {
|
|
73
|
+
if (row.priority === undefined) return "-";
|
|
74
|
+
return row.priority > 0 ? `+${row.priority}` : String(row.priority);
|
|
75
|
+
}
|
|
76
|
+
|
|
70
77
|
export function formatAccountTable(rows: AccountRow[]): string {
|
|
71
|
-
const header = ["PROVIDER", "TYPE", "ID", "PLAN/LABEL", "STATUS"];
|
|
78
|
+
const header = ["PROVIDER", "TYPE", "ID", "PLAN/LABEL", "PRIORITY", "STATUS"];
|
|
72
79
|
const data = rows.map(r => {
|
|
73
80
|
const keyLabel = r.masked && r.label !== r.masked ? `${r.masked} (${r.label})` : r.masked;
|
|
74
|
-
return [
|
|
81
|
+
return [
|
|
82
|
+
r.provider,
|
|
83
|
+
r.type,
|
|
84
|
+
displayId(r.id),
|
|
85
|
+
r.type === "api-key" ? keyLabel ?? "-" : r.label ?? "-",
|
|
86
|
+
priorityText(r),
|
|
87
|
+
statusText(r),
|
|
88
|
+
];
|
|
75
89
|
});
|
|
76
90
|
const widths = header.map((h, i) => Math.max(h.length, ...data.map(d => d[i]!.length)));
|
|
77
91
|
const line = (cols: string[]) => cols.map((c, i) => c.padEnd(widths[i]!)).join(" ").trimEnd();
|
|
@@ -244,9 +258,11 @@ async function cmdUse(rest: string[], deps: AccountDeps): Promise<number> {
|
|
|
244
258
|
if (wantsJson) console.log(JSON.stringify({ ok: true, provider: name, type: c.type, activeId }, null, 2));
|
|
245
259
|
else console.log(`${name}: active ${c.type === "api-key" ? "key" : "account"} is now ${displayId(activeId)}`);
|
|
246
260
|
if (c.type === "codex") {
|
|
247
|
-
console.error("
|
|
248
|
-
|
|
249
|
-
|
|
261
|
+
console.error("Takes effect immediately; running threads move on their next request, and in-flight requests keep the account they captured.");
|
|
262
|
+
const active = await apiJson(deps, baseUrl, "GET", "/api/codex-auth/active");
|
|
263
|
+
if (active.status === 200 && typeof active.json.autoSwitchThreshold === "number" && active.json.autoSwitchThreshold > 0) {
|
|
264
|
+
console.error(`Note: auto-switch (threshold ${active.json.autoSwitchThreshold}%) may override this pin.`);
|
|
265
|
+
}
|
|
250
266
|
}
|
|
251
267
|
return 0;
|
|
252
268
|
}
|
|
@@ -260,6 +276,7 @@ export async function cmdAccount(args: string[], deps: AccountDeps = {}): Promis
|
|
|
260
276
|
if (sub === "refresh") return await cmdRefresh(rest, deps);
|
|
261
277
|
if (sub === "auto-switch") return await cmdAutoSwitch(rest, deps);
|
|
262
278
|
if (sub === "alias" || sub === "rename") return await cmdAlias(rest, deps);
|
|
279
|
+
if (sub === "priority") return await cmdPriority(rest, deps);
|
|
263
280
|
if (sub === "remove") return await cmdRemove(rest, deps);
|
|
264
281
|
if (sub === "clear-cooldown") return await cmdClearCooldown(rest, deps);
|
|
265
282
|
if (sub === "add-key") return await cmdAddKey(rest, deps);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { loadConfig, saveConfigPreservingClaudeCode } from "../config";
|
|
4
|
+
import { setIntegrationEnabled } from "../codex/desired-state";
|
|
4
5
|
import {
|
|
5
6
|
DESKTOP_FAMILIES,
|
|
6
7
|
moveDesktopRoute,
|
|
@@ -41,7 +42,11 @@ export async function applyProfile(
|
|
|
41
42
|
profile: DesktopProfile,
|
|
42
43
|
mode: Desktop3pConfigMode,
|
|
43
44
|
deps: ApplyProfileDeps = {},
|
|
44
|
-
): Promise<{ ok: boolean; path: string; reason?: string }> {
|
|
45
|
+
): Promise<{ ok: boolean; path: string; reason?: string; warning?: string }> {
|
|
46
|
+
// Explicit apply is an enable action. Persist intent before any Desktop write
|
|
47
|
+
// so a process crash cannot leave a gateway profile that startup immediately removes.
|
|
48
|
+
const desired = setIntegrationEnabled("claude-desktop", true);
|
|
49
|
+
if (!desired.ok) return { ok: false, path: "", reason: desired.message };
|
|
45
50
|
const config = loadConfig();
|
|
46
51
|
const state = await buildClaudeDesktopState(config, profile);
|
|
47
52
|
config.claudeCode = { ...(config.claudeCode ?? {}), desktopProfile: state.profile };
|
|
@@ -52,7 +57,7 @@ export async function applyProfile(
|
|
|
52
57
|
// serving process installs the map there; a local-only write leaves the
|
|
53
58
|
// daemon unable to decode aliases, and the provider rejects them (400).
|
|
54
59
|
const post = deps.postApplyImpl ?? (async (m: Desktop3pConfigMode, p: DesktopProfile) =>
|
|
55
|
-
runtimeRequest<{ ok?: boolean; path?: string; error?: string }>(
|
|
60
|
+
runtimeRequest<{ ok?: boolean; path?: string; error?: string; saved?: boolean; warning?: string }>(
|
|
56
61
|
"/api/claude-desktop/apply",
|
|
57
62
|
// The daemon's config may be older than what we just saved, so the
|
|
58
63
|
// profile travels with the request instead of being re-read there.
|
|
@@ -61,12 +66,26 @@ export async function applyProfile(
|
|
|
61
66
|
try {
|
|
62
67
|
const applied = await post(mode, state.profile);
|
|
63
68
|
if (applied.ok === false) return { ok: false, path: applied.path ?? "", reason: applied.error ?? "daemon apply failed" };
|
|
64
|
-
|
|
69
|
+
// Partial success: Desktop was written but the applied marker was not
|
|
70
|
+
// persisted. Pass the degradation up instead of reporting a clean apply.
|
|
71
|
+
const partial = (applied as { saved?: boolean; warning?: string }).saved === false;
|
|
72
|
+
return {
|
|
73
|
+
ok: true,
|
|
74
|
+
path: applied.path ?? "",
|
|
75
|
+
...(partial ? { warning: (applied as { warning?: string }).warning ?? "applied marker was not saved" } : {}),
|
|
76
|
+
};
|
|
65
77
|
} catch (error) {
|
|
66
78
|
return { ok: false, path: "", reason: error instanceof Error ? error.message : String(error) };
|
|
67
79
|
}
|
|
68
80
|
}
|
|
69
81
|
const allModels = await fetchAllModels(config);
|
|
82
|
+
// The toggle can persist OFF while fetchAllModels was awaiting (same race the
|
|
83
|
+
// management writers fence). Re-read persisted intent immediately before the
|
|
84
|
+
// writer; a lost race is a discriminated skip, not a write.
|
|
85
|
+
const { claudeDesktopIntegrationEnabledNow } = await import("../codex/desired-state");
|
|
86
|
+
if (!claudeDesktopIntegrationEnabledNow()) {
|
|
87
|
+
return { ok: false, path: "", reason: "desired_state_changed" };
|
|
88
|
+
}
|
|
70
89
|
const routed = filterCatalogVisibleModels(allModels, config).map(model => ({
|
|
71
90
|
provider: model.provider,
|
|
72
91
|
id: model.id,
|
|
@@ -111,6 +130,9 @@ export async function handleClaudeDesktopCommand(argv: string[], deps: ApplyProf
|
|
|
111
130
|
return 1;
|
|
112
131
|
}
|
|
113
132
|
console.log(`Claude Desktop 설정을 적용했습니다: ${result.path}`);
|
|
133
|
+
// The write landed; only the bookkeeping marker did not. Saying nothing
|
|
134
|
+
// would leave the saved-vs-applied display wrong with no explanation.
|
|
135
|
+
if (result.warning) console.warn(`⚠️ ${result.warning}`);
|
|
114
136
|
console.log("Claude Desktop을 완전히 종료한 뒤 다시 열어 주세요.");
|
|
115
137
|
return 0;
|
|
116
138
|
} catch (error) {
|
|
@@ -175,6 +197,7 @@ export async function handleClaudeDesktopCommand(argv: string[], deps: ApplyProf
|
|
|
175
197
|
if (flags.includes("--apply")) {
|
|
176
198
|
const result = await applyProfile(reconciled, "static", deps);
|
|
177
199
|
if (!result.ok) { console.error(`프로필은 저장했지만 Desktop 적용에 실패했습니다: ${result.reason ?? "unknown error"}`); return 1; }
|
|
200
|
+
if (result.warning) console.warn(`⚠️ ${result.warning}`);
|
|
178
201
|
}
|
|
179
202
|
console.log("Claude Desktop 프로필을 가져왔습니다.");
|
|
180
203
|
return 0;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { clearCodexAccountPin } from "../codex/account-priority";
|
|
2
3
|
import { getConfigPath, readConfigDiagnostics, saveConfig, validateConfigCandidate } from "../config";
|
|
3
4
|
import type { OcxConfig } from "../types";
|
|
4
5
|
import { CliUsageError, printData, rejectArgs, runCliAction, takeFlag } from "./runtime-api";
|
|
@@ -102,6 +103,14 @@ export async function handleConfigCommand(argv: string[]): Promise<number> {
|
|
|
102
103
|
setPath(candidate, path, raw === undefined ? undefined : parseValue(raw), action === "unset");
|
|
103
104
|
const config = validate(candidate);
|
|
104
105
|
const savedValue = action === "unset" ? null : getPath(config, path);
|
|
106
|
+
// Setting the order here is the operator restating it, exactly as through
|
|
107
|
+
// `ocx account priority` or the management route, so it releases the manual pin
|
|
108
|
+
// for the same reason those do: a pin made before any order existed would
|
|
109
|
+
// otherwise outrank every order set afterwards, capping the pool at the pinned
|
|
110
|
+
// account's tier with nothing on any surface explaining why. `import` is
|
|
111
|
+
// deliberately not covered — that file supplies its own pin, so there is no
|
|
112
|
+
// stale one to release.
|
|
113
|
+
if (pathSegments(path)[0] === "codexAccountPriorities") clearCodexAccountPin(config);
|
|
105
114
|
saveConfig(config);
|
|
106
115
|
printData({ ok: true, path, value: redact(savedValue, path.split(".").at(-1)) }, wantsJson,
|
|
107
116
|
[`${action === "unset" ? "Unset" : "Set"} ${path}.`]);
|
package/src/cli/help.ts
CHANGED
|
@@ -110,7 +110,7 @@ const helpEntries: Record<string, HelpEntry> = {
|
|
|
110
110
|
],
|
|
111
111
|
},
|
|
112
112
|
account: {
|
|
113
|
-
usage: "ocx account <list|current|use|refresh|auto-switch|login|reauth|code|cancel|remove|add-key|reset-credits|main> ...",
|
|
113
|
+
usage: "ocx account <list|current|use|refresh|auto-switch|priority|login|reauth|code|cancel|remove|add-key|reset-credits|main> ...",
|
|
114
114
|
summary: "List and switch provider accounts and API-key pools (GUI parity).",
|
|
115
115
|
details: [
|
|
116
116
|
"list [provider] Codex account pool, OAuth accounts and API keys (identifiers shown masked as the API returns them).",
|
|
@@ -118,12 +118,14 @@ const helpEntries: Record<string, HelpEntry> = {
|
|
|
118
118
|
"use <provider> <id> Switch the active credential; 'main' selects the Codex App login.",
|
|
119
119
|
"refresh <provider> Force-refresh Codex or provider quota reports.",
|
|
120
120
|
"auto-switch <provider> <on|off|status|threshold N> Control the Codex pool threshold.",
|
|
121
|
+
"priority <provider> <id|main> [first|earlier|normal|later|last|-100..100|reset] Selection order; omit the value to read it.",
|
|
121
122
|
"remove <provider> <id> --yes Remove a stored account or key after an existence check.",
|
|
122
123
|
"add-key <provider> [--label <label>] Add a key read only from piped stdin.",
|
|
123
124
|
"login/reauth/code/cancel Run browser or manual-code auth from a headless shell.",
|
|
124
125
|
"reset-credits <id|main> [--consume --yes] Inspect or consume Codex reset credits.",
|
|
125
126
|
"main <subcommand> Manage the physical native Codex login separately from Pool routing.",
|
|
126
|
-
"
|
|
127
|
+
"Switching the active account takes effect immediately; running threads move on their next request, and in-flight requests keep the account they captured.",
|
|
128
|
+
"A selection-order change applies from the next unbound request and never moves a bound thread.",
|
|
127
129
|
],
|
|
128
130
|
},
|
|
129
131
|
models: {
|
|
@@ -249,6 +251,19 @@ const helpEntries: Record<string, HelpEntry> = {
|
|
|
249
251
|
summary: "Check proxy health. Exits 0 if healthy, 1 otherwise.",
|
|
250
252
|
details: ["Use --json for structured output: {ok, pid, port}."],
|
|
251
253
|
},
|
|
254
|
+
ready: {
|
|
255
|
+
usage: "ocx ready [--json] [--wait [--timeout <seconds>]]",
|
|
256
|
+
summary: "Check post-sync readiness. Exits 0 only when ready.",
|
|
257
|
+
details: [
|
|
258
|
+
"Exact unauthenticated GET /readyz returns HTTP 200 when ready, or 503 with Retry-After: 1 for pending or failed.",
|
|
259
|
+
"Its sanitized HTTP identity is {service, version, uptime, pid, port, status}; /healthz is separate liveness, not readiness.",
|
|
260
|
+
"Default is a single identity-checked /readyz probe; old proxies without /readyz fail closed as unreachable.",
|
|
261
|
+
"--wait polls until ready or timeout, but exits immediately on terminal failed (default 45s, max 300s).",
|
|
262
|
+
"--timeout requires --wait and accepts a positive integer (1..300).",
|
|
263
|
+
"--json emits {ready, status, pid, port}; status is one of ready|pending|failed|unreachable.",
|
|
264
|
+
"Invalid or unknown arguments exit 64. Not-ready, pending, failed, timeout, and unreachable exit 1.",
|
|
265
|
+
],
|
|
266
|
+
},
|
|
252
267
|
};
|
|
253
268
|
|
|
254
269
|
function packageVersion(): string {
|
|
@@ -290,6 +305,7 @@ Usage:
|
|
|
290
305
|
ocx restart Stop and restart the proxy
|
|
291
306
|
ocx v2 <sub> multi_agent_v2 surface (status|on|off|mode|threads)
|
|
292
307
|
ocx health [--json] Check proxy health (exit 0=healthy, 1=not)
|
|
308
|
+
ocx ready [--json] [--wait [--timeout <s>]] Check post-sync readiness (exit 0 only when ready)
|
|
293
309
|
ocx provider <sub> Providers, connectivity, quota, and selected models
|
|
294
310
|
ocx account <sub> Accounts, login/reauth, key pools, and quota controls
|
|
295
311
|
ocx models <sub> Live/custom models, visibility, context, and shadow calls
|