@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.
Files changed (121) hide show
  1. package/README.md +31 -0
  2. package/bin/ocx.mjs +10 -0
  3. package/gui/dist/assets/index-Bk-PN-70.css +1 -0
  4. package/gui/dist/assets/index-BynIEIV-.js +70 -0
  5. package/gui/dist/index.html +2 -2
  6. package/package.json +4 -2
  7. package/src/adapters/cursor/effort-map.ts +11 -0
  8. package/src/adapters/cursor/live-transport.ts +11 -0
  9. package/src/adapters/cursor/native-exec-fs.ts +9 -6
  10. package/src/adapters/cursor/native-exec.ts +4 -2
  11. package/src/adapters/cursor/protobuf-events.ts +176 -4
  12. package/src/adapters/cursor/request-builder.ts +15 -4
  13. package/src/adapters/cursor/tool-definitions.ts +118 -2
  14. package/src/adapters/google.ts +15 -5
  15. package/src/adapters/openai-chat.ts +24 -2
  16. package/src/adapters/openai-responses.ts +2 -1
  17. package/src/bridge.ts +9 -5
  18. package/src/chat/outbound.ts +4 -3
  19. package/src/claude/desktop-3p.ts +222 -2
  20. package/src/claude/outbound.ts +15 -6
  21. package/src/cli/account-api.ts +4 -0
  22. package/src/cli/account-extended.ts +112 -0
  23. package/src/cli/account.ts +23 -6
  24. package/src/cli/claude-desktop.ts +26 -3
  25. package/src/cli/config-command.ts +9 -0
  26. package/src/cli/help.ts +18 -2
  27. package/src/cli/index.ts +277 -55
  28. package/src/cli/models.ts +5 -1
  29. package/src/cli/provider.ts +8 -2
  30. package/src/cli/ready.ts +301 -0
  31. package/src/cli/system-restart-client.ts +146 -0
  32. package/src/cli/tray-proxy.ts +153 -6
  33. package/src/clients/config-export.ts +12 -19
  34. package/src/codex/account-lifecycle.ts +3 -0
  35. package/src/codex/account-namespaces.ts +49 -3
  36. package/src/codex/account-priority.ts +83 -0
  37. package/src/codex/auth-api.ts +83 -0
  38. package/src/codex/auth-context.ts +5 -2
  39. package/src/codex/catalog/provider-fetch.ts +11 -0
  40. package/src/codex/catalog/sync.ts +23 -1
  41. package/src/codex/codex-write-lock.ts +16 -4
  42. package/src/codex/desired-state.ts +37 -4
  43. package/src/codex/history-job.ts +15 -5
  44. package/src/codex/history-provider.ts +31 -14
  45. package/src/codex/history-worker.ts +28 -4
  46. package/src/codex/inject-coordination.ts +13 -1
  47. package/src/codex/inject.ts +360 -66
  48. package/src/codex/internal/history-writer.ts +1 -1
  49. package/src/codex/native-main-lock-file.ts +5 -1
  50. package/src/codex/native-main-owner.ts +17 -3
  51. package/src/codex/native-profile-manager.ts +19 -0
  52. package/src/codex/native-profile-startup.ts +8 -0
  53. package/src/codex/native-residue.ts +140 -27
  54. package/src/codex/pool-rotation.ts +74 -4
  55. package/src/codex/refresh.ts +7 -0
  56. package/src/codex/routing.ts +177 -36
  57. package/src/codex/subagent-model-fallback.ts +34 -4
  58. package/src/codex/sync.ts +61 -0
  59. package/src/codex/upstream-host-health.ts +329 -31
  60. package/src/combos/request.ts +2 -0
  61. package/src/config.ts +221 -2
  62. package/src/images/loop.ts +1 -1
  63. package/src/integrations/native/ownership-preflight.ts +39 -2
  64. package/src/lib/bun-stream-caps.ts +3 -3
  65. package/src/lib/sse-decoder.ts +41 -0
  66. package/src/lib/system-restart-contract.ts +73 -0
  67. package/src/lib/windows-secret-acl.ts +141 -39
  68. package/src/lib/windows-user-principal.ts +283 -0
  69. package/src/lib/winsw.ts +18 -2
  70. package/src/oauth/key-providers.ts +12 -0
  71. package/src/providers/derive.ts +54 -2
  72. package/src/providers/free-directory.ts +6 -5
  73. package/src/providers/model-discovery.ts +9 -3
  74. package/src/providers/quota.ts +592 -0
  75. package/src/providers/registry.ts +316 -13
  76. package/src/responses/parser.ts +26 -10
  77. package/src/responses/reasoning-replay-cache.ts +1 -0
  78. package/src/routing/profile-namespace.ts +15 -0
  79. package/src/routing/profile.ts +2 -1
  80. package/src/server/auth-cors.ts +44 -13
  81. package/src/server/chat-completions.ts +0 -4
  82. package/src/server/claude-messages.ts +73 -15
  83. package/src/server/github-copilot-responses-repair.ts +338 -0
  84. package/src/server/index.ts +328 -111
  85. package/src/server/lifecycle.ts +36 -0
  86. package/src/server/management/agent-settings-routes.ts +147 -56
  87. package/src/server/management/config-routes.ts +7 -2
  88. package/src/server/management/context.ts +4 -0
  89. package/src/server/management/native-integration-routes.ts +199 -20
  90. package/src/server/management/provider-routes.ts +41 -0
  91. package/src/server/management/routing-profile-routes.ts +234 -5
  92. package/src/server/management/system-restart.ts +12 -10
  93. package/src/server/management/system-routes.ts +20 -0
  94. package/src/server/management-auth.ts +51 -3
  95. package/src/server/ports.ts +41 -1
  96. package/src/server/proxy-liveness.ts +129 -4
  97. package/src/server/readiness.ts +99 -0
  98. package/src/server/relay.ts +113 -97
  99. package/src/server/request-log.ts +10 -4
  100. package/src/server/responses/compact.ts +107 -12
  101. package/src/server/responses/core.ts +220 -39
  102. package/src/server/responses-item-id-repair.ts +22 -3
  103. package/src/server/responses-model-rewrite.ts +29 -0
  104. package/src/server/sse-frame-buffer.ts +292 -0
  105. package/src/server/sse-payload-rewrite.ts +25 -14
  106. package/src/server/ws-bridge.ts +27 -22
  107. package/src/service-manager-probe.ts +520 -10
  108. package/src/service.ts +134 -2
  109. package/src/storage/worker-lifecycle.ts +14 -14
  110. package/src/tray/windows-tray.ps1 +74 -9
  111. package/src/types.ts +68 -2
  112. package/src/update/index.ts +12 -0
  113. package/src/update/job.ts +392 -18
  114. package/src/update/npm-cache-preflight.d.mts +47 -0
  115. package/src/update/npm-cache-preflight.mjs +201 -0
  116. package/src/usage/log.ts +1 -1
  117. package/src/vision/index.ts +77 -2
  118. package/src/web-search/loop.ts +1 -1
  119. package/src/web-search/parse.ts +4 -1
  120. package/gui/dist/assets/index-BKVqyYqT.js +0 -70
  121. package/gui/dist/assets/index-Ca_3269W.css +0 -1
@@ -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 [r.provider, r.type, displayId(r.id), r.type === "api-key" ? keyLabel ?? "-" : r.label ?? "-", statusText(r)];
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("Applies to the next request after clearing existing pool affinity; in-flight requests keep their captured account.");
248
- console.error("Note: pool strategy, quota/cooldown/reauthentication state, and failure recovery may later select another eligible account.");
249
- console.error("Conversation context is replayed after account changes, but the provider-side prompt cache may be cold.");
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
- return { ok: true, path: applied.path ?? "" };
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
- "Codex pool selection applies to the next request after clearing existing affinity; in-flight requests keep their captured account.",
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