@bitkyc08/opencodex 2.75.0 → 2.76.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 +6 -1
- package/bin/ocx.mjs +12 -1
- package/gui/dist/assets/App-CddorHdA.js +51 -0
- package/gui/dist/assets/App-DMudtISW.css +1 -0
- package/gui/dist/assets/{Tray-CGAD2AGk.js → Tray-DjNwwrWS.js} +1 -1
- package/gui/dist/assets/index-Btze1I3T.css +1 -0
- package/gui/dist/assets/index-CvnY7E6i.js +86 -0
- package/gui/dist/assets/quota-summary-B_AWpHfP.js +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/opengateway.svg +12 -0
- package/package.json +1 -1
- package/src/adapters/anthropic/account-metadata.ts +17 -0
- package/src/adapters/anthropic/client-identity.ts +69 -0
- package/src/adapters/anthropic/passthrough.ts +23 -4
- package/src/adapters/anthropic.ts +18 -1
- package/src/adapters/command-code-project-context.ts +7 -11
- package/src/adapters/devin/cloud-direct/tool-schema.ts +12 -48
- package/src/adapters/kiro-retry.ts +23 -8
- package/src/adapters/openai-chat/messages.ts +1 -1
- package/src/adapters/registry.ts +8 -1
- package/src/adapters/zed.ts +377 -0
- package/src/bridge/sse.ts +4 -2
- package/src/chatgpt/app-server-shim/app-server-rewrite.ts +45 -0
- package/src/chatgpt/app-server-shim/bundle-trust.ts +77 -0
- package/src/chatgpt/app-server-shim/filter.ts +157 -0
- package/src/chatgpt/app-server-shim/gate-rewrite.ts +99 -0
- package/src/chatgpt/app-server-shim/launcher.ts +91 -0
- package/src/claude/desktop-3p-startup.ts +62 -0
- package/src/claude/desktop-3p.ts +5 -0
- package/src/claude/first-party-settings.ts +14 -0
- package/src/claude/intercept/cli-catalog.ts +150 -0
- package/src/claude/intercept/cli-picker.ts +82 -0
- package/src/claude/intercept/listener.ts +21 -6
- package/src/claude/intercept/picker-bootstrap.ts +23 -5
- package/src/claude/intercept/picker-models.ts +57 -13
- package/src/claude/intercept/runtime.ts +30 -2
- package/src/claude/intercept/settings.ts +3 -0
- package/src/cli/agent.ts +172 -0
- package/src/cli/alias.ts +2 -1
- package/src/cli/capabilities-command.ts +3 -2
- package/src/cli/capabilities.ts +87 -0
- package/src/cli/chatgpt-command.ts +140 -0
- package/src/cli/combo.ts +144 -19
- package/src/cli/config-command.ts +3 -1
- package/src/cli/dispatch.ts +10 -1
- package/src/cli/help.ts +1 -0
- package/src/cli/index.ts +5 -24
- package/src/cli/init.ts +16 -2
- package/src/cli/integrations.ts +13 -0
- package/src/cli/internal-command.ts +12 -5
- package/src/cli/observe.ts +6 -1
- package/src/cli/registry.ts +6 -0
- package/src/cli/route-policy.ts +3 -1
- package/src/cli/runtime-api.ts +5 -2
- package/src/cli/status-oauth.ts +5 -0
- package/src/cli/status.ts +8 -1
- package/src/cli/stop-approval.ts +55 -16
- package/src/cli/stop-report.ts +5 -0
- package/src/clients/lazycodex.ts +48 -0
- package/src/clients/omo-role-models.ts +111 -0
- package/src/codex/account-credit-use.ts +99 -0
- package/src/codex/account-lifecycle.ts +2 -0
- package/src/codex/agent-role-models.ts +171 -0
- package/src/codex/auth-api/account-list.ts +10 -3
- package/src/codex/auth-api/routes.ts +34 -0
- package/src/codex/auth-context.ts +52 -11
- package/src/codex/catalog/discovered-natives.ts +11 -1
- package/src/codex/catalog/gather-capture.ts +6 -1
- package/src/codex/catalog/model-hints.ts +9 -1
- package/src/codex/catalog/provider-models.ts +60 -0
- package/src/codex/catalog/routed-gather.ts +30 -5
- package/src/codex/convergence-types.ts +2 -1
- package/src/codex/main-account-external-usage.ts +62 -0
- package/src/codex/main-account-hard-lock.ts +24 -13
- package/src/codex/native-profile-processes.ts +53 -16
- package/src/codex/prompt-layers/toml-edit.ts +1 -1
- package/src/codex/quota-types.ts +3 -7
- package/src/codex/quota.ts +19 -4
- package/src/codex/role-auto-assign.ts +159 -0
- package/src/codex/role-sizing-limits.ts +2 -0
- package/src/codex/role-sizing.ts +216 -0
- package/src/codex/routing/selection.ts +36 -0
- package/src/codex/routing/thread-affinity.ts +2 -0
- package/src/codex/routing.ts +4 -4
- package/src/codex/subagent-model-fallback.ts +82 -26
- package/src/codex/subagent-selectable-models.ts +32 -0
- package/src/combos/index.ts +18 -0
- package/src/combos/jev-decision-contract.ts +20 -0
- package/src/combos/jev-dispatch.ts +18 -0
- package/src/combos/jev-model-backend.ts +127 -0
- package/src/combos/jev.ts +249 -33
- package/src/combos/types.ts +125 -2
- package/src/config/diagnostics.ts +18 -0
- package/src/config/macos-system-proxy.ts +72 -15
- package/src/config/owner-registry.ts +13 -4
- package/src/config/process-state.ts +17 -8
- package/src/config/proxy-env.ts +35 -5
- package/src/config/schema/config-schema.ts +30 -1
- package/src/config/schema/leaf-validators.ts +4 -0
- package/src/config/serving-runtimes.ts +95 -1
- package/src/generated/compatibility-version.json +336 -156
- package/src/lib/errors.ts +7 -7
- package/src/lib/jsonc.ts +13 -0
- package/src/lib/local-chat-completion.ts +144 -0
- package/src/lib/provider-outbound.ts +87 -1
- package/src/lib/provider-runtime-fetch.ts +23 -0
- package/src/lib/provider-tls-profile.ts +221 -0
- package/src/lib/request-execution-budget.ts +16 -6
- package/src/lib/spend-reservation-ledger.ts +54 -20
- package/src/lib/sse-decoder.ts +20 -17
- package/src/lib/synced-state-location.ts +96 -0
- package/src/lib/winsw.ts +14 -5
- package/src/oauth/anthropic-account-refusal.ts +53 -4
- package/src/oauth/anthropic-model-quota.ts +83 -0
- package/src/oauth/anthropic-rate-limit-policy.ts +37 -0
- package/src/oauth/anthropic-routing.ts +112 -58
- package/src/oauth/health.ts +39 -3
- package/src/oauth/index.ts +14 -0
- package/src/oauth/kiro-account-load.ts +10 -0
- package/src/oauth/zed.ts +134 -0
- package/src/providers/model-discovery.ts +19 -8
- package/src/providers/model-rename-fields.ts +1 -0
- package/src/providers/provider-id-rewrite.ts +9 -2
- package/src/providers/quota/account-cache.ts +29 -7
- package/src/providers/quota/anthropic-cooldown-recovery.ts +18 -0
- package/src/providers/quota/anthropic-family-headers.ts +25 -0
- package/src/providers/quota/vendor-probes-oauth.ts +5 -1
- package/src/providers/quota-types.ts +4 -0
- package/src/providers/registry/entries-core.ts +18 -0
- package/src/providers/registry/entries-extended.ts +55 -0
- package/src/providers/registry/types.ts +3 -0
- package/src/providers/xai-fast-model.ts +12 -4
- package/src/providers/zed.ts +598 -0
- package/src/responses/state/snapshot-select.ts +22 -9
- package/src/responses/state.ts +2 -3
- package/src/router.ts +9 -0
- package/src/server/anthropic-rate-limit-headers.ts +7 -0
- package/src/server/auth-cors.ts +7 -0
- package/src/server/chat-completions.ts +9 -4
- package/src/server/claude-messages.ts +59 -21
- package/src/server/index/claude-intercept-lifecycle.ts +71 -25
- package/src/server/index/optional-listeners.ts +4 -0
- package/src/server/index/spend-ledger-lifecycle.ts +4 -0
- package/src/server/management/agent-settings-routes.ts +74 -37
- package/src/server/management/claude-desktop-picker-routes.ts +7 -0
- package/src/server/management/claude-intercept-routes.ts +35 -0
- package/src/server/management/codex-agent-role-routes.ts +122 -0
- package/src/server/management/codex-role-auto-assign.ts +208 -0
- package/src/server/management/combo-routes.ts +62 -5
- package/src/server/management/config-routes.ts +35 -3
- package/src/server/management/context.ts +4 -1
- package/src/server/management/decision-discovery.ts +74 -0
- package/src/server/management/decision-model-validation.ts +80 -0
- package/src/server/management/decision-routes.ts +160 -0
- package/src/server/management/native-integration-routes.ts +16 -0
- package/src/server/management/provider-routes.ts +30 -34
- package/src/server/management/route-registry.ts +12 -0
- package/src/server/management/sibling-guard.ts +1 -0
- package/src/server/management/zed-provider-probe.ts +38 -0
- package/src/server/management-api.ts +12 -1
- package/src/server/messages-native-oauth.ts +7 -2
- package/src/server/messages-native.ts +7 -3
- package/src/server/relay.ts +3 -1
- package/src/server/request-log.ts +2 -0
- package/src/server/responses/adapter-continuation.ts +3 -3
- package/src/server/responses/adapter-dispatch.ts +18 -3
- package/src/server/responses/compact.ts +14 -1
- package/src/server/responses/core-auth.ts +2 -0
- package/src/server/responses/core-combo.ts +67 -11
- package/src/server/responses/core-normalize.ts +21 -1
- package/src/server/responses/core-options.ts +11 -1
- package/src/server/responses/core.ts +5 -3
- package/src/server/responses/fetch-helpers.ts +6 -3
- package/src/server/responses/jev-model-invoke.ts +190 -0
- package/src/server/responses/passthrough-delivery.ts +16 -0
- package/src/server/responses/policy-fallback.ts +55 -3
- package/src/server/responses/policy-request-scope.ts +80 -0
- package/src/server/responses/request-prepare.ts +70 -18
- package/src/server/responses/request-transport.ts +54 -23
- package/src/server/responses/run-turn-execution.ts +1 -0
- package/src/server/responses/sidecar-execution.ts +9 -3
- package/src/server/responses-hosted-image-display.ts +225 -0
- package/src/service/managing-cli.ts +34 -4
- package/src/service/systemd-env.ts +75 -0
- package/src/service/windows-guarded-manager.ts +60 -19
- package/src/service/windows-scheduler.ts +22 -3
- package/src/service/windows-taskxml.ts +32 -10
- package/src/service-manager-probe.ts +72 -19
- package/src/types/config.ts +31 -1
- package/src/types/provider.ts +2 -0
- package/src/types/request.ts +2 -0
- package/src/update/index.ts +39 -6
- package/src/update/job.ts +63 -63
- package/src/update/npm-cache-preflight.d.mts +33 -1
- package/src/update/npm-cache-preflight.mjs +131 -18
- package/src/update/ownership-transaction.ts +19 -1
- package/src/update/restart-ownership.ts +80 -6
- package/src/update/transactional-install.d.mts +2 -0
- package/src/update/transactional-install.mjs +6 -1
- package/src/usage/jev-stats.ts +28 -1
- package/src/usage/log.ts +1 -1
- package/src/vision/anthropic-describe.ts +5 -3
- package/src/vision/index.ts +1 -0
- package/src/vision/plan.ts +2 -1
- package/src/vision/routed-describe.ts +29 -121
- package/src/web-search/alpha-search.ts +3 -3
- package/src/web-search/anthropic-executor.ts +6 -4
- package/src/web-search/index.ts +1 -1
- package/src/web-search/loop.ts +3 -3
- package/src/web-search/passthrough-bridge.ts +2 -1
- package/src/web-search/run-turn-loop.ts +1 -1
- package/src/web-search/sidecar-providers.ts +2 -1
- package/gui/dist/assets/App-ia4U0ufY.css +0 -1
- package/gui/dist/assets/App-xm0zGNOe.js +0 -51
- package/gui/dist/assets/index-DBT3Q31Y.css +0 -1
- package/gui/dist/assets/index-D_RGAT6Q.js +0 -86
- package/gui/dist/assets/quota-summary-CUFq48t-.js +0 -1
|
@@ -7,8 +7,8 @@ import { dirname } from "node:path";
|
|
|
7
7
|
import { nativeOpenAiContextWindow, type NativeContextLimitsInput } from "../../codex/catalog";
|
|
8
8
|
import type { OcxClaudeDesktopProfile } from "../../types";
|
|
9
9
|
import { aliasForRoute, claudeCodeNativeAlias } from "../alias";
|
|
10
|
-
import { AUTO_CONTEXT_OFF, withOneMillionMarker } from "../context-windows";
|
|
11
|
-
import { displayModelId, type Desktop3pRoutedModel } from "../desktop-3p";
|
|
10
|
+
import { AUTO_CONTEXT_OFF, stripOneMillionMarker, withOneMillionMarker } from "../context-windows";
|
|
11
|
+
import { activeDesktop3pAlias, displayModelId, resolveDesktop3pAlias, type Desktop3pRoutedModel } from "../desktop-3p";
|
|
12
12
|
import { reconcileDesktopProfile, renderDesktopProfile, type DesktopProfileModel } from "../desktop-profile";
|
|
13
13
|
import type { PickerModelEntry } from "./picker-bootstrap";
|
|
14
14
|
|
|
@@ -19,7 +19,8 @@ export interface PickerRouteInput {
|
|
|
19
19
|
nativeContextCap?: NativeContextLimitsInput;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
/** Candidate routes in the gateway profile's order and with its labels. */
|
|
23
|
+
function renderPickerCandidates(input: PickerRouteInput): DesktopProfileModel[] {
|
|
23
24
|
const candidates: DesktopProfileModel[] = [
|
|
24
25
|
...input.nativeSlugs.map(id => {
|
|
25
26
|
const contextWindow = nativeOpenAiContextWindow(id, input.nativeContextCap);
|
|
@@ -35,27 +36,64 @@ export function buildPickerModels(input: PickerRouteInput): PickerModelEntry[] {
|
|
|
35
36
|
...(contextWindow === undefined ? {} : { contextWindow }),
|
|
36
37
|
})),
|
|
37
38
|
];
|
|
38
|
-
|
|
39
|
+
return input.profile
|
|
39
40
|
? renderDesktopProfile(reconcileDesktopProfile(input.profile, candidates), candidates)
|
|
40
41
|
: candidates;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function splitRoute(route: string): { provider: string; id: string } {
|
|
45
|
+
const slash = route.indexOf("/");
|
|
46
|
+
return { provider: route.slice(0, slash), id: route.slice(slash + 1) };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Desktop runners do not inherit the proxy's compaction env: mark only real >=1M windows. */
|
|
50
|
+
function pickerSelector(alias: string, contextWindow: number | undefined): string {
|
|
51
|
+
return withOneMillionMarker(alias, contextWindow === undefined ? {} : { [alias]: contextWindow }, AUTO_CONTEXT_OFF)!;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function buildPickerModels(input: PickerRouteInput): PickerModelEntry[] {
|
|
55
|
+
const rendered = renderPickerCandidates(input);
|
|
41
56
|
const out: PickerModelEntry[] = [];
|
|
42
57
|
const seen = new Set<string>();
|
|
43
58
|
for (const model of rendered) {
|
|
44
|
-
const
|
|
45
|
-
const provider = model.route.slice(0, slash);
|
|
46
|
-
const id = model.route.slice(slash + 1);
|
|
59
|
+
const { provider, id } = splitRoute(model.route);
|
|
47
60
|
if (provider === "anthropic" && id.startsWith("claude-")) continue;
|
|
48
61
|
const alias = provider === "native" ? claudeCodeNativeAlias(id) : aliasForRoute(provider, id);
|
|
49
62
|
if (!alias || seen.has(alias)) continue;
|
|
50
63
|
seen.add(alias);
|
|
51
|
-
|
|
52
|
-
const selector = withOneMillionMarker(alias, model.contextWindow === undefined ? {} : { [alias]: model.contextWindow }, AUTO_CONTEXT_OFF)!;
|
|
53
|
-
out.push({ id: selector, name: model.label,
|
|
64
|
+
out.push({ id: pickerSelector(alias, model.contextWindow), name: model.label,
|
|
54
65
|
...(model.contextWindow === undefined ? {} : { contextWindow: model.contextWindow }) });
|
|
55
66
|
}
|
|
56
67
|
return out;
|
|
57
68
|
}
|
|
58
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Rows for the Claude Code CLI's `cc` catalog. The CLI only offers Claude-shaped ids, so each route
|
|
72
|
+
* uses its Desktop 3P registry alias, and only one the registry decodes back to the same route: an
|
|
73
|
+
* id the router cannot resolve is never advertised. Real Anthropic rows are already in the catalog.
|
|
74
|
+
*/
|
|
75
|
+
export function buildCliPickerModels(input: PickerRouteInput): PickerModelEntry[] {
|
|
76
|
+
const out: PickerModelEntry[] = [];
|
|
77
|
+
const seen = new Set<string>();
|
|
78
|
+
for (const model of renderPickerCandidates(input)) {
|
|
79
|
+
const { provider, id } = splitRoute(model.route);
|
|
80
|
+
if (provider === "anthropic" && id.startsWith("claude-")) continue;
|
|
81
|
+
const alias = activeDesktop3pAlias(provider, id);
|
|
82
|
+
if (seen.has(alias) || resolveDesktop3pAlias(alias) !== model.route) continue;
|
|
83
|
+
seen.add(alias);
|
|
84
|
+
out.push({ id: pickerSelector(alias, model.contextWindow), name: model.label,
|
|
85
|
+
description: `opencodex · ${model.route}`, route: model.route,
|
|
86
|
+
...(model.contextWindow === undefined ? {} : { contextWindow: model.contextWindow }) });
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Rows whose alias still decodes to the route it was minted for: the registry can be rebuilt after a snapshot was taken. */
|
|
92
|
+
export function routableCliPickerModels(models: readonly PickerModelEntry[]): PickerModelEntry[] {
|
|
93
|
+
return models.filter(model => model.route !== undefined
|
|
94
|
+
&& resolveDesktop3pAlias(stripOneMillionMarker(model.id)) === model.route);
|
|
95
|
+
}
|
|
96
|
+
|
|
59
97
|
export interface PickerModelSnapshot {
|
|
60
98
|
current(): { models: PickerModelEntry[]; builtAt: number } | null;
|
|
61
99
|
refresh(): Promise<void>;
|
|
@@ -68,11 +106,17 @@ function parseSnapshot(value: unknown): { models: PickerModelEntry[]; builtAt: n
|
|
|
68
106
|
if (typeof candidate.builtAt !== "number" || !Number.isFinite(candidate.builtAt) || !Array.isArray(candidate.models)) return null;
|
|
69
107
|
if (!candidate.models.every(model => model && typeof model === "object"
|
|
70
108
|
&& typeof model.id === "string" && typeof model.name === "string"
|
|
71
|
-
&& (model.contextWindow === undefined || typeof model.contextWindow === "number")
|
|
109
|
+
&& (model.contextWindow === undefined || typeof model.contextWindow === "number")
|
|
110
|
+
&& (model.description === undefined || typeof model.description === "string")
|
|
111
|
+
&& (model.route === undefined || typeof model.route === "string"))) return null;
|
|
72
112
|
return { models: candidate.models as PickerModelEntry[], builtAt: candidate.builtAt };
|
|
73
113
|
}
|
|
74
114
|
|
|
75
|
-
export function createPickerModelSnapshot(
|
|
115
|
+
export function createPickerModelSnapshot(
|
|
116
|
+
load: () => Promise<PickerRouteInput>,
|
|
117
|
+
persistPath?: string,
|
|
118
|
+
build: (input: PickerRouteInput) => PickerModelEntry[] = buildPickerModels,
|
|
119
|
+
): PickerModelSnapshot {
|
|
76
120
|
let snapshot: { models: PickerModelEntry[]; builtAt: number } | null = null;
|
|
77
121
|
if (persistPath) {
|
|
78
122
|
try { snapshot = parseSnapshot(JSON.parse(readFileSync(persistPath, "utf8")) as unknown); } catch { /* No usable prior snapshot. */ }
|
|
@@ -82,7 +126,7 @@ export function createPickerModelSnapshot(load: () => Promise<PickerRouteInput>,
|
|
|
82
126
|
if (pending) return pending;
|
|
83
127
|
pending = (async () => {
|
|
84
128
|
try {
|
|
85
|
-
const models =
|
|
129
|
+
const models = build(await load());
|
|
86
130
|
const next = { models, builtAt: Date.now() };
|
|
87
131
|
if (persistPath) {
|
|
88
132
|
mkdirSync(dirname(persistPath), { recursive: true, mode: 0o700 });
|
|
@@ -4,6 +4,7 @@ import { getConfigDir } from "../../config/paths";
|
|
|
4
4
|
import type { DesktopPickerController } from "../desktop-picker";
|
|
5
5
|
import type { ClaudeFirstPartyDesired } from "../first-party-settings";
|
|
6
6
|
import { classifyInterceptClient, interceptRouteFor } from "./client-class";
|
|
7
|
+
import { createCliCatalogProvider } from "./cli-picker";
|
|
7
8
|
import { CLAUDE_INTERCEPT_HOSTS, isBrowserConnect, startConnectProxy, type ConnectProxyHandle } from "./connect-proxy";
|
|
8
9
|
import { startClaudeInterceptListener } from "./listener";
|
|
9
10
|
import { claudeInterceptCaCertPath, ensureLocalInterceptCaForStartup, issueLocalInterceptLeaf } from "./local-ca";
|
|
@@ -50,7 +51,11 @@ export function claudePickerProxyPort(config: Pick<OcxConfig, "claudeCode">, pub
|
|
|
50
51
|
return interceptPort < 65535 ? interceptPort + 1 : interceptPort - 1;
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
export type ClaudeInterceptOutcome = { ok: true; state: ClaudeInterceptState } | { ok: false; reason: "disabled" | "client_role" | "ephemeral_port" | "port_in_use" | "stopped" | "failed"; port?: number; message?: string };
|
|
55
|
+
|
|
53
56
|
export interface ClaudeInterceptState {
|
|
57
|
+
pickerReason?: "port_in_use" | "failed" | null;
|
|
58
|
+
pickerFailurePort?: number;
|
|
54
59
|
proxyPort: number;
|
|
55
60
|
caCertPath: string;
|
|
56
61
|
/** Desktop egress proxy for picker mode; null when the picker is not wired or could not bind. */
|
|
@@ -101,6 +106,14 @@ export async function createPickerPreferenceWriter(live: OcxConfig): Promise<(va
|
|
|
101
106
|
};
|
|
102
107
|
}
|
|
103
108
|
|
|
109
|
+
export class ClaudeInterceptProxyBindError extends Error {
|
|
110
|
+
readonly code: unknown;
|
|
111
|
+
constructor(error: unknown, readonly port: number) {
|
|
112
|
+
super(error instanceof Error ? error.message : "CONNECT proxy bind failed", { cause: error });
|
|
113
|
+
this.code = error && typeof error === "object" && "code" in error ? error.code : undefined;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
104
117
|
export interface StartClaudeInterceptOptions<T> {
|
|
105
118
|
config: OcxConfig;
|
|
106
119
|
/** Bound public port; the derived proxy port is offset from it. */
|
|
@@ -160,6 +173,10 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
|
|
|
160
173
|
dispatch: options.dispatch,
|
|
161
174
|
...(options.desiredClients ? { route: (req: Request) =>
|
|
162
175
|
interceptRouteFor(classifyInterceptClient(req.headers.get("user-agent")), options.desiredClients!()) } : {}),
|
|
176
|
+
// The CLI's /model catalog (cli-picker.ts): wired from the routes alone, never from Desktop picker state.
|
|
177
|
+
...(options.loadPickerRoutes && options.desiredClients ? { cliCatalog: createCliCatalogProvider({
|
|
178
|
+
configDir, loadRoutes: options.loadPickerRoutes, desiredClients: options.desiredClients,
|
|
179
|
+
}) } : {}),
|
|
163
180
|
upstreamBase: options.config.claudeCode?.anthropicBaseUrl,
|
|
164
181
|
...(options.maxRequestBodySize !== undefined ? { maxRequestBodySize: options.maxRequestBodySize } : {}),
|
|
165
182
|
});
|
|
@@ -173,13 +190,15 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
|
|
|
173
190
|
});
|
|
174
191
|
} catch (error) {
|
|
175
192
|
await listener.stop(true);
|
|
176
|
-
throw error;
|
|
193
|
+
throw new ClaudeInterceptProxyBindError(error, claudeInterceptProxyPort(options.config, options.publicPort));
|
|
177
194
|
}
|
|
178
195
|
// Widened on purpose: assignments happen in nested awaits the catch below must still see.
|
|
179
196
|
let picker = null as PickerRuntime | null;
|
|
180
197
|
let pickerProxy = null as ConnectProxyHandle | null;
|
|
181
198
|
let controller = null as DesktopPickerController | null;
|
|
182
199
|
let pickerProxyLive = false;
|
|
200
|
+
let pickerReason: ClaudeInterceptState["pickerReason"] = null;
|
|
201
|
+
let pickerFailurePort: number | undefined;
|
|
183
202
|
try {
|
|
184
203
|
if (options.loadPickerRoutes) {
|
|
185
204
|
// A picker authority is process-scoped, and older releases persisted an exportable ca.key
|
|
@@ -208,9 +227,11 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
|
|
|
208
227
|
}
|
|
209
228
|
} catch (error) {
|
|
210
229
|
pickerBlocked = true;
|
|
230
|
+
pickerReason = "failed";
|
|
211
231
|
console.warn(`⚠ Claude Desktop picker CA cleanup deferred: ${error instanceof Error ? error.message : String(error)}`);
|
|
212
232
|
}
|
|
213
233
|
if (pickerBlocked) {
|
|
234
|
+
pickerReason = "failed";
|
|
214
235
|
console.warn("⚠ Claude Desktop picker disabled: the previous certificate could not be untrusted");
|
|
215
236
|
if (pickerProfile.kind === "applied") {
|
|
216
237
|
// Keep Desktop's actual pinned egress alive without ever constructing a TLS terminator.
|
|
@@ -223,6 +244,8 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
|
|
|
223
244
|
});
|
|
224
245
|
pickerProxyLive = true;
|
|
225
246
|
} catch (error) {
|
|
247
|
+
pickerReason = error && typeof error === "object" && "code" in error && error.code === "EADDRINUSE" ? "port_in_use" : "failed";
|
|
248
|
+
if (pickerReason === "port_in_use") pickerFailurePort = port;
|
|
226
249
|
console.warn(`⚠ Claude Desktop blind egress relay could not start: ${error instanceof Error ? error.message : String(error)}`);
|
|
227
250
|
}
|
|
228
251
|
}
|
|
@@ -241,8 +264,9 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
|
|
|
241
264
|
if (picker) {
|
|
242
265
|
const runtime = picker;
|
|
243
266
|
const interceptPort = listener.port!;
|
|
267
|
+
const pickerPort = claudePickerProxyPort(options.config, options.publicPort);
|
|
244
268
|
try {
|
|
245
|
-
pickerProxy = await startProxy(
|
|
269
|
+
pickerProxy = await startProxy(pickerPort, {
|
|
246
270
|
interceptPort,
|
|
247
271
|
// No authToken: Desktop's egressProxyUrl cannot present proxy credentials, so this
|
|
248
272
|
// listener stays an unauthenticated loopback relay until the profile format can carry
|
|
@@ -267,6 +291,8 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
|
|
|
267
291
|
// Picker mode is optional: a busy port leaves the intercept pair running without it.
|
|
268
292
|
// The selected profile row survives, so the next startup retries the restore once the
|
|
269
293
|
// port is free again.
|
|
294
|
+
pickerReason = error && typeof error === "object" && "code" in error && error.code === "EADDRINUSE" ? "port_in_use" : "failed";
|
|
295
|
+
if (pickerReason === "port_in_use") pickerFailurePort = pickerPort;
|
|
270
296
|
console.warn(`⚠ Claude Desktop picker proxy could not start: ${error instanceof Error ? error.message : String(error)}`);
|
|
271
297
|
await runtime.stop();
|
|
272
298
|
picker = null;
|
|
@@ -313,6 +339,8 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
|
|
|
313
339
|
throw error;
|
|
314
340
|
}
|
|
315
341
|
const state: ClaudeInterceptState = {
|
|
342
|
+
pickerReason,
|
|
343
|
+
pickerFailurePort,
|
|
316
344
|
proxyPort: proxy.port,
|
|
317
345
|
caCertPath: claudeInterceptCaCertPath(configDir),
|
|
318
346
|
pickerProxyPort: pickerProxy?.port ?? null,
|
|
@@ -2,6 +2,7 @@ import { mkdirSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { atomicWriteFile } from "../../config/atomic-write";
|
|
4
4
|
import { claudeConfigDir } from "../auth-detect";
|
|
5
|
+
import { invalidateClaudeCodeServedCatalog } from "./cli-catalog";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Claude Code `settings.json` env block for intercept mode.
|
|
@@ -115,6 +116,8 @@ function writeSettings(path: string, doc: SettingsDoc): void {
|
|
|
115
116
|
// The managed env embeds the proxy token, so the file must stay owner-only:
|
|
116
117
|
// atomicWriteFile applies the real NTFS ACL on Windows where chmod is a no-op.
|
|
117
118
|
atomicWriteFile(path, `${JSON.stringify(doc, null, 2)}\n`);
|
|
119
|
+
// Whether Claude Code reaches the proxy just changed, so its cached /model list is stale.
|
|
120
|
+
invalidateClaudeCodeServedCatalog(dirname(path));
|
|
118
121
|
}
|
|
119
122
|
|
|
120
123
|
export type ClaudeInterceptSettingsWrite =
|
package/src/cli/agent.ts
CHANGED
|
@@ -25,9 +25,11 @@ const USAGE = `Usage:
|
|
|
25
25
|
ocx agent [status] [--json]
|
|
26
26
|
ocx agent injection <status|set> [--model <id|->] [--effort <level|->]
|
|
27
27
|
[--prompt <text|->] [--guidance <on|off>] [--json]
|
|
28
|
+
ocx agent injection suggest <work description> [--model <id>] [--apply] [--json]
|
|
28
29
|
ocx agent effort <status|set> [--main <level|->] [--subagent <level|->] [--json]
|
|
29
30
|
ocx agent subagents <status|set|clear> [model,model...] [--json]
|
|
30
31
|
ocx agent fallback <status|set|clear> [model,model...] [--poll-ms <5000-600000>] [--json]
|
|
32
|
+
ocx agent roles [status|set <role> <model>|suggest [--model <id>] [--apply]] [--json]
|
|
31
33
|
ocx agent sidecar <status|web|vision> [--list] [--model <id|->]
|
|
32
34
|
[--backend web:<openai|anthropic|xai|gemini|exa|-> vision:<openai|anthropic|routed|->]
|
|
33
35
|
[--reasoning <level>] [--max-descriptions <n>] [--enabled <on|off>] [--json]
|
|
@@ -53,6 +55,58 @@ async function status(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
53
55
|
printData(result, wantsJson, summaryLines(result));
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
interface DelegationProposal {
|
|
59
|
+
model: string | null;
|
|
60
|
+
effort: string | null;
|
|
61
|
+
status: "proposed" | "unassigned" | "unsized";
|
|
62
|
+
tier?: string;
|
|
63
|
+
effortIntent?: string;
|
|
64
|
+
rationale?: string;
|
|
65
|
+
moveUpIf?: string;
|
|
66
|
+
moveDownIf?: string;
|
|
67
|
+
proposedModel?: string | null;
|
|
68
|
+
proposedEffort?: string | null;
|
|
69
|
+
reason?: string | null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function suggestInjection(args: string[], wantsJson: boolean, deps: RuntimeApiDeps): Promise<void> {
|
|
73
|
+
const model = takeOption(args, "--model");
|
|
74
|
+
const apply = takeFlag(args, "--apply");
|
|
75
|
+
if (args.some(arg => arg.startsWith("--"))) rejectArgs(args, USAGE);
|
|
76
|
+
const work = args.join(" ").trim();
|
|
77
|
+
if (!work) throw new CliUsageError("describe the delegated work to size", USAGE);
|
|
78
|
+
const result = await runtimeRequest<{ sizingModel?: string; proposal?: DelegationProposal }>(
|
|
79
|
+
"/api/injection-model/suggest",
|
|
80
|
+
{ method: "POST", body: JSON.stringify({ work, ...(model ? { model } : {}) }) },
|
|
81
|
+
deps,
|
|
82
|
+
);
|
|
83
|
+
const p = result.proposal;
|
|
84
|
+
const alreadySet = p?.status === "proposed" && !!p.proposedModel
|
|
85
|
+
&& p.proposedModel === p.model && (p.proposedEffort ?? null) === (p.effort ?? null);
|
|
86
|
+
let applied: { model: string; effort: string | null } | null = null;
|
|
87
|
+
if (apply && !alreadySet && p?.status === "proposed" && p.proposedModel) {
|
|
88
|
+
applied = { model: p.proposedModel, effort: p.proposedEffort ?? null };
|
|
89
|
+
await runtimeRequest("/api/injection-model", { method: "PUT", body: JSON.stringify(applied) }, deps);
|
|
90
|
+
}
|
|
91
|
+
printData(apply ? { ...result, applied, alreadySet } : result, wantsJson, [
|
|
92
|
+
`Sized with ${result.sizingModel ?? "unknown"}.`,
|
|
93
|
+
!p || p.status === "unsized"
|
|
94
|
+
? `Not sized (${p?.reason ?? "unknown"}).`
|
|
95
|
+
: p.status === "unassigned"
|
|
96
|
+
? `${p.tier}/${p.effortIntent}, no model (${p.reason ?? "unknown"})`
|
|
97
|
+
: `${p.model ?? "(none)"}${p.effort ? ` (${p.effort})` : ""} -> ${p.proposedModel}${p.proposedEffort ? ` (${p.proposedEffort})` : ""} [${p.tier}/${p.effortIntent}] ${p.rationale ?? ""}`,
|
|
98
|
+
...(p?.moveUpIf ? [`Move up if: ${p.moveUpIf}`] : []),
|
|
99
|
+
...(p?.moveDownIf ? [`Move down if: ${p.moveDownIf}`] : []),
|
|
100
|
+
apply
|
|
101
|
+
? (applied
|
|
102
|
+
? "Applied to the delegation model."
|
|
103
|
+
: alreadySet
|
|
104
|
+
? `Already set to ${p!.proposedModel}${p!.proposedEffort ? ` (${p!.proposedEffort})` : ""}; nothing applied.`
|
|
105
|
+
: "Nothing to apply.")
|
|
106
|
+
: "Nothing was written; rerun with --apply to set the delegation model.",
|
|
107
|
+
]);
|
|
108
|
+
}
|
|
109
|
+
|
|
56
110
|
async function injection(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
57
111
|
const args = [...argv];
|
|
58
112
|
const action = (args.shift() ?? "status").toLowerCase();
|
|
@@ -63,6 +117,10 @@ async function injection(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
63
117
|
printData(result, wantsJson, summaryLines(result));
|
|
64
118
|
return;
|
|
65
119
|
}
|
|
120
|
+
if (action === "suggest") {
|
|
121
|
+
await suggestInjection(args, wantsJson, deps);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
66
124
|
if (action !== "set") throw new CliUsageError(`unknown injection action ${action}`, USAGE);
|
|
67
125
|
const model = clearable(takeOption(args, "--model"));
|
|
68
126
|
const effort = clearable(takeOption(args, "--effort"));
|
|
@@ -236,6 +294,119 @@ async function sidecar(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
236
294
|
printData(result, wantsJson, lines);
|
|
237
295
|
}
|
|
238
296
|
|
|
297
|
+
interface CodexAgentRolesStatus {
|
|
298
|
+
lazycodex?: { detected?: boolean };
|
|
299
|
+
omoJsonc?: { state?: string } | null;
|
|
300
|
+
roles?: Array<{ role: string; model: string | null; omoJsoncModel: string | null }>;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
interface CodexRoleProposal {
|
|
304
|
+
role: string;
|
|
305
|
+
model: string | null;
|
|
306
|
+
effort?: string | null;
|
|
307
|
+
status: "proposed" | "unassigned" | "unsized";
|
|
308
|
+
tier?: string;
|
|
309
|
+
effortIntent?: string;
|
|
310
|
+
rationale?: string;
|
|
311
|
+
proposedModel?: string | null;
|
|
312
|
+
proposedEffort?: string | null;
|
|
313
|
+
reason?: string | null;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function proposalAlreadySet(proposal: CodexRoleProposal): boolean {
|
|
317
|
+
return proposal.proposedModel === proposal.model
|
|
318
|
+
&& (proposal.proposedEffort == null || proposal.proposedEffort === proposal.effort);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const OMO_JSONC_NOT_WRITTEN = new Set(["skipped_comments", "invalid", "write_failed"]);
|
|
322
|
+
|
|
323
|
+
async function suggestRoles(args: string[], wantsJson: boolean, deps: RuntimeApiDeps): Promise<void> {
|
|
324
|
+
const model = takeOption(args, "--model");
|
|
325
|
+
const apply = takeFlag(args, "--apply");
|
|
326
|
+
rejectArgs(args, USAGE);
|
|
327
|
+
const result = await runtimeRequest<{ sizingModel?: string; proposals?: CodexRoleProposal[] }>(
|
|
328
|
+
"/api/codex-agent-roles/auto-assign",
|
|
329
|
+
{ method: "POST", body: JSON.stringify(model ? { model } : {}) },
|
|
330
|
+
deps,
|
|
331
|
+
);
|
|
332
|
+
const proposals = result.proposals ?? [];
|
|
333
|
+
const applied: Array<{ role: string; model: string; effort?: string }> = [];
|
|
334
|
+
const skipped: string[] = [];
|
|
335
|
+
const omoJsoncNotWritten: Array<{ role: string; status: string }> = [];
|
|
336
|
+
if (apply) {
|
|
337
|
+
for (const proposal of proposals) {
|
|
338
|
+
if (proposal.status !== "proposed" || !proposal.proposedModel) continue;
|
|
339
|
+
if (proposalAlreadySet(proposal)) {
|
|
340
|
+
skipped.push(proposal.role);
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
const body = { model: proposal.proposedModel, ...(proposal.proposedEffort ? { effort: proposal.proposedEffort } : {}) };
|
|
344
|
+
const written = await runtimeRequest<{ omoJsonc?: { status?: string } } | null>(
|
|
345
|
+
`/api/codex-agent-roles/${encodeURIComponent(proposal.role)}`,
|
|
346
|
+
{ method: "PUT", body: JSON.stringify(body) },
|
|
347
|
+
deps,
|
|
348
|
+
);
|
|
349
|
+
applied.push({ role: proposal.role, ...body });
|
|
350
|
+
const omoStatus = written?.omoJsonc?.status;
|
|
351
|
+
if (omoStatus && OMO_JSONC_NOT_WRITTEN.has(omoStatus)) omoJsoncNotWritten.push({ role: proposal.role, status: omoStatus });
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
printData(apply ? { ...result, applied, skipped, omoJsoncNotWritten } : result, wantsJson, [
|
|
355
|
+
`Sized with ${result.sizingModel ?? "unknown"}.`,
|
|
356
|
+
...proposals.map(p => p.status === "unsized"
|
|
357
|
+
? `${p.role}: not sized (${p.reason ?? "unknown"})`
|
|
358
|
+
: p.status === "unassigned"
|
|
359
|
+
? `${p.role}: ${p.tier}/${p.effortIntent}, no model (${p.reason ?? "unknown"})`
|
|
360
|
+
: `${p.role}: ${p.model ?? "(no pin)"} -> ${p.proposedModel}${p.proposedEffort ? ` (${p.proposedEffort})` : ""} [${p.tier}/${p.effortIntent}] ${p.rationale ?? ""}`),
|
|
361
|
+
apply
|
|
362
|
+
? `Applied ${applied.length} of ${proposals.length} roles.${skipped.length > 0 ? ` Skipped ${skipped.length} already set: ${skipped.join(", ")}.` : ""}`
|
|
363
|
+
: "Nothing was written; rerun with --apply to write every proposal that differs from the role's current pin.",
|
|
364
|
+
...omoJsoncNotWritten.map(entry => `${entry.role}: omo.jsonc not written (${entry.status}); the role TOML was updated.`),
|
|
365
|
+
]);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async function roles(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
369
|
+
const args = [...argv];
|
|
370
|
+
const wantsJson = takeFlag(args, "--json");
|
|
371
|
+
const action = (args.shift() ?? "status").toLowerCase();
|
|
372
|
+
if (action === "status") {
|
|
373
|
+
rejectArgs(args, USAGE);
|
|
374
|
+
const result = await runtimeRequest<CodexAgentRolesStatus>("/api/codex-agent-roles", {}, deps);
|
|
375
|
+
if (result.lazycodex?.detected !== true) {
|
|
376
|
+
printData(result, wantsJson, ["omo (Codex / LazyCodex) is not installed in this CODEX_HOME; role models are managed only with it."]);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const rows = result.roles ?? [];
|
|
380
|
+
printData(result, wantsJson, [
|
|
381
|
+
"omo (Codex / LazyCodex): detected",
|
|
382
|
+
...(rows.length === 0 ? ["No Codex agent roles found."] : rows.map(row => `${row.role}: ${row.model ?? "(no model pin)"}`)),
|
|
383
|
+
`omo.jsonc: ${result.omoJsonc?.state ?? "unknown"}`,
|
|
384
|
+
]);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (action === "suggest") {
|
|
388
|
+
await suggestRoles(args, wantsJson, deps);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (action !== "set") throw new CliUsageError(`unknown roles action ${action}`, USAGE);
|
|
392
|
+
const role = args.shift();
|
|
393
|
+
const model = args.shift();
|
|
394
|
+
if (!role || !model) throw new CliUsageError("a role and a model are required", USAGE);
|
|
395
|
+
rejectArgs(args, USAGE);
|
|
396
|
+
const result = await runtimeRequest<{ toml?: { status?: string }; omoJsonc?: { status?: string } }>(
|
|
397
|
+
`/api/codex-agent-roles/${encodeURIComponent(role)}`,
|
|
398
|
+
{ method: "PUT", body: JSON.stringify({ model }) },
|
|
399
|
+
deps,
|
|
400
|
+
);
|
|
401
|
+
const omo = result.omoJsonc?.status;
|
|
402
|
+
printData(result, wantsJson, [
|
|
403
|
+
`${role}: ${model} (role TOML ${result.toml?.status ?? "unknown"})`,
|
|
404
|
+
omo === "skipped_comments"
|
|
405
|
+
? "omo.jsonc: not written, because it contains comments that a rewrite would lose."
|
|
406
|
+
: `omo.jsonc: ${omo ?? "unknown"}`,
|
|
407
|
+
]);
|
|
408
|
+
}
|
|
409
|
+
|
|
239
410
|
export async function handleAgentCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
|
|
240
411
|
return runCliAction(async () => {
|
|
241
412
|
const [sub = "status", ...rest] = argv;
|
|
@@ -244,6 +415,7 @@ export async function handleAgentCommand(argv: string[], deps: RuntimeApiDeps =
|
|
|
244
415
|
else if (sub === "effort") await effort(rest, deps);
|
|
245
416
|
else if (sub === "subagents" || sub === "roster") await subagents(rest, deps);
|
|
246
417
|
else if (sub === "fallback") await fallback(rest, deps);
|
|
418
|
+
else if (sub === "roles") await roles(rest, deps);
|
|
247
419
|
else if (sub === "sidecar") await sidecar(rest, deps);
|
|
248
420
|
// Lives here rather than as a top-level verb because it is an agent-behavior feature flag:
|
|
249
421
|
// it controls whether default mode may ask the operator a question mid-task.
|
package/src/cli/alias.ts
CHANGED
|
@@ -12,10 +12,11 @@ function selector(value: string): { provider: string; model?: string } {
|
|
|
12
12
|
return slash < 0 ? { provider: value } : { provider: value.slice(0, slash), model: value.slice(slash + 1) };
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/** Manage model aliases, consuming JSON output selection before the default list action. */
|
|
15
16
|
export async function handleAliasCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
|
|
16
17
|
const args = [...argv];
|
|
17
|
-
const action = (args.shift() ?? "list").toLowerCase();
|
|
18
18
|
const wantsJson = takeFlag(args, "--json");
|
|
19
|
+
const action = (args.shift() ?? "list").toLowerCase();
|
|
19
20
|
if (action === "list") {
|
|
20
21
|
rejectArgs(args, USAGE);
|
|
21
22
|
const result = await runtimeRequest<Record<string, unknown>>("/api/aliases", {}, deps);
|
|
@@ -45,14 +45,15 @@ function renderHuman(caps: readonly Capability[], includeHead: boolean): void {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/** Render declared capabilities; reject invalid arguments with 64 and report unmatched routes with 4. */
|
|
48
49
|
export async function runCapabilities(argv: string[]): Promise<number> {
|
|
49
50
|
const args = [...argv];
|
|
50
51
|
const json = takeFlag(args, "--json");
|
|
51
52
|
const mutatingOnly = takeFlag(args, "--mutating-only");
|
|
52
53
|
const route = takeValueFlag(args, "--route");
|
|
53
54
|
|
|
54
|
-
if (route !== undefined && route.length === 0) {
|
|
55
|
-
console.error("Usage: ocx capabilities --route <path>");
|
|
55
|
+
if (args.length > 0 || (route !== undefined && route.trim().length === 0)) {
|
|
56
|
+
console.error("Usage: ocx capabilities [--json] [--mutating-only] [--route <path>]");
|
|
56
57
|
return 64;
|
|
57
58
|
}
|
|
58
59
|
|
package/src/cli/capabilities.ts
CHANGED
|
@@ -95,6 +95,13 @@ export const HEAD_CAPABILITIES: readonly HeadCapability[] = [
|
|
|
95
95
|
* A capability must not name a route the command does not actually fetch.
|
|
96
96
|
*/
|
|
97
97
|
export const CAPABILITIES: readonly Capability[] = [
|
|
98
|
+
{
|
|
99
|
+
command: ["chatgpt"],
|
|
100
|
+
summary: "Experimental ChatGPT app-server shim: launch, restore or status (macOS only).",
|
|
101
|
+
routes: [], flags: [], mutates: true, json: "none",
|
|
102
|
+
bannerLines: ["ocx chatgpt <sub> Experimental app-server shim: launch|restore|status (macOS)"],
|
|
103
|
+
details: ["Default off; launch requires chatgptDesktop.appServerShim: true. Restore removes the generated launcher."],
|
|
104
|
+
},
|
|
98
105
|
{
|
|
99
106
|
command: ["link", "port"],
|
|
100
107
|
summary: "Allocate a free loopback port for a remote home link.",
|
|
@@ -908,6 +915,14 @@ export const CAPABILITIES: readonly Capability[] = [
|
|
|
908
915
|
json: "payload",
|
|
909
916
|
details: ["`status` reads the route; `set` writes only submitted fields. Enabling first-party requires a running Claude intercept."],
|
|
910
917
|
},
|
|
918
|
+
{
|
|
919
|
+
command: ["claude", "intercept", "start"],
|
|
920
|
+
summary: "Start the local Claude interception pair on demand.",
|
|
921
|
+
routes: [{ method: "POST", path: "/api/claude-intercept/start" }],
|
|
922
|
+
flags: [{ name: "--json", value: "boolean", summary: "Emit the management response as JSON." }],
|
|
923
|
+
mutates: true,
|
|
924
|
+
json: "payload",
|
|
925
|
+
},
|
|
911
926
|
{
|
|
912
927
|
command: ["claude", "desktop", "status"],
|
|
913
928
|
summary: "Applied-vs-desired Claude Desktop state, including staleness, drift, and health.",
|
|
@@ -1060,6 +1075,51 @@ export const CAPABILITIES: readonly Capability[] = [
|
|
|
1060
1075
|
json: "payload",
|
|
1061
1076
|
details: ["A bare invocation reads and never writes."],
|
|
1062
1077
|
},
|
|
1078
|
+
{
|
|
1079
|
+
command: ["agent", "roles"],
|
|
1080
|
+
summary: "omo (Codex / LazyCodex): show each Codex agent role's model pin, set one role's model in its TOML and in omo.jsonc, or suggest a model for every role.",
|
|
1081
|
+
routes: [
|
|
1082
|
+
{ method: "GET", path: "/api/codex-agent-roles" },
|
|
1083
|
+
{ method: "PUT", path: "/api/codex-agent-roles/{role}" },
|
|
1084
|
+
{ method: "POST", path: "/api/codex-agent-roles/auto-assign" },
|
|
1085
|
+
],
|
|
1086
|
+
flags: [
|
|
1087
|
+
{ name: "--json", value: "boolean", summary: "Emit the role list, the write result or the proposals as JSON." },
|
|
1088
|
+
{ name: "--model", value: "string", summary: "suggest: size the roles with this model instead of the Codex default model." },
|
|
1089
|
+
{ name: "--apply", value: "boolean", summary: "suggest: write every proposal through the role model write." },
|
|
1090
|
+
],
|
|
1091
|
+
mutates: true,
|
|
1092
|
+
json: "payload",
|
|
1093
|
+
details: [
|
|
1094
|
+
"A bare invocation reads and never writes.",
|
|
1095
|
+
"Requires Codex-based omo (LazyCodex): the omo@sisyphuslabs Codex plugin enabled in config.toml and installed; otherwise status lists no roles, and set and suggest are refused.",
|
|
1096
|
+
"set rewrites only the root model value of $CODEX_HOME/agents/<role>.toml; omo.jsonc is skipped when absent or when it contains comments.",
|
|
1097
|
+
"suggest sizes every role with one model call and prints proposals without writing; --apply writes each proposed model, and its effort when the role file already sets model_reasoning_effort.",
|
|
1098
|
+
],
|
|
1099
|
+
},
|
|
1100
|
+
{
|
|
1101
|
+
command: ["agent", "injection"],
|
|
1102
|
+
summary: "Show or set the delegation model and effort, or suggest both for a described piece of delegated work.",
|
|
1103
|
+
routes: [
|
|
1104
|
+
{ method: "GET", path: "/api/injection-model" },
|
|
1105
|
+
{ method: "PUT", path: "/api/injection-model" },
|
|
1106
|
+
{ method: "POST", path: "/api/injection-model/suggest" },
|
|
1107
|
+
],
|
|
1108
|
+
flags: [
|
|
1109
|
+
{ name: "--json", value: "boolean", summary: "Emit the delegation settings, the write result or the proposal as JSON." },
|
|
1110
|
+
{ name: "--model", value: "string", summary: "set: the delegation model, - clears it. suggest: size the work with this model instead of the Codex default model." },
|
|
1111
|
+
{ name: "--effort", value: "string", summary: "set: the delegation reasoning effort, - clears it." },
|
|
1112
|
+
{ name: "--prompt", value: "string", summary: "set: a custom guidance prompt, - clears it." },
|
|
1113
|
+
{ name: "--guidance", value: "string", summary: "set: on or off for OpenCodex delegation guidance." },
|
|
1114
|
+
{ name: "--apply", value: "boolean", summary: "suggest: write the proposed model and effort through the delegation settings write." },
|
|
1115
|
+
],
|
|
1116
|
+
mutates: true,
|
|
1117
|
+
json: "payload",
|
|
1118
|
+
details: [
|
|
1119
|
+
"A bare invocation reads and never writes.",
|
|
1120
|
+
"suggest sizes the described work with one model call, picks the cheapest sufficient model the delegation picker offers, and writes nothing unless --apply is given.",
|
|
1121
|
+
],
|
|
1122
|
+
},
|
|
1063
1123
|
{
|
|
1064
1124
|
command: ["api", "protocols"],
|
|
1065
1125
|
summary: "Read the protocol contract version, API surfaces, protocol settings and feature vocabulary.",
|
|
@@ -1071,6 +1131,33 @@ export const CAPABILITIES: readonly Capability[] = [
|
|
|
1071
1131
|
mutates: false,
|
|
1072
1132
|
json: "payload",
|
|
1073
1133
|
},
|
|
1134
|
+
{
|
|
1135
|
+
command: ["combo", "test"],
|
|
1136
|
+
summary: "Run one JEV decision probe through a decision method: TypeSafe, a System One row, or an opencodex model.",
|
|
1137
|
+
routes: [{ method: "POST", path: "/api/combos/decision-test" }],
|
|
1138
|
+
flags: [
|
|
1139
|
+
{ name: "--combo", value: "string", summary: "Combo id whose saved decision method is probed and whose recursion rules apply." },
|
|
1140
|
+
{ name: "--decision-provider", value: "string", summary: "Probe a jev-decision provider row (or jev for TypeSafe) instead of the saved method." },
|
|
1141
|
+
{ name: "--decision-model", value: "string", summary: "Probe an opencodex-routed model instead of the saved method." },
|
|
1142
|
+
{ name: "--decision-timeout", value: "number", summary: "Decision deadline in milliseconds (1000-120000)." },
|
|
1143
|
+
{ name: "--json", value: "boolean", summary: "Emit the probe result." },
|
|
1144
|
+
],
|
|
1145
|
+
mutates: true,
|
|
1146
|
+
json: "payload",
|
|
1147
|
+
details: ["Sends one synthetic two-option decision; it may spend a decision call on the chosen backend."],
|
|
1148
|
+
},
|
|
1149
|
+
{
|
|
1150
|
+
command: ["combo", "discover"],
|
|
1151
|
+
summary: "List configured System One decision rows and catalog models that look like decision services.",
|
|
1152
|
+
routes: [{ method: "GET", path: "/api/combos/decision-discovery" }],
|
|
1153
|
+
flags: [
|
|
1154
|
+
{ name: "--query", value: "string", summary: "Match catalog rows by this text instead of the built-in decision-model hint." },
|
|
1155
|
+
{ name: "--json", value: "boolean", summary: "Emit the discovery payload." },
|
|
1156
|
+
],
|
|
1157
|
+
mutates: false,
|
|
1158
|
+
json: "payload",
|
|
1159
|
+
details: ["Read-only: nothing is probed and no provider row is created."],
|
|
1160
|
+
},
|
|
1074
1161
|
{
|
|
1075
1162
|
command: ["api", "explain"],
|
|
1076
1163
|
summary: "Preview the request path a model would take from one inbound API, computed from config.",
|