@yansigit/opencodex 2.31.3 → 2.32.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/bin/ocx.mjs +99 -70
- package/gui/dist/assets/index-BG43zwVe.js +102 -0
- package/gui/dist/assets/index-CiSI-jrP.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +6 -8
- package/src/adapters/base.ts +2 -0
- package/src/adapters/command-code.ts +2 -3
- package/src/adapters/cursor/live-models.ts +8 -0
- package/src/adapters/cursor/native-exec-desktop.ts +16 -0
- package/src/adapters/cursor/tool-definitions.ts +1 -1
- package/src/adapters/google.ts +6 -7
- package/src/adapters/kiro.ts +0 -3
- package/src/adapters/openai-responses.ts +3 -0
- package/src/adapters/tool-catalog-nudge.ts +1 -1
- package/src/adapters/xai-web-search.ts +7 -2
- package/src/bridge.ts +21 -15
- package/src/cli/dispatch.ts +50 -2
- package/src/cli/doctor.ts +24 -11
- package/src/cli/help.ts +4 -3
- package/src/cli/index.ts +11 -4
- package/src/cli/models.ts +13 -3
- package/src/cli/observe.ts +20 -5
- package/src/cli/provider.ts +2 -1
- package/src/cli/registry.ts +7 -5
- package/src/cli/status.ts +2 -1
- package/src/cli/system-restart-client.ts +1 -1
- package/src/cli/usage-report.ts +134 -0
- package/src/codex/app-server-processes.ts +3 -1
- package/src/codex/catalog/aggregation.ts +13 -1
- package/src/codex/catalog/effort.ts +24 -9
- package/src/codex/catalog/model-metadata.ts +566 -0
- package/src/codex/catalog/parsing.ts +33 -0
- package/src/codex/catalog/provider-fetch.ts +123 -32
- package/src/codex/catalog/sync.ts +5 -4
- package/src/codex/desktop-app-restart.ts +342 -0
- package/src/codex/history-job.ts +32 -3
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +5 -5
- package/src/codex/history-provider.ts +825 -247
- package/src/codex/history-worker.ts +8 -5
- package/src/codex/inject.ts +49 -21
- package/src/codex/injected-marker.ts +1 -1
- package/src/codex/internal/history-writer.ts +4 -3
- package/src/codex/native-profile-startup.ts +157 -27
- package/src/codex/native-residue.ts +26 -33
- package/src/combos/failover.ts +27 -0
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +81 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +308 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config.ts +75 -812
- package/src/generated/compatibility-version.json +145 -85
- package/src/images/plan.ts +5 -4
- package/src/integrations/ownership-policy.ts +141 -0
- package/src/integrations/ownership.ts +10 -0
- package/src/integrations/state.ts +44 -5
- package/src/integrations/writer.ts +6 -0
- package/src/lib/bounded-body.ts +14 -2
- package/src/lib/process-control.ts +2 -1
- package/src/lib/state-store-registrations.ts +2 -0
- package/src/lib/tool-argument-integers.ts +56 -5
- package/src/oauth/health.ts +1 -1
- package/src/providers/registry.ts +1 -1
- package/src/reasoning-effort.ts +19 -2
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/custom-tool-compat.ts +132 -38
- package/src/responses/parser.ts +3 -2
- package/src/responses/reasoning-replay-cache.ts +81 -3
- package/src/server/auth-cors.ts +9 -7
- package/src/server/index.ts +102 -21
- package/src/server/local-management-read-client.ts +1 -1
- package/src/server/local-provider-reload-client.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +1 -1
- package/src/server/management/config-routes.ts +4 -1
- package/src/server/management/context.ts +1 -1
- package/src/server/management/logs-usage-routes.ts +27 -6
- package/src/server/management/model-routes.ts +8 -4
- package/src/server/management/native-integration-routes.ts +2 -1
- package/src/server/management/provider-capability-config.ts +1 -1
- package/src/server/management/system-restart.ts +1 -1
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/proxy-liveness.ts +2 -1
- package/src/server/request-log-conversation.ts +30 -0
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/combo-stream-preflight.ts +171 -0
- package/src/server/responses/compact.ts +6 -21
- package/src/server/responses/core.ts +225 -94
- package/src/server/responses/fetch-helpers.ts +2 -97
- package/src/server/responses-custom-tool-repair.ts +41 -5
- package/src/server/responses-undeclared-tool-guard.ts +156 -15
- package/src/service.ts +8 -4
- package/src/types/request.ts +6 -1
- package/src/types/tools.ts +87 -11
- package/src/types.ts +1 -1
- package/src/update/index.ts +5 -4
- package/src/update/job.ts +3 -1
- package/src/update/transactional-install.mjs +8 -1
- package/src/usage/log.ts +16 -8
- package/src/usage/summary.ts +201 -8
- package/src/vision/describe.ts +18 -13
- package/src/web-search/executor.ts +10 -3
- package/gui/dist/assets/index-CGoDO3uO.css +0 -1
- package/gui/dist/assets/index-Cxt5fZMP.js +0 -102
package/src/adapters/google.ts
CHANGED
|
@@ -14,7 +14,7 @@ import type {
|
|
|
14
14
|
OcxToolResultMessage,
|
|
15
15
|
OcxUsage,
|
|
16
16
|
} from "../types";
|
|
17
|
-
import { isAllowedToolChoice, namespacedToolName, resolveToolChoiceWireName,
|
|
17
|
+
import { isAllowedToolChoice, namespacedToolName, resolveToolChoiceWireName, toolChoiceToolPredicate } from "../types";
|
|
18
18
|
import { contentPartsToText, parseDataUrl } from "./image";
|
|
19
19
|
import { getVertexAccessToken } from "../lib/gcp-adc";
|
|
20
20
|
import { fetchAntigravityWithRetry, fetchVertexWithRetry } from "./google-http";
|
|
@@ -346,11 +346,8 @@ function toolsToGeminiFormat(
|
|
|
346
346
|
wireModelId: string,
|
|
347
347
|
): unknown[] | undefined {
|
|
348
348
|
const grounding = parsed._ccaInTurnGrounding;
|
|
349
|
-
const
|
|
350
|
-
?
|
|
351
|
-
: undefined;
|
|
352
|
-
const tools = allowed
|
|
353
|
-
? parsed.context.tools?.filter(t => toolAllowedByChoice(t, allowed, parsed.context.tools)) ?? []
|
|
349
|
+
const tools = isAllowedToolChoice(parsed.options.toolChoice)
|
|
350
|
+
? parsed.context.tools?.filter(toolChoiceToolPredicate(parsed.options.toolChoice, parsed.context.tools)) ?? []
|
|
354
351
|
: parsed.context.tools ?? [];
|
|
355
352
|
const functionDeclarations = tools.map(t => ({
|
|
356
353
|
name: namespacedToolName(t.namespace, t.name),
|
|
@@ -1077,7 +1074,9 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1077
1074
|
const replaySession = provider.googleMode === "cloud-code-assist" ? antigravitySession : vertexReplaySession;
|
|
1078
1075
|
if ((provider.googleMode === "cloud-code-assist" || provider.googleMode === "vertex")
|
|
1079
1076
|
&& parts && replayModel && replaySession) {
|
|
1080
|
-
|
|
1077
|
+
// Observation may scan the whole frame, so use it only for replay-cache side effects.
|
|
1078
|
+
// The source-order loop below exclusively owns stream carry and cannot pair backwards.
|
|
1079
|
+
observeAntigravityReplay(
|
|
1081
1080
|
replayModel,
|
|
1082
1081
|
replaySession,
|
|
1083
1082
|
parts as unknown[],
|
package/src/adapters/kiro.ts
CHANGED
|
@@ -318,9 +318,6 @@ function validateKiroCapabilities(parsed: OcxParsedRequest): void {
|
|
|
318
318
|
if (choice !== undefined && choice !== "auto" && choice !== "none") {
|
|
319
319
|
throw new Error("Kiro supports only automatic tool choice or tool_choice:none");
|
|
320
320
|
}
|
|
321
|
-
if (parsed.options.parallelToolCalls === true) {
|
|
322
|
-
throw new Error("Kiro does not support parallel tool calls");
|
|
323
|
-
}
|
|
324
321
|
if (parsed.options.serviceTier !== undefined) {
|
|
325
322
|
throw new Error("Kiro does not support service tiers");
|
|
326
323
|
}
|
|
@@ -1684,6 +1684,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
1684
1684
|
|
|
1685
1685
|
const forward = provider.authMode === "forward";
|
|
1686
1686
|
let convertedRoutedCustomToolNames: Set<string> | undefined;
|
|
1687
|
+
let routedCustomToolRepairNames: Set<string> | undefined;
|
|
1687
1688
|
let convertedRoutedToolSearchNames: Set<string> | undefined;
|
|
1688
1689
|
let convertedRoutedNamespaceToolAliases: Map<string, { namespace: string; name: string }> | undefined;
|
|
1689
1690
|
const unexpandedMiss = !!parsed.previousResponseId && parsed._previousResponseInputExpanded !== true;
|
|
@@ -1741,6 +1742,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
1741
1742
|
);
|
|
1742
1743
|
outBody = rewritten.body;
|
|
1743
1744
|
convertedRoutedCustomToolNames = rewritten.names;
|
|
1745
|
+
routedCustomToolRepairNames = rewritten.repairNames;
|
|
1744
1746
|
}
|
|
1745
1747
|
if (!isCanonicalOpenAiForwardProvider(provider)) {
|
|
1746
1748
|
// Run after custom-tool lowering so the search compatibility layer can choose a
|
|
@@ -1811,6 +1813,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
1811
1813
|
body,
|
|
1812
1814
|
releaseBodyObservation,
|
|
1813
1815
|
...(convertedRoutedCustomToolNames ? { convertedRoutedCustomToolNames } : {}),
|
|
1816
|
+
...(routedCustomToolRepairNames ? { routedCustomToolRepairNames } : {}),
|
|
1814
1817
|
...(convertedRoutedToolSearchNames ? { convertedRoutedToolSearchNames } : {}),
|
|
1815
1818
|
...(convertedRoutedNamespaceToolAliases ? { convertedRoutedNamespaceToolAliases } : {}),
|
|
1816
1819
|
...(tierLog ? { tierLog } : {}),
|
|
@@ -120,7 +120,7 @@ export function buildNonOpenAIToolCatalogNudgeFromNames(
|
|
|
120
120
|
"Call only listed names with their listed argument keys; do not invent, translate, or rename tools.",
|
|
121
121
|
"Names mentioned only in instructions, tool descriptions, argument descriptions, or nested helper APIs are not additional top-level tools.",
|
|
122
122
|
verifiedCodeModeExecName
|
|
123
|
-
? "`" + verifiedCodeModeExecName + "` is Codex code mode: its body is JavaScript evaluated in a V8 isolate. Nested helpers are called INSIDE that body as `await tools.<name>(...)`, for example `await tools.exec_command({cmd: \"ls\"})` or `await tools.codex_app__list_threads({})`. Absence from the top-level catalog or from `" + verifiedCodeModeExecName + "`'s description is not absence: deferred helpers stay callable on `tools.<name>`. Discover them from the isolate global `ALL_TOOLS`, not `tools.ALL_TOOLS`. Do not skip an available nested helper because it is omitted from the listed top-level names."
|
|
123
|
+
? "`" + verifiedCodeModeExecName + "` is Codex code mode: its body is JavaScript evaluated in a V8 isolate. Nested helpers are called INSIDE that body as `await tools.<name>(...)`, for example `await tools.exec_command({cmd: \"ls\"})` or `await tools.codex_app__list_threads({})`. Absence from the top-level catalog or from `" + verifiedCodeModeExecName + "`'s description is not absence: deferred helpers stay callable on `tools.<name>`. Discover them from the isolate global `ALL_TOOLS`, not `tools.ALL_TOOLS`. Do not skip an available nested helper because it is omitted from the listed top-level names. Nested `tools.apply_patch(input)` is host-executed: the string must begin exactly with `*** Begin Patch` and end with `*** End Patch` (no trailing `***` on those lines). OpenCodex does not rewrite JavaScript inside exec, so a decorated `*** Begin Patch ***` envelope is rejected by Codex before the file is touched."
|
|
124
124
|
: "If a listed tool exposes nested helpers such as a tools.* API, call the listed parent tool and use those helpers only inside that tool's input.",
|
|
125
125
|
unavailableNeighborNames.length > 0
|
|
126
126
|
? "Do not use neighboring-agent tool names " + quoteNames(unavailableNeighborNames) + " unless this turn's catalog lists those exact names."
|
|
@@ -60,10 +60,15 @@ function normalizeToolGroup(tools: unknown[]): ToolGroupRewrite {
|
|
|
60
60
|
: undefined;
|
|
61
61
|
const enableImageSearch = searchContentTypes?.includes("image") === true;
|
|
62
62
|
const next: Record<string, unknown> = { ...tool, type: CODEX_WEB_SEARCH_TOOL };
|
|
63
|
+
// Only the two fields xAI actually refuses are removed. Probed 2026-08-22, one field per
|
|
64
|
+
// request, against BOTH xAI destinations (api.x.ai and cli-chat-proxy.grok.com): they behave
|
|
65
|
+
// identically — `external_web_access` 400s on every value including `true`, and
|
|
66
|
+
// `search_context_size` 400s, while `user_location`, `search_content_types`, `filters` and
|
|
67
|
+
// `enable_image_search` are all accepted. Deleting the accepted ones was a silent capability
|
|
68
|
+
// loss, and it contradicted the sibling layer, whose own probe note already records
|
|
69
|
+
// user_location/filters as accepted (tests/responses-routed-web-search-fields.test.ts).
|
|
63
70
|
delete next.external_web_access;
|
|
64
71
|
delete next.search_context_size;
|
|
65
|
-
delete next.search_content_types;
|
|
66
|
-
delete next.user_location;
|
|
67
72
|
if (enableImageSearch && !Object.hasOwn(next, "enable_image_search")) {
|
|
68
73
|
next.enable_image_search = true;
|
|
69
74
|
}
|
package/src/bridge.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
} from "./types";
|
|
9
9
|
import { coerceIntegerToolArguments } from "./lib/tool-argument-integers";
|
|
10
10
|
import { adapterFailureFromMessage, classifyError, CYBER_POLICY_ERROR_CODE, isCyberPolicyCode, type OcxErrorPayload } from "./lib/errors";
|
|
11
|
+
import { repairFreeformToolInput } from "./responses/apply-patch-envelope";
|
|
11
12
|
import { encodeCompactionSummary } from "./responses/compaction";
|
|
12
13
|
import { isTruncatedStopReason, truncationReasonFor } from "./responses/truncated-stop-reason";
|
|
13
14
|
import { encodeReasoningEnvelope, type ReasoningEnvelope } from "./responses/reasoning-envelope";
|
|
@@ -230,12 +231,12 @@ export function bridgeToResponsesSSE(
|
|
|
230
231
|
const replayCacheScope = options?.replayCacheScope;
|
|
231
232
|
const setBeatInterval = options?.timers?.setInterval ?? ((handler: () => void, ms: number) => setInterval(handler, ms));
|
|
232
233
|
const clearBeatInterval = options?.timers?.clearInterval ?? ((id: unknown) => clearInterval(id as ReturnType<typeof setInterval>));
|
|
233
|
-
// Freeform/custom tools (apply_patch) carry their body in `input`; the
|
|
234
|
-
// function with `{input:string}`, so unwrap it here when relaying back
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
234
|
+
// Freeform/custom tools (apply_patch, code-mode exec) carry their body in `input`; the
|
|
235
|
+
// model is given a function with `{input:string}`, so unwrap it here when relaying back
|
|
236
|
+
// as a custom_tool_call. Decorated apply_patch envelopes are repaired at this boundary.
|
|
237
|
+
const freeformInput = (args: string, toolName: string, namespace?: string): string => (
|
|
238
|
+
repairFreeformToolInput(args, toolName, namespace)
|
|
239
|
+
);
|
|
239
240
|
// Best-effort unwrap of a PARTIAL freeform arg buffer for live input streaming
|
|
240
241
|
// (`response.custom_tool_call_input.delta` — codex-rs uses it for UI preview only;
|
|
241
242
|
// the completed custom_tool_call item stays authoritative). Compact `{"input":"...`
|
|
@@ -626,6 +627,7 @@ export function bridgeToResponsesSSE(
|
|
|
626
627
|
const argsStr = coerceIntegerToolArguments(
|
|
627
628
|
currentToolCall.args || "{}",
|
|
628
629
|
options?.toolParameterSchemas?.get(currentToolCall.name),
|
|
630
|
+
currentToolCall.namespace === undefined ? currentToolCall.name : undefined,
|
|
629
631
|
);
|
|
630
632
|
// Finalize streamed function-call arguments so Codex commits the call (incl. MCP / computer_use).
|
|
631
633
|
if (!currentToolCall.freeform && !currentToolCall.toolSearch) {
|
|
@@ -636,7 +638,8 @@ export function bridgeToResponsesSSE(
|
|
|
636
638
|
if (currentToolCall.freeform) {
|
|
637
639
|
emit("response.custom_tool_call_input.done", {
|
|
638
640
|
item_id: currentToolCall.itemId, output_index: currentToolCall.outputIndex,
|
|
639
|
-
|
|
641
|
+
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
642
|
+
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace),
|
|
640
643
|
});
|
|
641
644
|
}
|
|
642
645
|
// Freeform tools serialize as custom_tool_call without extra_content; remember the
|
|
@@ -652,7 +655,8 @@ export function bridgeToResponsesSSE(
|
|
|
652
655
|
? {
|
|
653
656
|
type: "custom_tool_call", id: currentToolCall.itemId,
|
|
654
657
|
call_id: currentToolCall.callId, name: currentToolCall.name,
|
|
655
|
-
|
|
658
|
+
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
659
|
+
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace), status: "completed",
|
|
656
660
|
}
|
|
657
661
|
: {
|
|
658
662
|
type: "function_call", id: currentToolCall.itemId,
|
|
@@ -691,7 +695,8 @@ export function bridgeToResponsesSSE(
|
|
|
691
695
|
? {
|
|
692
696
|
type: "custom_tool_call", id: currentToolCall.itemId,
|
|
693
697
|
call_id: currentToolCall.callId, name: currentToolCall.name,
|
|
694
|
-
|
|
698
|
+
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
699
|
+
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace), status: "incomplete",
|
|
695
700
|
}
|
|
696
701
|
: {
|
|
697
702
|
type: "function_call", id: currentToolCall.itemId,
|
|
@@ -1064,7 +1069,7 @@ export function bridgeToResponsesSSE(
|
|
|
1064
1069
|
const item = toolSearch
|
|
1065
1070
|
? { type: "tool_search_call", id: itemId, call_id: event.id, execution: "client", arguments: {}, status: "in_progress" }
|
|
1066
1071
|
: freeform
|
|
1067
|
-
? { type: "custom_tool_call", id: itemId, call_id: event.id, name: realName, input: "", status: "in_progress" }
|
|
1072
|
+
? { type: "custom_tool_call", id: itemId, call_id: event.id, name: realName, ...(ns ? { namespace: ns } : {}), input: "", status: "in_progress" }
|
|
1068
1073
|
: { type: "function_call", id: itemId, call_id: event.id, name: realName, arguments: "", status: "in_progress", ...(ns ? { namespace: ns } : {}) };
|
|
1069
1074
|
emit("response.output_item.added", { output_index: outputIndex, item });
|
|
1070
1075
|
currentToolCall = { itemId, outputIndex, callId: event.id, name: realName, args: "", argsBytes: 0, namespace: ns, freeform, toolSearch, providerMetadata: event.providerMetadata };
|
|
@@ -1567,10 +1572,9 @@ function buildResponseJSONWithBudget(
|
|
|
1567
1572
|
// Web-search citations awaiting the next assistant message (attached as url_citation annotations).
|
|
1568
1573
|
let pendingWebSources: { url: string; title?: string }[] = [];
|
|
1569
1574
|
|
|
1570
|
-
const freeformInput = (args: string): string =>
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
};
|
|
1575
|
+
const freeformInput = (args: string, toolName: string, namespace?: string): string => (
|
|
1576
|
+
repairFreeformToolInput(args, toolName, namespace)
|
|
1577
|
+
);
|
|
1574
1578
|
const parseArgsObj = (args: string): Record<string, unknown> => {
|
|
1575
1579
|
try { const o = JSON.parse(args); return o && typeof o === "object" ? o : {}; } catch { return {}; }
|
|
1576
1580
|
};
|
|
@@ -1656,6 +1660,7 @@ function buildResponseJSONWithBudget(
|
|
|
1656
1660
|
const coercedArgs = coerceIntegerToolArguments(
|
|
1657
1661
|
currentToolCallArgs,
|
|
1658
1662
|
options?.toolParameterSchemas?.get(currentToolCallName),
|
|
1663
|
+
ns === undefined ? realName : undefined,
|
|
1659
1664
|
);
|
|
1660
1665
|
// Freeform tools serialize as custom_tool_call without extra_content; remember the
|
|
1661
1666
|
// signature server-side regardless so the replayed call can be re-signed (#1735).
|
|
@@ -1670,7 +1675,8 @@ function buildResponseJSONWithBudget(
|
|
|
1670
1675
|
pushOutput({
|
|
1671
1676
|
type: "custom_tool_call", id: `ctc_${uuid()}`,
|
|
1672
1677
|
call_id: currentToolCallId, name: realName,
|
|
1673
|
-
|
|
1678
|
+
...(ns ? { namespace: ns } : {}),
|
|
1679
|
+
input: freeformInput(currentToolCallArgs, realName, ns), status,
|
|
1674
1680
|
});
|
|
1675
1681
|
} else {
|
|
1676
1682
|
pushOutput({
|
package/src/cli/dispatch.ts
CHANGED
|
@@ -203,7 +203,11 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
203
203
|
return 0;
|
|
204
204
|
},
|
|
205
205
|
sync: async deps => {
|
|
206
|
-
const
|
|
206
|
+
const syncArgs = deps.args.slice(1);
|
|
207
|
+
const restartCodex = syncArgs.includes("--restart-codex");
|
|
208
|
+
// Separate flag on purpose: --restart-codex promises app-server-only scope,
|
|
209
|
+
// and quitting the desktop app ends live conversations.
|
|
210
|
+
const restartDesktopApp = syncArgs.includes("--restart-desktop-app");
|
|
207
211
|
const live = await deps.findLiveProxy();
|
|
208
212
|
const synced = await syncModelsToCodex(
|
|
209
213
|
live?.port,
|
|
@@ -229,6 +233,7 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
229
233
|
// exactly when a long-lived app-server is holding the stale list.
|
|
230
234
|
if (synced.catalogWritten || synced.cacheSynced) {
|
|
231
235
|
afterCatalogWriteHandleAppServers({ restart: restartCodex, log: console });
|
|
236
|
+
if (restartDesktopApp) await handleDesktopAppRestart(console);
|
|
232
237
|
}
|
|
233
238
|
// `ocx sync` is a direct CLI path; it does not call the management
|
|
234
239
|
// `/api/sync` route. Refresh the already-connected MCode block here too,
|
|
@@ -259,7 +264,9 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
259
264
|
return await cmdV2(deps.args.slice(1), {}, async () => (await deps.findLiveProxy())?.port);
|
|
260
265
|
},
|
|
261
266
|
"sync-cache": async deps => {
|
|
262
|
-
const
|
|
267
|
+
const cacheArgs = deps.args.slice(1);
|
|
268
|
+
const restartCodex = cacheArgs.includes("--restart-codex");
|
|
269
|
+
const restartDesktopApp = cacheArgs.includes("--restart-desktop-app");
|
|
263
270
|
const { withCatalogWriteSerialization } = await import("../codex/catalog-write-serialization");
|
|
264
271
|
const { invalidateCodexModelsCacheWithPermit } = await import("../codex/catalog/sync");
|
|
265
272
|
const { getCodexHome } = await import("../codex/paths");
|
|
@@ -270,6 +277,7 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
270
277
|
// Only warn/restart when models_cache was actually rewritten from a readable catalog.
|
|
271
278
|
if (invalidated.kind === "completed" && invalidated.value) {
|
|
272
279
|
afterCatalogWriteHandleAppServers({ restart: restartCodex, log: console });
|
|
280
|
+
if (restartDesktopApp) await handleDesktopAppRestart(console);
|
|
273
281
|
} else if (desiredDisabled) {
|
|
274
282
|
console.log("Codex integration is OFF; cache sync skipped (no catalog or cache write).");
|
|
275
283
|
}
|
|
@@ -583,3 +591,43 @@ export async function dispatchCommand(head: CliHead, deps: CliDispatchDeps): Pro
|
|
|
583
591
|
}
|
|
584
592
|
return await runner(deps);
|
|
585
593
|
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Report the outcome of an opt-in desktop-app restart. Kept next to the two
|
|
597
|
+
* callers so `sync` and `sync-cache` cannot drift in what they tell the user.
|
|
598
|
+
*/
|
|
599
|
+
async function handleDesktopAppRestart(log: Pick<Console, "log" | "error">): Promise<void> {
|
|
600
|
+
const { restartCodexDesktopApp } = await import("../codex/desktop-app-restart");
|
|
601
|
+
const result = restartCodexDesktopApp();
|
|
602
|
+
switch (result.reason) {
|
|
603
|
+
case "windows_only":
|
|
604
|
+
log.error("--restart-desktop-app is supported on Windows only; nothing was stopped.");
|
|
605
|
+
return;
|
|
606
|
+
case "package_discovery_failed":
|
|
607
|
+
log.error(
|
|
608
|
+
"Could not identify the installed Codex desktop package. Quit and relaunch the desktop app "
|
|
609
|
+
+ "manually to refresh the model picker.",
|
|
610
|
+
);
|
|
611
|
+
return;
|
|
612
|
+
case "self_ancestry":
|
|
613
|
+
log.error(
|
|
614
|
+
"Refusing to restart the desktop app because this command is running inside it. "
|
|
615
|
+
+ "Run 'ocx sync --restart-desktop-app' from an external terminal instead.",
|
|
616
|
+
);
|
|
617
|
+
return;
|
|
618
|
+
case "no_targets":
|
|
619
|
+
log.log("Codex desktop app is not running; nothing to restart.");
|
|
620
|
+
return;
|
|
621
|
+
case "targets_survived":
|
|
622
|
+
log.error(
|
|
623
|
+
`Codex desktop app PID(s) ${result.surviving.join(", ")} did not exit, so it was not relaunched. `
|
|
624
|
+
+ "Quit the desktop app manually to refresh the model picker.",
|
|
625
|
+
);
|
|
626
|
+
return;
|
|
627
|
+
default:
|
|
628
|
+
if (result.relaunch === "started") {
|
|
629
|
+
log.log("Codex desktop app restarted; its model picker will re-read the catalog.");
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
package/src/cli/doctor.ts
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
import { accessSync, constants, existsSync, readFileSync } from "node:fs";
|
|
11
11
|
import { homedir } from "node:os";
|
|
12
12
|
import { dirname, join } from "node:path";
|
|
13
|
-
import { getConfigDir, getConfigPath, readConfigDiagnostics,
|
|
13
|
+
import { getConfigDir, getConfigPath, readConfigDiagnostics, resolveEnvValue } from "../config";
|
|
14
|
+
import { readPid } from "../config/process-state";
|
|
14
15
|
import { findLiveProxy, type LiveProxy } from "../server/proxy-liveness";
|
|
15
16
|
import { BUN_RUNTIME_SOURCES } from "../lib/bun-runtime";
|
|
16
17
|
import type { BunRuntimeSource } from "../lib/bun-runtime";
|
|
@@ -1072,10 +1073,10 @@ export async function runDoctor(args: string[] = []): Promise<void> {
|
|
|
1072
1073
|
console.log(` ${probe.ok ? "ok " : "-- "} ${WHAM_USAGE_URL}`);
|
|
1073
1074
|
console.log(` ${detail}, ${probe.durationMs}ms, ${probe.authenticated ? "authenticated" : "unauthenticated"}`);
|
|
1074
1075
|
|
|
1075
|
-
// Design B upgrade visibility:
|
|
1076
|
-
//
|
|
1077
|
-
//
|
|
1078
|
-
console.log("\nCodex history
|
|
1076
|
+
// Design B upgrade visibility: only the backup manifest authorizes restoring provider
|
|
1077
|
+
// metadata. Bare routed rows have unknown provenance and remain unchanged. This read-only
|
|
1078
|
+
// probe reports manifest work and database readability; it never mutates.
|
|
1079
|
+
console.log("\nCodex history metadata restore");
|
|
1079
1080
|
// The history failure messages point here; make the visit worthwhile by
|
|
1080
1081
|
// probing the coordinator namespace the locks live in. The probe exercises
|
|
1081
1082
|
// identity, runtime-root, and permission checks without taking any lock or
|
|
@@ -1096,11 +1097,17 @@ export async function runDoctor(args: string[] = []): Promise<void> {
|
|
|
1096
1097
|
for (const line of formatCoordinatorDoctorLines(inspectCodexCoordinator())) console.log(line);
|
|
1097
1098
|
const pending = countPendingOpencodexHistory();
|
|
1098
1099
|
if (pending.failed) {
|
|
1099
|
-
|
|
1100
|
+
if (pending.failureReason === "busy") {
|
|
1101
|
+
console.log(" -- history database, backup manifest, or rollout file is busy — exact metadata restore is pending");
|
|
1102
|
+
} else if (pending.failureReason === "permission") {
|
|
1103
|
+
console.log(" -- state DB or backup manifest access was denied — restore state unknown");
|
|
1104
|
+
} else {
|
|
1105
|
+
console.log(" -- backup manifest or restore target failed integrity checks — manual review required");
|
|
1106
|
+
}
|
|
1100
1107
|
} else if (pending.pendingRows === 0 && pending.backupEntries === 0) {
|
|
1101
|
-
console.log(" ok no
|
|
1108
|
+
console.log(" ok no manifest-backed provider metadata pending; untracked routed history is unchanged");
|
|
1102
1109
|
} else {
|
|
1103
|
-
console.log(` -- ${pending.
|
|
1110
|
+
console.log(` -- ${pending.backupEntries} backup manifest entr${pending.backupEntries === 1 ? "y" : "ies"} pending exact metadata restore`);
|
|
1104
1111
|
}
|
|
1105
1112
|
|
|
1106
1113
|
console.log("\nProject Codex configs");
|
|
@@ -1148,7 +1155,7 @@ export async function runDoctor(args: string[] = []): Promise<void> {
|
|
|
1148
1155
|
const { collectCodexAppServerCatalogState } = await import("../codex/app-server-processes");
|
|
1149
1156
|
const catalogState = collectCodexAppServerCatalogState();
|
|
1150
1157
|
if (catalogState.state === "stale") {
|
|
1151
|
-
console.log(` [WARN] Codex app-server (PID(s): ${catalogState.processes.map(p => p.pid).join(", ")}) started before the on-disk catalog changed; its in-memory model list disagrees with ocx. Action: restart Codex (or run \`ocx sync --restart-codex\`)`);
|
|
1158
|
+
console.log(` [WARN] Codex app-server (PID(s): ${catalogState.processes.map(p => p.pid).join(", ")}) started before the on-disk catalog changed; its in-memory model list disagrees with ocx. Action: restart Codex (or run \`ocx sync --restart-codex\`; on Windows the desktop app may need \`ocx sync --restart-desktop-app\`)`);
|
|
1152
1159
|
} else if (catalogState.state === "unknown") {
|
|
1153
1160
|
console.log(" [WARN] Could not verify whether the running Codex app-server's model catalog is current (start time or catalog unreadable). Action: if the model list looks stale, restart Codex");
|
|
1154
1161
|
} else if (catalogState.state === "fresh") {
|
|
@@ -1185,8 +1192,14 @@ export async function runDoctor(args: string[] = []): Promise<void> {
|
|
|
1185
1192
|
}
|
|
1186
1193
|
}
|
|
1187
1194
|
}
|
|
1188
|
-
if (pending.failed
|
|
1189
|
-
hints.push("
|
|
1195
|
+
if (pending.failed && pending.failureReason === "busy") {
|
|
1196
|
+
hints.push("Backed-up history metadata is pending or its state is unreadable. The running proxy retries exact restoration automatically; to force it now, close the Codex app and run 'ocx sync'. Untracked routed history is not relabeled.");
|
|
1197
|
+
} else if (pending.failed && pending.failureReason === "permission") {
|
|
1198
|
+
hints.push("Backed-up history metadata could not be inspected because access was denied. Fix access to the reported Codex history paths, then run 'ocx sync'; repeated retries do not repair permissions.");
|
|
1199
|
+
} else if (pending.failed) {
|
|
1200
|
+
hints.push("The history manifest or its target is invalid or changed. Preserve both, inspect the manifest/database/rollout identity, and do not repeatedly run 'ocx sync' until the mismatch is understood. Untracked routed history is not relabeled.");
|
|
1201
|
+
} else if (pending.backupEntries > 0) {
|
|
1202
|
+
hints.push("Backed-up history metadata is pending. The running proxy retries exact restoration automatically; to force it now, close the Codex app and run 'ocx sync'. Untracked routed history is not relabeled.");
|
|
1190
1203
|
}
|
|
1191
1204
|
if (dual.dualInstall && !dual.effectiveIsWindowsMount) {
|
|
1192
1205
|
hints.push(`Codex is installed on BOTH WSL and Windows. Each side keeps its own ~/.codex (logins, config, catalog are separate); ocx here manages the Linux one. To share a single home, set CODEX_HOME=${dual.windowsCodexHomes[0] ?? `${dual.automountRoot}/c/Users/<you>/.codex`} in WSL (drvfs file locking is less reliable).`);
|
package/src/cli/help.ts
CHANGED
|
@@ -24,8 +24,8 @@ Usage:
|
|
|
24
24
|
ocx stop Stop the proxy AND restore native Codex (plain codex works again)
|
|
25
25
|
ocx restore Restore native Codex without stopping (alias: eject)
|
|
26
26
|
ocx restore back Re-point codex at the running proxy (undo restore)
|
|
27
|
-
ocx recover-history --legacy-openai
|
|
28
|
-
|
|
27
|
+
ocx recover-history --legacy-openai --yes
|
|
28
|
+
Force all user-message opencodex rows to OpenAI (legacy recovery)
|
|
29
29
|
ocx uninstall Remove service/shim/config and restore native Codex (alias: remove)
|
|
30
30
|
ocx service [sub] Run as a background service (default: install/update/start)
|
|
31
31
|
ocx codex-shim <sub> Auto-start proxy when \`codex\` launches (install|status|uninstall|remove)
|
|
@@ -57,7 +57,8 @@ Usage:
|
|
|
57
57
|
ocx observe <sub> Logs, usage, storage, memory, and debug data
|
|
58
58
|
ocx route <sub> Routing features (combo, policy)
|
|
59
59
|
ocx logs [filters] Alias of ocx observe logs
|
|
60
|
-
ocx usage [--range <7d|30d|all>]
|
|
60
|
+
ocx usage [--range <today|1d|7d|30d|all>] [--provider <name>] [--model <id>]
|
|
61
|
+
Token and estimated-cost report (alias of ocx observe usage)
|
|
61
62
|
ocx storage [--json] Alias of ocx observe storage
|
|
62
63
|
ocx memory [--json] Alias of ocx observe memory
|
|
63
64
|
ocx api-key <sub> Alias of ocx access key
|
package/src/cli/index.ts
CHANGED
|
@@ -12,6 +12,9 @@ import {
|
|
|
12
12
|
codexAutoStartEnabled,
|
|
13
13
|
getConfigDir,
|
|
14
14
|
loadConfig,
|
|
15
|
+
saveConfig,
|
|
16
|
+
} from "../config";
|
|
17
|
+
import {
|
|
15
18
|
readPid,
|
|
16
19
|
readPidFileValue,
|
|
17
20
|
readRuntimePort,
|
|
@@ -19,10 +22,9 @@ import {
|
|
|
19
22
|
removePidIfValueIs,
|
|
20
23
|
removeRuntimePort,
|
|
21
24
|
removeRuntimePortIfPidIs,
|
|
22
|
-
saveConfig,
|
|
23
25
|
writePid,
|
|
24
26
|
writeRuntimePort,
|
|
25
|
-
} from "../config";
|
|
27
|
+
} from "../config/process-state";
|
|
26
28
|
import { collectStatus } from "./status";
|
|
27
29
|
|
|
28
30
|
import {
|
|
@@ -906,8 +908,13 @@ async function handleStatus() {
|
|
|
906
908
|
|
|
907
909
|
async function handleRecoverHistory() {
|
|
908
910
|
if (args[1] !== "--legacy-openai") {
|
|
909
|
-
console.error("Usage: ocx recover-history --legacy-openai");
|
|
910
|
-
console.error("
|
|
911
|
+
console.error("Usage: ocx recover-history --legacy-openai --yes");
|
|
912
|
+
console.error("This force-relabels every user-message opencodex row to OpenAI, including legitimate dedicated-provider history. Back up first and use it only for pre-backup legacy recovery.");
|
|
913
|
+
process.exit(1);
|
|
914
|
+
}
|
|
915
|
+
console.error("WARNING: this force-relabels every user-message opencodex row to OpenAI, normalizes exec to cli, and includes legitimate dedicated-provider history.");
|
|
916
|
+
if (args.length !== 3 || args[2] !== "--yes") {
|
|
917
|
+
console.error("Re-run with explicit confirmation: ocx recover-history --legacy-openai --yes");
|
|
911
918
|
process.exit(1);
|
|
912
919
|
}
|
|
913
920
|
// Manifest-independent legacy ejection, serialized like every other history
|
package/src/cli/models.ts
CHANGED
|
@@ -5,7 +5,12 @@ import { randomUUID } from "node:crypto";
|
|
|
5
5
|
import { createInterface } from "node:readline/promises";
|
|
6
6
|
import { syncModelsToCodex } from "../codex/sync";
|
|
7
7
|
import { hasOwnProvider, isValidProviderName, loadConfig, saveConfig } from "../config";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
canonicalizeReasoningEfforts,
|
|
10
|
+
configuredReasoningEfforts,
|
|
11
|
+
isDeclaredReasoningEffort,
|
|
12
|
+
modelRecordValue,
|
|
13
|
+
} from "../reasoning-effort";
|
|
9
14
|
import { encodedModelIdCollides, routedSlug, slugEquals } from "../providers/slug-codec";
|
|
10
15
|
import { knownModelIdsForProvider } from "../router";
|
|
11
16
|
import { findLiveProxy } from "../server/proxy-liveness";
|
|
@@ -91,7 +96,6 @@ function collectModels(config: OcxConfig, providerFilter?: string): ModelEntry[]
|
|
|
91
96
|
const seen = new Set<string>();
|
|
92
97
|
const contextWindows = prov.modelContextWindows ?? {};
|
|
93
98
|
const inputModalities = prov.modelInputModalities ?? {};
|
|
94
|
-
const reasoningEfforts = prov.modelReasoningEfforts ?? {};
|
|
95
99
|
const globalContext = prov.contextWindow ?? null;
|
|
96
100
|
|
|
97
101
|
const addModel = (model: string, isDefault: boolean) => {
|
|
@@ -107,7 +111,13 @@ function collectModels(config: OcxConfig, providerFilter?: string): ModelEntry[]
|
|
|
107
111
|
// an exact `gpt-oss:120b` entry that lists "image", and the proxy rejects the image.
|
|
108
112
|
const noVision = modelInList(prov.noVisionModels, model);
|
|
109
113
|
const modalities = noVision ? ["text"] : (modelRecordValue(inputModalities, model) ?? null);
|
|
110
|
-
|
|
114
|
+
// Same reason, for the ladder: `configuredReasoningEfforts` is what the catalog
|
|
115
|
+
// (`provider-fetch`) and the effort cap (`effort-policy`) resolve through, and it
|
|
116
|
+
// does three things this expression did not — it returns [] for a noReasoningModels
|
|
117
|
+
// match, drops levels Codex does not declare, and re-adds tiers the wire map proves
|
|
118
|
+
// the model emits. Restating two of its five lines here reported a ladder the proxy
|
|
119
|
+
// strips, and unsanitized junk as a supported level.
|
|
120
|
+
const efforts = configuredReasoningEfforts(prov, model) ?? null;
|
|
111
121
|
|
|
112
122
|
entries.push({
|
|
113
123
|
provider: provName,
|
package/src/cli/observe.ts
CHANGED
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
takeOption,
|
|
11
11
|
type RuntimeApiDeps,
|
|
12
12
|
} from "./runtime-api";
|
|
13
|
+
import { formatUsageReport } from "./usage-report";
|
|
14
|
+
import { USAGE_RANGES, USAGE_SURFACES } from "../usage/summary";
|
|
13
15
|
|
|
14
16
|
const USAGE = `Usage:
|
|
15
17
|
ocx observe logs [--provider <name>] [--model <id>] [--status <code>]
|
|
@@ -17,7 +19,8 @@ const USAGE = `Usage:
|
|
|
17
19
|
ocx logs explain <request-id> [--json]
|
|
18
20
|
ocx logs rebuild-index
|
|
19
21
|
ocx logs index-status
|
|
20
|
-
ocx observe usage [--range <7d|30d|all>] [--surface <all|codex|claude|grok>]
|
|
22
|
+
ocx observe usage [--range <today|1d|7d|30d|all>] [--surface <all|codex|claude|grok>]
|
|
23
|
+
[--provider <name>] [--model <id>] [--json]
|
|
21
24
|
ocx observe storage [codex-logs [status|protect|unprotect|repair|compact] [--mode <compat|quiet>]] [--json]
|
|
22
25
|
ocx observe memory [--json]
|
|
23
26
|
ocx observe debug [--json]
|
|
@@ -131,11 +134,23 @@ async function usage(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
131
134
|
const wantsJson = takeFlag(args, "--json");
|
|
132
135
|
const range = takeOption(args, "--range") ?? "30d";
|
|
133
136
|
const surface = takeOption(args, "--surface") ?? "all";
|
|
134
|
-
|
|
135
|
-
|
|
137
|
+
const provider = takeOption(args, "--provider");
|
|
138
|
+
const model = takeOption(args, "--model");
|
|
139
|
+
// `1d` is accepted here as well as server-side so the CLI does not reject an
|
|
140
|
+
// alias the API would have understood.
|
|
141
|
+
const ranges = [...USAGE_RANGES, "1d"];
|
|
142
|
+
if (!ranges.includes(range)) throw new CliUsageError(`--range must be one of ${USAGE_RANGES.join(", ")} (1d aliases today)`, USAGE);
|
|
143
|
+
if (!USAGE_SURFACES.includes(surface as (typeof USAGE_SURFACES)[number])) {
|
|
144
|
+
throw new CliUsageError(`--surface must be one of ${USAGE_SURFACES.join(", ")}`, USAGE);
|
|
145
|
+
}
|
|
136
146
|
rejectArgs(args, USAGE);
|
|
137
|
-
const result = await runtimeRequest(`/api/usage${query({ range, surface })}`, {}, deps);
|
|
138
|
-
|
|
147
|
+
const result = await runtimeRequest(`/api/usage${query({ range, surface, provider, model })}`, {}, deps);
|
|
148
|
+
// Built only when it will be printed: JavaScript evaluates arguments before
|
|
149
|
+
// the call, so passing formatUsageReport(...) inline would run the human
|
|
150
|
+
// renderer during --json and let its assumptions affect a path that is meant
|
|
151
|
+
// to bypass it entirely.
|
|
152
|
+
if (wantsJson) printData(result, true);
|
|
153
|
+
else printData(result, false, formatUsageReport(result as Parameters<typeof formatUsageReport>[0]));
|
|
139
154
|
}
|
|
140
155
|
|
|
141
156
|
async function simple(path: string, argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
package/src/cli/provider.ts
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* show <name> Show provider config details (secrets masked)
|
|
9
9
|
* set-default <name> Change the default provider
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
11
|
+
import { hasOwnProvider, isValidProviderName, loadConfig, sanitizeModelCostsForDisplay, saveConfig } from "../config";
|
|
12
|
+
import { apiKeyTransportConfigError } from "../config/provider-validation";
|
|
12
13
|
import { hasHelpFlag } from "./help";
|
|
13
14
|
import { getProviderRegistryEntry, PROVIDER_REGISTRY } from "../providers/registry";
|
|
14
15
|
import { providerConfigSeed } from "../providers/derive";
|
package/src/cli/registry.ts
CHANGED
|
@@ -36,8 +36,8 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
name: "recover-history",
|
|
39
|
-
usage: "ocx recover-history --legacy-openai",
|
|
40
|
-
summary: "
|
|
39
|
+
usage: "ocx recover-history --legacy-openai --yes",
|
|
40
|
+
summary: "Force all user-message opencodex rows to OpenAI for legacy recovery.",
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
name: "uninstall",
|
|
@@ -88,20 +88,22 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
88
88
|
{ name: "ensure", usage: "ocx ensure", summary: "Ensure the proxy is running and Codex config/cache are current." },
|
|
89
89
|
{
|
|
90
90
|
name: "sync",
|
|
91
|
-
usage: "ocx sync [--restart-codex]",
|
|
91
|
+
usage: "ocx sync [--restart-codex] [--restart-desktop-app]",
|
|
92
92
|
summary: "Fetch provider models and inject them into Codex config.",
|
|
93
93
|
details: [
|
|
94
94
|
"After writing the catalog, warns if long-lived Codex app-server processes are still running.",
|
|
95
95
|
"--restart-codex sends SIGTERM only to matching app-server / code-mode-host processes (may interrupt active turns).",
|
|
96
|
+
"--restart-desktop-app (Windows only, opt-in) fully restarts the Codex desktop app so its model picker re-reads the catalog. Never implied by --restart-codex: it ends live conversations.",
|
|
96
97
|
],
|
|
97
98
|
},
|
|
98
99
|
{
|
|
99
100
|
name: "sync-cache",
|
|
100
|
-
usage: "ocx sync-cache [--restart-codex]",
|
|
101
|
+
usage: "ocx sync-cache [--restart-codex] [--restart-desktop-app]",
|
|
101
102
|
summary: "Refresh Codex's model cache from the active catalog.",
|
|
102
103
|
details: [
|
|
103
104
|
"Warns when Codex app-server processes still hold an in-memory model list.",
|
|
104
105
|
"--restart-codex sends SIGTERM only to matching app-server / code-mode-host processes (may interrupt active turns).",
|
|
106
|
+
"--restart-desktop-app (Windows only, opt-in) fully restarts the Codex desktop app so its model picker re-reads the catalog. Never implied by --restart-codex: it ends live conversations.",
|
|
105
107
|
],
|
|
106
108
|
},
|
|
107
109
|
{ name: "status", usage: "ocx status", summary: "Check proxy server status." },
|
|
@@ -209,7 +211,7 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
209
211
|
{ name: "logs", usage: "ocx logs [filters] [--follow] [--json|--jsonl]", summary: "Alias of ocx observe logs." },
|
|
210
212
|
{
|
|
211
213
|
name: "usage",
|
|
212
|
-
usage: "ocx usage [--range <7d|30d|all>] [--surface <all|codex|claude|grok>] [--json]",
|
|
214
|
+
usage: "ocx usage [--range <today|1d|7d|30d|all>] [--surface <all|codex|claude|grok>] [--provider <name>] [--model <id>] [--json]",
|
|
213
215
|
summary: "Alias of ocx observe usage.",
|
|
214
216
|
},
|
|
215
217
|
{ name: "storage", usage: "ocx storage [--json]", summary: "Alias of ocx observe storage." },
|
package/src/cli/status.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { durableBunRuntime } from "../lib/bun-runtime";
|
|
2
|
-
import { codexAutoStartEnabled, getConfigPath,
|
|
2
|
+
import { codexAutoStartEnabled, getConfigPath, readConfigDiagnostics } from "../config";
|
|
3
|
+
import { getPidPath, readPid, readRuntimePort, type RuntimePortState } from "../config/process-state";
|
|
3
4
|
import { diagnoseCodexBundledPlugins, type CodexPluginsDiagnostic } from "../codex/plugins-doctor";
|
|
4
5
|
import { findLiveProxy, isOpencodexHealthz, probeHostname } from "../server/proxy-liveness";
|
|
5
6
|
import { directLocalHttpFetch } from "../server/direct-local-http";
|