@bitkyc08/opencodex 2.76.0 → 2.78.0-preview.20261005
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 +11 -10
- package/gui/dist/assets/App-CppMz-6w.js +54 -0
- package/gui/dist/assets/App-lCIslZ1L.css +1 -0
- package/gui/dist/assets/{Tray-DjNwwrWS.js → Tray-CJWQqtVv.js} +1 -1
- package/gui/dist/assets/index-DQt9srNi.js +94 -0
- package/gui/dist/assets/index-DmQB8D_R.css +1 -0
- package/gui/dist/assets/quota-summary-BMsyoZH2.js +1 -0
- package/gui/dist/index.html +4 -3
- package/package.json +1 -1
- package/src/adapters/anthropic/beta-allowlist.ts +19 -2
- package/src/adapters/anthropic/client-identity.ts +13 -4
- package/src/adapters/anthropic/native-client-preamble.ts +31 -0
- package/src/adapters/anthropic/passthrough.ts +103 -25
- package/src/adapters/antigravity-refusal.ts +28 -0
- package/src/adapters/cursor/protobuf-request.ts +6 -0
- package/src/adapters/cursor/request-builder.ts +3 -0
- package/src/adapters/cursor/structured-output.ts +13 -0
- package/src/adapters/cursor/types.ts +2 -0
- package/src/adapters/devin/cloud-direct/chat.ts +7 -2
- package/src/adapters/devin/trajectory.ts +49 -0
- package/src/adapters/devin.ts +13 -0
- package/src/adapters/ollama-native.ts +62 -19
- package/src/adapters/openai-responses/passthrough.ts +133 -103
- package/src/adapters/openai-responses/request-strips.ts +10 -4
- package/src/adapters/zed.ts +26 -4
- package/src/bridge/response-json.ts +14 -14
- package/src/bridge/sse.ts +15 -5
- package/src/chatgpt/app-server-shim/bundle-trust.ts +40 -10
- package/src/chatgpt/app-server-shim/gate-rewrite.ts +3 -0
- package/src/claude/agents-inject.ts +4 -43
- package/src/claude/gateway-cache.ts +16 -13
- package/src/claude/inbound-model-options.ts +9 -2
- package/src/claude/intercept/cli-catalog.ts +27 -16
- package/src/claude/intercept/client-hello.ts +103 -0
- package/src/claude/intercept/model-bindings.ts +3 -2
- package/src/claude/intercept/picker-bootstrap.ts +37 -16
- package/src/claude/intercept/picker-budget.ts +41 -0
- package/src/claude/intercept/picker-listener.ts +279 -66
- package/src/claude/outbound.ts +2 -0
- package/src/claude/subagent-force-status.ts +53 -0
- package/src/claude/subagent-model.ts +93 -0
- package/src/cli/access-audio-input.ts +84 -0
- package/src/cli/access-audio-live.ts +99 -0
- package/src/cli/access-audio.ts +123 -0
- package/src/cli/access-data-client.ts +127 -0
- package/src/cli/access-data-plane.ts +104 -0
- package/src/cli/access-data-response.ts +76 -0
- package/src/cli/access.ts +65 -2
- package/src/cli/account-api.ts +33 -6
- package/src/cli/account-auth.ts +111 -42
- package/src/cli/account-key-quota.ts +118 -0
- package/src/cli/account-main.ts +11 -4
- package/src/cli/account-next-actions.ts +63 -0
- package/src/cli/account-policy-dto.ts +68 -0
- package/src/cli/account-policy.ts +167 -0
- package/src/cli/account-target.ts +8 -0
- package/src/cli/account.ts +37 -9
- package/src/cli/agent-runtime-settings.ts +130 -0
- package/src/cli/agent-settings.ts +101 -0
- package/src/cli/agent.ts +29 -2
- package/src/cli/alias.ts +7 -2
- package/src/cli/aside-profiles.ts +2 -2
- package/src/cli/capabilities-access-remote.ts +147 -0
- package/src/cli/capabilities-accounts.ts +195 -0
- package/src/cli/capabilities-agents-routing.ts +414 -0
- package/src/cli/capabilities-base.ts +783 -0
- package/src/cli/capabilities-command.ts +1 -0
- package/src/cli/capabilities-integrations.ts +264 -0
- package/src/cli/capabilities-lab.ts +214 -0
- package/src/cli/capabilities-observe-system.ts +662 -0
- package/src/cli/capabilities-provider-models.ts +302 -0
- package/src/cli/capabilities.ts +30 -1190
- package/src/cli/capability-types.ts +54 -0
- package/src/cli/catalog-command-result.ts +57 -0
- package/src/cli/chatgpt-command.ts +35 -9
- package/src/cli/claude-agent-startup-sync.ts +17 -0
- package/src/cli/claude-desktop-profile.ts +100 -0
- package/src/cli/claude-desktop.ts +39 -6
- package/src/cli/claude.ts +22 -6
- package/src/cli/codex-shim-autorestore.ts +2 -1
- package/src/cli/codex-shim-readiness.ts +109 -0
- package/src/cli/combo-input.ts +88 -0
- package/src/cli/combo-result.ts +52 -0
- package/src/cli/combo-stats.ts +123 -0
- package/src/cli/combo.ts +37 -14
- package/src/cli/companion-timeline.ts +142 -0
- package/src/cli/companion-usage.ts +146 -0
- package/src/cli/companion.ts +12 -3
- package/src/cli/config-command.ts +24 -8
- package/src/cli/connect.ts +19 -0
- package/src/cli/dispatch.ts +55 -82
- package/src/cli/doctor.ts +22 -4
- package/src/cli/export-command.ts +20 -9
- package/src/cli/gui.ts +8 -3
- package/src/cli/help-catalog.ts +101 -0
- package/src/cli/help-models-context.ts +17 -0
- package/src/cli/help-navigation.ts +50 -0
- package/src/cli/help-recovery.ts +66 -0
- package/src/cli/help.ts +69 -11
- package/src/cli/index.ts +38 -27
- package/src/cli/injection-follow.ts +35 -0
- package/src/cli/inspect.ts +5 -0
- package/src/cli/integration-aside-sync.ts +69 -0
- package/src/cli/integration-cursor.ts +106 -0
- package/src/cli/integration-input.ts +60 -0
- package/src/cli/integration-journal.ts +49 -0
- package/src/cli/integration-plan-dto.ts +156 -0
- package/src/cli/integration-preview.ts +215 -0
- package/src/cli/integrations.ts +24 -18
- package/src/cli/json-input.ts +134 -0
- package/src/cli/link.ts +32 -4
- package/src/cli/local-sync-result.ts +41 -0
- package/src/cli/log-follow.ts +84 -0
- package/src/cli/log-view-filter.ts +146 -0
- package/src/cli/logout-command.ts +44 -0
- package/src/cli/model-picker-ordering.ts +137 -0
- package/src/cli/models-custom-input.ts +107 -0
- package/src/cli/models-custom-runtime.ts +112 -0
- package/src/cli/models-order.ts +194 -0
- package/src/cli/models-runtime-subcommands.ts +2 -0
- package/src/cli/models-runtime.ts +9 -1
- package/src/cli/models.ts +66 -81
- package/src/cli/observe-stream.ts +142 -0
- package/src/cli/observe.ts +75 -34
- package/src/cli/opencode.ts +54 -10
- package/src/cli/provider-batch.ts +77 -0
- package/src/cli/provider-lifecycle-runtime.ts +170 -0
- package/src/cli/provider-result.ts +124 -0
- package/src/cli/provider-runtime.ts +15 -12
- package/src/cli/provider-settings.ts +143 -0
- package/src/cli/provider.ts +116 -64
- package/src/cli/registry.ts +90 -24
- package/src/cli/remote-workspace-hub.ts +83 -0
- package/src/cli/remote-workspace.ts +6 -1
- package/src/cli/restart-failure.ts +27 -0
- package/src/cli/root.ts +42 -9
- package/src/cli/route-policy-write.ts +137 -0
- package/src/cli/route-policy.ts +8 -0
- package/src/cli/runtime-api.ts +57 -80
- package/src/cli/secret-args.ts +149 -0
- package/src/cli/settings-result.ts +30 -0
- package/src/cli/storage.ts +77 -7
- package/src/cli/system-command.ts +60 -2
- package/src/cli/system-restart-client.ts +20 -3
- package/src/cli/system-settings-parity.ts +83 -0
- package/src/cli/tray-proxy.ts +3 -2
- package/src/cli/update-restart-candidate.ts +21 -0
- package/src/cli/update-restart-child.ts +81 -0
- package/src/cli/update-restart-home.ts +38 -0
- package/src/cli/update-restart-transport.ts +193 -0
- package/src/cli/update-restart.ts +260 -0
- package/src/cli/usage-model-search.ts +58 -0
- package/src/cli/usage-report.ts +13 -5
- package/src/cli/v2-input.ts +66 -0
- package/src/cli/v2-local-output.ts +37 -0
- package/src/cli/v2-runtime.ts +90 -0
- package/src/cli/v2.ts +102 -144
- package/src/clients/config-export/contracts.ts +100 -0
- package/src/clients/config-export/droid.ts +54 -2
- package/src/clients/config-export/dsh.ts +21 -0
- package/src/clients/config-export/kilo.ts +12 -2
- package/src/clients/config-export/model-metadata.ts +10 -4
- package/src/clients/config-export/reasoning-metadata.ts +47 -0
- package/src/clients/config-export.ts +53 -67
- package/src/clients/omo-role-models.ts +17 -3
- package/src/codex/account-credit-use.ts +4 -2
- package/src/codex/account-runtime-state.ts +21 -0
- package/src/codex/account-store.ts +11 -47
- package/src/codex/account-usability.ts +11 -8
- package/src/codex/auth-api/account-list.ts +73 -63
- package/src/codex/auth-api/main-account-probe.ts +25 -12
- package/src/codex/auth-api/pool-mode-gate.ts +6 -9
- package/src/codex/auth-api/pool-quota-probe.ts +2 -4
- package/src/codex/auth-api/reset-credit-service.ts +3 -2
- package/src/codex/auth-context.ts +30 -11
- package/src/codex/catalog/aggregation.ts +3 -0
- package/src/codex/catalog/build-entries.ts +1 -1
- package/src/codex/catalog/bundled.ts +9 -2
- package/src/codex/catalog/heal-observation.ts +66 -0
- package/src/codex/catalog/model-hints.ts +2 -1
- package/src/codex/catalog/model-visibility.ts +6 -3
- package/src/codex/catalog/parsing.ts +2 -0
- package/src/codex/catalog/provider-models.ts +13 -4
- package/src/codex/catalog/publication-observer.ts +19 -0
- package/src/codex/catalog/remote.ts +13 -15
- package/src/codex/catalog/restore.ts +19 -6
- package/src/codex/catalog/retained-sync.ts +60 -11
- package/src/codex/catalog/routed-removal.ts +116 -0
- package/src/codex/catalog/write-audit-contract.ts +21 -0
- package/src/codex/catalog/write-audit.ts +171 -0
- package/src/codex/catalog-self-heal.ts +284 -0
- package/src/codex/catalog-write-serialization.ts +71 -7
- package/src/codex/chatgpt-refresh-failure.ts +56 -0
- package/src/codex/codex-home-owner.ts +124 -0
- package/src/codex/convergence.ts +57 -16
- package/src/codex/features.ts +8 -1
- package/src/codex/inject/restore.ts +46 -4
- package/src/codex/inject.ts +11 -1
- package/src/codex/internal/catalog-writer.ts +120 -13
- package/src/codex/journal.ts +49 -13
- package/src/codex/main-account.ts +144 -11
- package/src/codex/management-convergence.ts +3 -0
- package/src/codex/quota-refresh-outcome.ts +17 -2
- package/src/codex/quota-types.ts +36 -1
- package/src/codex/quota.ts +67 -16
- package/src/codex/refresh.ts +20 -9
- package/src/codex/routing/cooldown-math.ts +10 -4
- package/src/codex/routing/selection.ts +11 -4
- package/src/codex/routing.ts +9 -9
- package/src/codex/runtime.ts +7 -1
- package/src/codex/shim-diagnostics.ts +80 -0
- package/src/codex/shim-fingerprint.ts +7 -2
- package/src/codex/shim-migration.ts +153 -0
- package/src/codex/shim-overlay.ts +509 -0
- package/src/codex/shim-path-resolution.ts +145 -0
- package/src/codex/shim-probe.ts +4 -3
- package/src/codex/shim-state-file.ts +93 -7
- package/src/codex/shim.ts +54 -107
- package/src/codex/subagent-model-fallback.ts +2 -1
- package/src/codex/sync.ts +24 -4
- package/src/combos/failover.ts +101 -1
- package/src/combos/index.ts +1 -0
- package/src/combos/jev.ts +13 -3
- package/src/combos/resolve.ts +12 -0
- package/src/config/atomic-write.ts +13 -8
- package/src/config/diagnostics.ts +23 -1
- package/src/config/load-degrade.ts +7 -0
- package/src/config/provider-validation.ts +3 -0
- package/src/config/schema/chatgpt-desktop.ts +22 -0
- package/src/config/schema/compaction-triggers.ts +8 -0
- package/src/config/schema/config-schema.ts +43 -14
- package/src/config/schema/leaf-validators.ts +8 -3
- package/src/config/subagent-models.ts +12 -0
- package/src/generated/compatibility-version.json +645 -237
- package/src/grok/session-identity.ts +33 -0
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/merge.ts +13 -5
- package/src/integrations/mutation-plan.ts +50 -5
- package/src/integrations/omp-yaml-source.ts +181 -1
- package/src/integrations/ownership-policy.ts +66 -0
- package/src/integrations/registry.ts +50 -1
- package/src/integrations/state.ts +106 -19
- package/src/integrations/target.ts +25 -2
- package/src/integrations/writer.ts +107 -35
- package/src/lib/gui-pair-capability.ts +17 -0
- package/src/lib/package-tree-integrity.ts +42 -1
- package/src/lib/provider-client-headers.ts +62 -0
- package/src/lib/proxy-env.ts +3 -0
- package/src/lib/test-home-guard.ts +2 -1
- package/src/oauth/anthropic-account-refusal.ts +19 -8
- package/src/oauth/anthropic-continuity.ts +65 -0
- package/src/oauth/anthropic-identity.ts +75 -0
- package/src/oauth/anthropic-routing.ts +20 -15
- package/src/oauth/anthropic.ts +34 -3
- package/src/oauth/generic-account-failover.ts +21 -0
- package/src/oauth/health.ts +32 -5
- package/src/oauth/index.ts +42 -35
- package/src/oauth/local-token-detect.ts +7 -0
- package/src/oauth/pool-settings-capability.ts +9 -2
- package/src/oauth/refresh-policy.ts +2 -0
- package/src/oauth/store.ts +55 -6
- package/src/oauth/types.ts +9 -0
- package/src/protocols/encoders/messages.ts +4 -3
- package/src/protocols/plan-snapshot.ts +1 -1
- package/src/protocols/settings.ts +28 -4
- package/src/providers/antigravity-effort-families.ts +75 -0
- package/src/providers/antigravity-models.ts +54 -16
- package/src/providers/antigravity-wire-snapshot.ts +66 -0
- package/src/providers/derive.ts +2 -0
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/new-model-policy.ts +48 -10
- package/src/providers/registry/entries-extended.ts +3 -0
- package/src/providers/registry/model-ids.ts +2 -0
- package/src/providers/registry/model-seeds.ts +5 -0
- package/src/providers/registry/types.ts +4 -0
- package/src/responses/compaction.ts +19 -0
- package/src/router.ts +6 -0
- package/src/server/auth-cors.ts +9 -0
- package/src/server/caller-session-identity.ts +32 -0
- package/src/server/chat-completions.ts +10 -0
- package/src/server/claude-messages.ts +29 -15
- package/src/server/droid-reasoning-default.ts +50 -0
- package/src/server/github-copilot-responses-repair.ts +3 -6
- package/src/server/grok-responses-control-frame.ts +2 -2
- package/src/server/grok-responses-snapshot-repair.ts +4 -5
- package/src/server/gui-session.ts +33 -11
- package/src/server/index/claude-intercept-lifecycle.ts +4 -0
- package/src/server/index/serve-options.ts +13 -7
- package/src/server/index.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +12 -134
- package/src/server/management/config-routes.ts +1 -1
- package/src/server/management/context.ts +8 -1
- package/src/server/management/cursor-integration-routes.ts +3 -3
- package/src/server/management/integration-routes.ts +69 -11
- package/src/server/management/link-routes.ts +20 -13
- package/src/server/management/model-routes.ts +3 -3
- package/src/server/management/model-row-export-metadata.ts +150 -0
- package/src/server/management/model-rows.ts +148 -9
- package/src/server/management/native-integration-routes.ts +2 -2
- package/src/server/management/oauth-account-routes.ts +96 -30
- package/src/server/management/route-registry.ts +29 -27
- package/src/server/management/shared.ts +19 -5
- package/src/server/management/subagent-model-routes.ts +214 -0
- package/src/server/messages-native-eligibility.ts +3 -6
- package/src/server/messages-native-oauth.ts +82 -35
- package/src/server/messages-native.ts +78 -16
- package/src/server/relay.ts +14 -44
- package/src/server/request-log-generation-window.ts +38 -0
- package/src/server/request-log.ts +13 -0
- package/src/server/responses/adapter-dispatch.ts +152 -28
- package/src/server/responses/codex-auth-error.ts +10 -2
- package/src/server/responses/combo-stream-preflight.ts +29 -3
- package/src/server/responses/compaction-routing.ts +10 -1
- package/src/server/responses/core-auth.ts +67 -30
- package/src/server/responses/core-codex-account.ts +6 -3
- package/src/server/responses/core-combo-failure.ts +5 -1
- package/src/server/responses/core-combo-native.ts +7 -1
- package/src/server/responses/core-combo.ts +64 -7
- package/src/server/responses/core-normalize.ts +7 -0
- package/src/server/responses/core-options.ts +5 -0
- package/src/server/responses/fetch-helpers.ts +9 -0
- package/src/server/responses/passthrough-delivery.ts +1 -1
- package/src/server/responses/policy-fallback.ts +1 -1
- package/src/server/responses/request-prepare.ts +5 -0
- package/src/server/responses/request-spend.ts +30 -30
- package/src/server/responses-custom-tool-repair.ts +2 -2
- package/src/server/responses-hosted-image-display.ts +70 -9
- package/src/server/responses-terminal-repair.ts +15 -2
- package/src/server/sse-frame-buffer.ts +38 -36
- package/src/server/sse-payload-rewrite.ts +78 -49
- package/src/server/ws-bridge.ts +1 -11
- package/src/service/cli.ts +6 -1
- package/src/service/ownership-mutation-lease.d.mts +26 -0
- package/src/service/ownership-mutation-lease.mjs +76 -1
- package/src/service/windows-taskxml.ts +1 -2
- package/src/service-manager-probe.ts +36 -2
- package/src/types/config.ts +6 -0
- package/src/types/provider.ts +28 -10
- package/src/update/index.ts +2 -1
- package/src/update/npm-invocation.mjs +35 -3
- package/src/update/proxy-liveness-probe.mjs +38 -8
- package/src/update/update-failure-guidance.d.mts +6 -0
- package/src/update/update-failure-guidance.mjs +30 -5
- package/src/usage/expected-prices.ts +9 -0
- package/src/usage/log.ts +7 -0
- package/src/usage/summary.ts +82 -1
- package/src/web-search/loop.ts +11 -8
- package/src/web-search/run-turn-loop.ts +10 -7
- package/gui/dist/assets/App-CddorHdA.js +0 -51
- package/gui/dist/assets/App-DMudtISW.css +0 -1
- package/gui/dist/assets/index-Btze1I3T.css +0 -1
- package/gui/dist/assets/index-CvnY7E6i.js +0 -86
- package/gui/dist/assets/quota-summary-B_AWpHfP.js +0 -1
package/src/cli/usage-report.ts
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* (as in `formatAccountTable`), plain text, no ANSI colour.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import type { UsageModelView } from "./usage-model-search";
|
|
16
|
+
|
|
15
17
|
interface CostRow {
|
|
16
18
|
provider: string;
|
|
17
19
|
model?: string;
|
|
@@ -43,7 +45,7 @@ interface UsageReportInput {
|
|
|
43
45
|
models?: CostRow[];
|
|
44
46
|
providers?: CostRow[];
|
|
45
47
|
days?: { date: string; requests: number; totalTokens: number; estimatedCostUsd?: number }[];
|
|
46
|
-
filter?: { provider: string | null; model: string | null; matched: boolean; comboOverlap: boolean };
|
|
48
|
+
filter?: { apiKeyId?: string | null; provider: string | null; model: string | null; matched: boolean; comboOverlap: boolean };
|
|
47
49
|
/**
|
|
48
50
|
* Per-account totals the API already returns and the CLI discarded (#2700).
|
|
49
51
|
*
|
|
@@ -106,11 +108,12 @@ function describeScope(data: UsageReportInput): string {
|
|
|
106
108
|
const parts = [`Usage — ${interval}`];
|
|
107
109
|
if (data.surface && data.surface !== "all") parts.push(`surface=${data.surface}`);
|
|
108
110
|
if (data.filter?.provider) parts.push(`provider=${data.filter.provider}`);
|
|
111
|
+
if (data.filter?.apiKeyId) parts.push(`api-key-id=${data.filter.apiKeyId}`);
|
|
109
112
|
if (data.filter?.model) parts.push(`model=${data.filter.model}`);
|
|
110
113
|
return terminalText(parts.join(", "));
|
|
111
114
|
}
|
|
112
115
|
|
|
113
|
-
export function formatUsageReport(data: UsageReportInput): string[] {
|
|
116
|
+
export function formatUsageReport(data: UsageReportInput, options?: { modelView?: UsageModelView }): string[] {
|
|
114
117
|
const summary = data.summary ?? {};
|
|
115
118
|
const lines: string[] = [describeScope(data), ""];
|
|
116
119
|
if (data.source === "hub" && data.scope === "client") {
|
|
@@ -119,9 +122,14 @@ export function formatUsageReport(data: UsageReportInput): string[] {
|
|
|
119
122
|
if (data.usageIncomplete === true) {
|
|
120
123
|
lines.push("WARNING: Usage is incomplete; some records could not be included. Totals and rankings reflect readable records only.", "");
|
|
121
124
|
}
|
|
125
|
+
const view = options?.modelView;
|
|
126
|
+
if (view) {
|
|
127
|
+
lines.push(`Model search ${JSON.stringify(view.query)}: ${view.returnedModelCount} of ${view.matchedModelCount} matching model rows (limit ${view.limit}). Report totals are unchanged.`, "");
|
|
128
|
+
if (view.matchedModelCount === 0) lines.push("No model rows match this search. Change --search or use --search= to show the model view.", "");
|
|
129
|
+
}
|
|
122
130
|
|
|
123
131
|
if (data.filter && !data.filter.matched) {
|
|
124
|
-
const what = [data.filter.provider && `provider "${data.filter.provider}"`, data.filter.model && `model "${data.filter.model}"`]
|
|
132
|
+
const what = [data.filter.apiKeyId && `API key "${data.filter.apiKeyId}"`, data.filter.provider && `provider "${data.filter.provider}"`, data.filter.model && `model "${data.filter.model}"`]
|
|
125
133
|
.filter(Boolean).join(" and ");
|
|
126
134
|
lines.push(data.usageIncomplete === true
|
|
127
135
|
? `No matching readable usage records for ${terminalText(what)} in this range; skipped records may contain matches.`
|
|
@@ -181,10 +189,10 @@ export function formatUsageReport(data: UsageReportInput): string[] {
|
|
|
181
189
|
));
|
|
182
190
|
}
|
|
183
191
|
|
|
184
|
-
const models = (data.models ?? []).filter(row => row.requests > 0);
|
|
192
|
+
const models = view ? (data.models ?? []) : (data.models ?? []).filter(row => row.requests > 0);
|
|
185
193
|
if (models.length > 0) {
|
|
186
194
|
lines.push("");
|
|
187
|
-
const shown = models.slice(0, MAX_MODEL_ROWS);
|
|
195
|
+
const shown = models.slice(0, view?.limit ?? MAX_MODEL_ROWS);
|
|
188
196
|
lines.push(...table(
|
|
189
197
|
["MODEL", "PROVIDER", "REQUESTS", "TOKENS", "EST. COST"],
|
|
190
198
|
shown.map(row => [row.model ?? "-", row.provider, count(row.requests), count(row.totalTokens), usd(row.estimatedCostUsd)]),
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { CliUsageError } from "./runtime-api";
|
|
2
|
+
|
|
3
|
+
type V2Controls = { json: boolean; live: boolean; acknowledge: boolean };
|
|
4
|
+
export type V2ParsedCommand = V2Controls & (
|
|
5
|
+
| { verb: "status" | "on" | "off" }
|
|
6
|
+
| { verb: "mode"; value: "v1" | "default" | "v2" }
|
|
7
|
+
| { verb: "keep-native-v1"; value: boolean }
|
|
8
|
+
| { verb: "threads"; value: number }
|
|
9
|
+
| { verb: "mode-hint"; value: string | null }
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
export const V2_USAGE = "Usage: ocx v2 [status|on|off|mode <v1|default|v2>|keep-native-v1 <on|off>|threads <n>|mode-hint <text|--clear>] [--live] [--json]\nFor literal reserved hint text: ocx v2 mode-hint [--live] [--json] -- <text>\nOnly live mode accepts --acknowledge-surface-advisory.";
|
|
13
|
+
|
|
14
|
+
/** Parse all controls before touching native/config state. A terminator protects literal hints. */
|
|
15
|
+
export function parseV2Command(argv: readonly string[]): V2ParsedCommand {
|
|
16
|
+
const boundary = argv.indexOf("--");
|
|
17
|
+
const prefix = boundary < 0 ? argv : argv.slice(0, boundary);
|
|
18
|
+
const suffix = boundary < 0 ? [] : argv.slice(boundary + 1);
|
|
19
|
+
const controls: V2Controls = { json: false, live: false, acknowledge: false };
|
|
20
|
+
const operands: string[] = [];
|
|
21
|
+
for (const arg of prefix) {
|
|
22
|
+
const flag = arg === "--json" ? "json" : arg === "--live" ? "live"
|
|
23
|
+
: arg === "--acknowledge-surface-advisory" ? "acknowledge" : undefined;
|
|
24
|
+
if (["--json=", "--live=", "--acknowledge-surface-advisory="].some(prefix => arg.startsWith(prefix))) {
|
|
25
|
+
throw new CliUsageError("v2: control flags do not take values; use -- for literal hint text", V2_USAGE);
|
|
26
|
+
}
|
|
27
|
+
if (flag) {
|
|
28
|
+
if (controls[flag]) throw new CliUsageError("v2: duplicate control flag", V2_USAGE);
|
|
29
|
+
controls[flag] = true;
|
|
30
|
+
} else operands.push(arg);
|
|
31
|
+
}
|
|
32
|
+
const verb = (operands.shift() ?? "status").trim().toLowerCase();
|
|
33
|
+
const invalid = (message: string): never => { throw new CliUsageError(message, V2_USAGE); };
|
|
34
|
+
if (controls.acknowledge && (!controls.live || verb !== "mode")) {
|
|
35
|
+
invalid("v2: advisory acknowledgment requires explicit live mode");
|
|
36
|
+
}
|
|
37
|
+
if (boundary >= 0 && (verb !== "mode-hint" || operands.length !== 0 || suffix.length !== 1)) {
|
|
38
|
+
invalid("v2: the terminator requires exactly one literal mode-hint operand");
|
|
39
|
+
}
|
|
40
|
+
if (verb === "status" || verb === "on" || verb === "off") {
|
|
41
|
+
if (operands.length) invalid("v2: unexpected operands or flags");
|
|
42
|
+
return { ...controls, verb };
|
|
43
|
+
}
|
|
44
|
+
const values = boundary >= 0 ? suffix : operands;
|
|
45
|
+
if (values.length !== 1) invalid(verb === "keep-native-v1" ? "v2 keep-native-v1: expected on|off" : "v2: exactly one value is required");
|
|
46
|
+
const raw = values[0]!;
|
|
47
|
+
if (verb === "mode-hint") {
|
|
48
|
+
if (!raw.trim()) invalid("v2 mode-hint: pass nonblank text, or --clear to unset it");
|
|
49
|
+
// Legacy unreserved hyphen-leading hint strings remain ordinary content.
|
|
50
|
+
return { ...controls, verb, value: boundary < 0 && raw === "--clear" ? null : raw };
|
|
51
|
+
}
|
|
52
|
+
const value = raw.trim().toLowerCase();
|
|
53
|
+
if (verb === "mode" && (value === "v1" || value === "default" || value === "v2")) {
|
|
54
|
+
return { ...controls, verb, value };
|
|
55
|
+
}
|
|
56
|
+
if (verb === "keep-native-v1" && (value === "on" || value === "off")) {
|
|
57
|
+
return { ...controls, verb, value: value === "on" };
|
|
58
|
+
}
|
|
59
|
+
if (verb === "keep-native-v1") invalid("v2 keep-native-v1: expected on|off");
|
|
60
|
+
if (verb === "threads") {
|
|
61
|
+
const count = Number(value);
|
|
62
|
+
if (Number.isSafeInteger(count) && count >= 1) return { ...controls, verb, value: count };
|
|
63
|
+
invalid("v2 threads: pass a safe integer >= 1");
|
|
64
|
+
}
|
|
65
|
+
return invalid("v2: invalid command or value");
|
|
66
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { loadConfig } from "../config";
|
|
2
|
+
import { multiAgentSurfaceAdvisory, resolveMultiAgentMode } from "../config/multi-agent-surface";
|
|
3
|
+
import { getAgentsEnabled, getAgentsMaxDepth, getLogicalMaxThreads, getMultiAgentModeHintText,
|
|
4
|
+
getSubagentDeveloperInstructions, hasAgentsMaxThreads, isMultiAgentV2Enabled } from "../codex/features";
|
|
5
|
+
import { MULTI_AGENT_MODE_HINT_RECOMMENDATION } from "../codex/multi-agent-mode-policy";
|
|
6
|
+
import type { CodexSyncResult } from "../codex/sync";
|
|
7
|
+
import { projectLocalSyncResult, type LocalSyncResult } from "./local-sync-result";
|
|
8
|
+
|
|
9
|
+
/** Rebuild local state from actual post-write readers; no paths, credentials, or logs. */
|
|
10
|
+
export function localV2State(isEnabled = isMultiAgentV2Enabled, hasMaxThreads = hasAgentsMaxThreads) {
|
|
11
|
+
const config = loadConfig();
|
|
12
|
+
const enabled = isEnabled();
|
|
13
|
+
return { enabled, agentsMaxThreadsConflict: enabled && hasMaxThreads(),
|
|
14
|
+
multiAgentMode: resolveMultiAgentMode(config), keepNativeChatGptOnV1: config.keepNativeChatGptOnV1 === true,
|
|
15
|
+
multiAgentSurfaceAdvisory: multiAgentSurfaceAdvisory(config), maxConcurrentThreadsPerSession: getLogicalMaxThreads(),
|
|
16
|
+
agentsEnabled: getAgentsEnabled(), agentsMaxDepth: getAgentsMaxDepth(),
|
|
17
|
+
agentsMaxDepthAppliesWhenV2Disabled: !enabled, subagentDeveloperInstructions: getSubagentDeveloperInstructions(),
|
|
18
|
+
multiAgentModeHintText: getMultiAgentModeHintText(), multiAgentModeHintRecommendation: MULTI_AGENT_MODE_HINT_RECOMMENDATION };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type V2LocalSync = LocalSyncResult | { status: "unverified"; ok: false };
|
|
22
|
+
|
|
23
|
+
/** The existing injected sync contract returns unknown. Validate before the typed shared projection. */
|
|
24
|
+
export function v2LocalSyncResult(value: unknown): V2LocalSync {
|
|
25
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return { status: "unverified", ok: false };
|
|
26
|
+
const source = value as Record<string, unknown>;
|
|
27
|
+
if (typeof source.status !== "string" || !["applied", "catalog-only", "skipped", "refused"].includes(source.status)
|
|
28
|
+
|| typeof source.ok !== "boolean" || typeof source.catalogExists !== "boolean"
|
|
29
|
+
|| typeof source.catalogWritten !== "boolean" || typeof source.cacheSynced !== "boolean"
|
|
30
|
+
|| typeof source.added !== "number" || !Number.isSafeInteger(source.added) || source.added < 0
|
|
31
|
+
|| (source.catalogPath !== null && typeof source.catalogPath !== "string") || typeof source.message !== "string"
|
|
32
|
+
|| (source.refreshOutcome !== undefined && source.refreshOutcome !== "committed" && source.refreshOutcome !== "refused")) {
|
|
33
|
+
return { status: "unverified", ok: false };
|
|
34
|
+
}
|
|
35
|
+
// The checked fields are the complete mandatory backend contract. Private text is never emitted.
|
|
36
|
+
return projectLocalSyncResult(source as unknown as CodexSyncResult);
|
|
37
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { SUBAGENT_SURFACE_GUIDE_URL } from "../config/multi-agent-surface";
|
|
2
|
+
import { printCatalogResult, runCatalogAction } from "./catalog-command-result";
|
|
3
|
+
import { serializeManagementJson } from "./json-input";
|
|
4
|
+
import { RuntimeApiError, printData, runtimeBaseUrl, runtimeRequest, type RuntimeApiDeps } from "./runtime-api";
|
|
5
|
+
import type { V2ParsedCommand } from "./v2-input";
|
|
6
|
+
|
|
7
|
+
function record(value: unknown): value is Record<string, unknown> {
|
|
8
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
function mode(value: unknown): value is "v1" | "default" | "v2" {
|
|
11
|
+
return value === "v1" || value === "default" || value === "v2";
|
|
12
|
+
}
|
|
13
|
+
function nullableString(value: unknown): value is string | null { return value === null || typeof value === "string"; }
|
|
14
|
+
|
|
15
|
+
/** Allowlisted task fields only: neither helper errors nor arbitrary response spreads. */
|
|
16
|
+
function stateDto(value: unknown): Record<string, unknown> {
|
|
17
|
+
if (!record(value) || !mode(value.multiAgentMode)) throw new Error("Invalid v2 state");
|
|
18
|
+
const result: Record<string, unknown> = { multiAgentMode: value.multiAgentMode };
|
|
19
|
+
for (const key of ["enabled", "agentsMaxThreadsConflict", "keepNativeChatGptOnV1", "agentsMaxDepthAppliesWhenV2Disabled"] as const) {
|
|
20
|
+
if (typeof value[key] !== "boolean") throw new Error("Invalid v2 boolean");
|
|
21
|
+
result[key] = value[key];
|
|
22
|
+
}
|
|
23
|
+
if (value.agentsEnabled !== null && typeof value.agentsEnabled !== "boolean") throw new Error("Invalid agents state");
|
|
24
|
+
if (value.maxConcurrentThreadsPerSession !== null && !(typeof value.maxConcurrentThreadsPerSession === "number"
|
|
25
|
+
&& Number.isSafeInteger(value.maxConcurrentThreadsPerSession) && value.maxConcurrentThreadsPerSession >= 1)) throw new Error("Invalid thread state");
|
|
26
|
+
if (value.agentsMaxDepth !== null && !(typeof value.agentsMaxDepth === "number" && Number.isInteger(value.agentsMaxDepth)
|
|
27
|
+
&& value.agentsMaxDepth >= -2147483648 && value.agentsMaxDepth <= 2147483647)) throw new Error("Invalid depth state");
|
|
28
|
+
for (const key of ["subagentDeveloperInstructions", "multiAgentModeHintText"] as const) {
|
|
29
|
+
if (!nullableString(value[key])) throw new Error("Invalid text state");
|
|
30
|
+
result[key] = value[key];
|
|
31
|
+
}
|
|
32
|
+
const recommendation = value.multiAgentModeHintRecommendation;
|
|
33
|
+
if (!record(recommendation) || typeof recommendation.revision !== "string" || typeof recommendation.text !== "string") throw new Error("Invalid hint recommendation");
|
|
34
|
+
const advisory = value.multiAgentSurfaceAdvisory;
|
|
35
|
+
if (!record(advisory) || typeof advisory.required !== "boolean" || advisory.mode !== value.multiAgentMode
|
|
36
|
+
|| advisory.recommended !== "v1" || typeof advisory.version !== "number" || !Number.isSafeInteger(advisory.version)
|
|
37
|
+
|| advisory.version < 1 || advisory.docsUrl !== SUBAGENT_SURFACE_GUIDE_URL) throw new Error("Invalid surface advisory");
|
|
38
|
+
return { ...result, agentsEnabled: value.agentsEnabled, agentsMaxDepth: value.agentsMaxDepth,
|
|
39
|
+
maxConcurrentThreadsPerSession: value.maxConcurrentThreadsPerSession,
|
|
40
|
+
multiAgentModeHintRecommendation: { revision: recommendation.revision, text: recommendation.text },
|
|
41
|
+
multiAgentSurfaceAdvisory: { required: advisory.required, mode: advisory.mode, recommended: "v1",
|
|
42
|
+
version: advisory.version, docsUrl: advisory.docsUrl } };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function bodyFor(parsed: V2ParsedCommand): Record<string, unknown> {
|
|
46
|
+
switch (parsed.verb) {
|
|
47
|
+
case "on": case "off": return { enabled: parsed.verb === "on" };
|
|
48
|
+
case "mode": return { multiAgentMode: parsed.value,
|
|
49
|
+
...(parsed.acknowledge ? { multiAgentSurfaceAdvisoryAcknowledged: true } : {}) };
|
|
50
|
+
case "keep-native-v1": return { keepNativeChatGptOnV1: parsed.value };
|
|
51
|
+
case "threads": return { maxConcurrentThreadsPerSession: parsed.value };
|
|
52
|
+
case "mode-hint": return { multiAgentModeHintText: parsed.value };
|
|
53
|
+
case "status": return {};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function handleV2RuntimeCommand(parsed: V2ParsedCommand, deps: RuntimeApiDeps = {}): Promise<number> {
|
|
58
|
+
return runCatalogAction(async () => {
|
|
59
|
+
const read = parsed.verb === "status";
|
|
60
|
+
const body = read ? undefined : serializeManagementJson(bodyFor(parsed));
|
|
61
|
+
const pinned = { ...deps, baseUrl: await runtimeBaseUrl(deps) };
|
|
62
|
+
let response: unknown;
|
|
63
|
+
try {
|
|
64
|
+
response = await runtimeRequest("/api/v2", { method: read ? "GET" : "PUT", redirect: "error", ...(body ? { body } : {}) }, pinned);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if (error instanceof RuntimeApiError && error.status === 502) {
|
|
67
|
+
console.error("Error: Native V2 settings may be partially applied. Read ocx v2 status --live before retrying; no rollback is implied.");
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
const state = stateDto(response);
|
|
73
|
+
const lines = [`multi_agent_v2: ${state.enabled ? "ON" : "OFF"}`, `multi_agent_mode: ${state.multiAgentMode}`,
|
|
74
|
+
`keep_native_chatgpt_on_v1: ${state.keepNativeChatGptOnV1 ? "ON" : "OFF"}`,
|
|
75
|
+
`max_threads: ${state.maxConcurrentThreadsPerSession ?? "unset"}`,
|
|
76
|
+
`agents.enabled: ${state.agentsEnabled ?? "unset (upstream default)"}`,
|
|
77
|
+
`agents.max_depth: ${state.agentsMaxDepth ?? "unset"}${state.enabled ? " (V1-only — ignored while V2 is enabled)" : ""}`,
|
|
78
|
+
`subagent_developer_instructions: ${JSON.stringify(state.subagentDeveloperInstructions)}`,
|
|
79
|
+
`multi_agent_mode_hint_text: ${JSON.stringify(state.multiAgentModeHintText)}`];
|
|
80
|
+
if (state.agentsMaxThreadsConflict) lines.push("Native thread-limit conflict: inspect the selected target's Codex configuration before starting Codex.");
|
|
81
|
+
if (state.enabled && state.agentsEnabled === false) lines.push("agents.enabled=false does not disable multi-agent while the global V2 feature is enabled.");
|
|
82
|
+
if (record(state.multiAgentSurfaceAdvisory) && state.multiAgentSurfaceAdvisory.required) {
|
|
83
|
+
lines.push(`Surface advisory: v1 is recommended. ${SUBAGENT_SURFACE_GUIDE_URL}`);
|
|
84
|
+
}
|
|
85
|
+
if (read) { printData({ target: "live", ...state }, parsed.json, lines); return 0; }
|
|
86
|
+
if (!record(response) || response.ok !== true) throw new Error("Invalid write receipt");
|
|
87
|
+
return printCatalogResult({ ok: true, target: "live", ...state }, response.catalogRefresh, parsed.json,
|
|
88
|
+
[...lines, "V2 settings saved. Applies to new sessions."]);
|
|
89
|
+
});
|
|
90
|
+
}
|
package/src/cli/v2.ts
CHANGED
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
* config keys, with byte-for-byte rollback when the feature command fails.
|
|
11
11
|
* - nothing in the catalog build path calls this module; no auto-flip exists.
|
|
12
12
|
*/
|
|
13
|
+
import { CliUsageError, terminalSafeText, type RuntimeApiDeps } from "./runtime-api";
|
|
14
|
+
import { parseV2Command, V2_USAGE, type V2ParsedCommand } from "./v2-input";
|
|
15
|
+
import { handleV2RuntimeCommand } from "./v2-runtime";
|
|
16
|
+
import { localV2State, v2LocalSyncResult, type V2LocalSync } from "./v2-local-output";
|
|
13
17
|
import { execFileSync } from "node:child_process";
|
|
14
18
|
import { dirname } from "node:path";
|
|
15
19
|
import { activeCodexConfigPath, getAgentsEnabled, getAgentsMaxDepth, getLogicalMaxThreads, getMultiAgentModeHintText, getSubagentDeveloperInstructions, hasAgentsMaxThreads, isMultiAgentV2Enabled, setMultiAgentModeHintText, transitionMultiAgentV2 } from "../codex/features";
|
|
@@ -23,6 +27,9 @@ export interface V2CliDeps {
|
|
|
23
27
|
isEnabled?: typeof isMultiAgentV2Enabled;
|
|
24
28
|
hasMaxThreads?: typeof hasAgentsMaxThreads;
|
|
25
29
|
sync?: (port?: number) => Promise<unknown>;
|
|
30
|
+
runtimeApi?: RuntimeApiDeps;
|
|
31
|
+
/** Internal seam: bypass executable discovery only when an exec implementation is injected. */
|
|
32
|
+
featuresInvocation?: (action: "enable" | "disable") => SpawnInvocation;
|
|
26
33
|
log?: Pick<Console, "log" | "error">;
|
|
27
34
|
}
|
|
28
35
|
|
|
@@ -79,7 +86,7 @@ export function runCodexFeaturesCommand(
|
|
|
79
86
|
|
|
80
87
|
function runCodexFeatures(action: "enable" | "disable", deps: V2CliDeps): void {
|
|
81
88
|
if (deps.execFile) {
|
|
82
|
-
const inv = codexFeaturesInvocation(action);
|
|
89
|
+
const inv = (deps.featuresInvocation ?? codexFeaturesInvocation)(action);
|
|
83
90
|
deps.execFile(inv.file, inv.args, inv.options);
|
|
84
91
|
return;
|
|
85
92
|
}
|
|
@@ -106,13 +113,10 @@ function requiresGlobalV2Disabled(multiAgentMode: string | undefined, keepNative
|
|
|
106
113
|
return multiAgentMode === "v2" && keepNativeChatGptOnV1;
|
|
107
114
|
}
|
|
108
115
|
|
|
109
|
-
|
|
116
|
+
function printLocalStatus(deps: V2CliDeps): number {
|
|
110
117
|
const log = deps.log ?? console;
|
|
111
118
|
const isEnabled = deps.isEnabled ?? isMultiAgentV2Enabled;
|
|
112
119
|
const hasMaxThreads = deps.hasMaxThreads ?? hasAgentsMaxThreads;
|
|
113
|
-
const verb = (args[0] ?? "status").trim().toLowerCase();
|
|
114
|
-
|
|
115
|
-
if (verb === "status") {
|
|
116
120
|
log.log(v2StatusLine(isEnabled()));
|
|
117
121
|
const cfg = loadConfig();
|
|
118
122
|
const mode = cfg.multiAgentMode ?? "default";
|
|
@@ -140,153 +144,107 @@ export async function cmdV2(args: string[], deps: V2CliDeps = {}, findPort?: ()
|
|
|
140
144
|
log.log("WARNING: [agents] max_threads is set — codex refuses to start while multi_agent_v2 is enabled. Remove it from config.toml (concurrency lives in features.multi_agent_v2.max_concurrent_threads_per_session).");
|
|
141
145
|
}
|
|
142
146
|
return 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Parse before selecting either the native writer or the runtime management target. */
|
|
150
|
+
export async function cmdV2(args: string[], deps: V2CliDeps = {}, findPort?: () => Promise<number | undefined>): Promise<number> {
|
|
151
|
+
const log = deps.log ?? console;
|
|
152
|
+
let parsed: V2ParsedCommand;
|
|
153
|
+
try { parsed = parseV2Command(args); }
|
|
154
|
+
catch (error) {
|
|
155
|
+
log.error(error instanceof CliUsageError ? error.message : "v2: invalid command");
|
|
156
|
+
for (const line of V2_USAGE.split("\n")) log.error(line);
|
|
157
|
+
const boundary = args.indexOf("--");
|
|
158
|
+
return (boundary < 0 ? args : args.slice(0, boundary)).includes("--live") ? 2 : 1;
|
|
143
159
|
}
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
return
|
|
160
|
-
}
|
|
161
|
-
// `--clear` is the only reserved token; hints are otherwise arbitrary
|
|
162
|
-
// nonblank text and may legitimately begin with a hyphen.
|
|
163
|
-
if (value.trim().length === 0) {
|
|
164
|
-
log.error("v2 mode-hint: pass the hint text, or --clear to unset it.");
|
|
165
|
-
return 1;
|
|
166
|
-
}
|
|
167
|
-
const result = setMultiAgentModeHintText(value);
|
|
168
|
-
if (!result.ok) {
|
|
169
|
-
log.error(`v2 mode-hint: ${result.error}`);
|
|
170
|
-
return 1;
|
|
160
|
+
if (parsed.live) return handleV2RuntimeCommand(parsed, deps.runtimeApi);
|
|
161
|
+
let changed: boolean | null = false;
|
|
162
|
+
let sync: V2LocalSync = { status: "not-attempted", ok: false };
|
|
163
|
+
const emit = (ok: boolean, lines: string[]): number => {
|
|
164
|
+
// A read-back failure cannot claim rollback or overwrite already observed mutation evidence.
|
|
165
|
+
let state: ReturnType<typeof localV2State> | null = null;
|
|
166
|
+
try { state = localV2State(deps.isEnabled, deps.hasMaxThreads); }
|
|
167
|
+
catch { ok = false; lines = ["Local V2 outcome is unverified. Read ocx v2 status before retrying; no rollback is implied."]; }
|
|
168
|
+
if (parsed.json) log.log(JSON.stringify({ ok, target: "local", action: parsed.verb, changed, state, sync }));
|
|
169
|
+
else for (const line of lines) (ok ? log.log : log.error).call(log, terminalSafeText(line));
|
|
170
|
+
return ok ? 0 : 1;
|
|
171
|
+
};
|
|
172
|
+
try {
|
|
173
|
+
if (parsed.verb === "status") {
|
|
174
|
+
if (!parsed.json) return printLocalStatus(deps);
|
|
175
|
+
return emit(true, []);
|
|
171
176
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (verb === "threads") {
|
|
178
|
-
const value = Number((args[1] ?? "").trim());
|
|
179
|
-
if (!Number.isInteger(value) || value < 1) {
|
|
180
|
-
log.error("v2 threads: pass an integer >= 1 (features.multi_agent_v2.max_concurrent_threads_per_session)");
|
|
181
|
-
return 1;
|
|
177
|
+
if (parsed.verb === "mode-hint") {
|
|
178
|
+
const result = setMultiAgentModeHintText(parsed.value);
|
|
179
|
+
if (!result.ok) return emit(false, ["Unable to write the native mode hint. Check Codex support and ownership, then read ocx v2 status."]);
|
|
180
|
+
changed = result.changed;
|
|
181
|
+
return emit(true, [result.changed ? "multi_agent_mode_hint_text saved — applies to new sessions." : "multi_agent_mode_hint_text already set — nothing to do."]);
|
|
182
182
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
: `max_threads already ${value} — nothing to do.`);
|
|
189
|
-
return 0;
|
|
190
|
-
}
|
|
191
|
-
if (verb === "mode") {
|
|
192
|
-
const modeArg = (args[1] ?? "").trim().toLowerCase();
|
|
193
|
-
if (modeArg !== "v1" && modeArg !== "default" && modeArg !== "v2") {
|
|
194
|
-
log.error("v2 mode: expected v1|default|v2");
|
|
195
|
-
return 1;
|
|
183
|
+
if (parsed.verb === "threads") {
|
|
184
|
+
const enabled = (deps.isEnabled ?? isMultiAgentV2Enabled)();
|
|
185
|
+
const result = transitionMultiAgentV2(enabled, next => runCodexFeatures(next ? "enable" : "disable", deps), { threadLimit: parsed.value });
|
|
186
|
+
if (!result.ok) return emit(false, ["Unable to update the native thread limit. Read ocx v2 status before retrying."]);
|
|
187
|
+
changed = result.changed;
|
|
188
|
+
return emit(true, [changed ? `max_threads = ${parsed.value} (${enabled ? "v2" : "v1"}) — applies to new sessions.` : `max_threads already ${parsed.value} — nothing to do.`]);
|
|
196
189
|
}
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
190
|
+
const config = loadConfig();
|
|
191
|
+
let successLines: string[];
|
|
192
|
+
let unchangedKeep: boolean | undefined;
|
|
193
|
+
if (parsed.verb === "mode") {
|
|
194
|
+
changed = (config.multiAgentMode ?? "default") !== parsed.value;
|
|
195
|
+
if (parsed.value !== "default") {
|
|
196
|
+
const target = parsed.value === "v2" && config.keepNativeChatGptOnV1 !== true;
|
|
197
|
+
const transition = transitionMultiAgentV2(target, next => runCodexFeatures(next ? "enable" : "disable", deps));
|
|
198
|
+
if (!transition.ok) { changed = false; return emit(false, ["Native mode transition failed. Read ocx v2 status before retrying."]); }
|
|
199
|
+
changed ||= transition.changed;
|
|
204
200
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const already = cfg.keepNativeChatGptOnV1 === true === next;
|
|
229
|
-
if (next && requiresGlobalV2Disabled(cfg.multiAgentMode, true)) {
|
|
230
|
-
const transition = transitionMultiAgentV2(false, enabled => runCodexFeatures(enabled ? "enable" : "disable", deps));
|
|
231
|
-
if (!transition.ok) {
|
|
232
|
-
log.error(`keep-native-v1 transition failed: ${transition.error}`);
|
|
233
|
-
return 1;
|
|
201
|
+
if (parsed.value === "default") deleteConfigTopLevelKey(config, "multiAgentMode");
|
|
202
|
+
else config.multiAgentMode = parsed.value;
|
|
203
|
+
saveConfig(config);
|
|
204
|
+
successLines = [multiAgentModeLine(parsed.value, config.keepNativeChatGptOnV1 === true),
|
|
205
|
+
"Applies to NEW sessions; running sessions keep their pinned multi-agent version."];
|
|
206
|
+
} else if (parsed.verb === "keep-native-v1") {
|
|
207
|
+
unchangedKeep = (config.keepNativeChatGptOnV1 === true) === parsed.value;
|
|
208
|
+
changed = !unchangedKeep;
|
|
209
|
+
if (parsed.value && requiresGlobalV2Disabled(config.multiAgentMode, true)) {
|
|
210
|
+
const transition = transitionMultiAgentV2(false, next => runCodexFeatures(next ? "enable" : "disable", deps));
|
|
211
|
+
if (!transition.ok) { changed = false; return emit(false, ["Native hybrid-mode transition failed. Read ocx v2 status before retrying."]); }
|
|
212
|
+
changed ||= transition.changed;
|
|
213
|
+
}
|
|
214
|
+
if (parsed.value) config.keepNativeChatGptOnV1 = true;
|
|
215
|
+
else deleteConfigTopLevelKey(config, "keepNativeChatGptOnV1");
|
|
216
|
+
saveConfig(config);
|
|
217
|
+
successLines = [parsed.value
|
|
218
|
+
? "keep_native_chatgpt_on_v1: ON — ChatGPT-native rows stay v1 when mode is v2 (new sessions)."
|
|
219
|
+
: "keep_native_chatgpt_on_v1: OFF — ChatGPT-native rows follow v1/base/v2 (new sessions)."];
|
|
220
|
+
} else {
|
|
221
|
+
const want = parsed.verb === "on";
|
|
222
|
+
if (want && requiresGlobalV2Disabled(config.multiAgentMode, config.keepNativeChatGptOnV1 === true)) {
|
|
223
|
+
return emit(false, ["v2 on: incompatible with keep-native-v1 while mode is v2 — Codex's global multi_agent_v2 overrides the native v1 catalog pin. Run 'ocx v2 keep-native-v1 off' first."]);
|
|
234
224
|
}
|
|
225
|
+
const transition = transitionMultiAgentV2(want, next => runCodexFeatures(next ? "enable" : "disable", deps));
|
|
226
|
+
if (!transition.ok) return emit(false, ["Native feature transition failed. Read ocx v2 status before retrying."]);
|
|
227
|
+
changed = transition.changed;
|
|
228
|
+
if (!changed) return emit(true, [`multi_agent_v2 already ${want ? "ON" : "OFF"} — nothing to do.`]);
|
|
229
|
+
successLines = [v2StatusLine(want),
|
|
230
|
+
"Applies to NEW sessions; running sessions keep their pinned multi-agent version. Restart the Codex app (or wait out its picker cache) to see the ladder change."];
|
|
235
231
|
}
|
|
236
|
-
|
|
237
|
-
else deleteConfigTopLevelKey(cfg, "keepNativeChatGptOnV1");
|
|
238
|
-
saveConfig(cfg);
|
|
232
|
+
// Preserve the legacy graph: mode/keep and changed toggles sync even with no port.
|
|
239
233
|
try {
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
: "keep_native_chatgpt_on_v1 already OFF — catalog re-synced.");
|
|
250
|
-
return 0;
|
|
251
|
-
}
|
|
252
|
-
log.log(next
|
|
253
|
-
? "keep_native_chatgpt_on_v1: ON — ChatGPT-native rows stay v1 when mode is v2 (new sessions)."
|
|
254
|
-
: "keep_native_chatgpt_on_v1: OFF — ChatGPT-native rows follow v1/base/v2 (new sessions).");
|
|
255
|
-
return 0;
|
|
256
|
-
}
|
|
257
|
-
if (verb !== "on" && verb !== "off") {
|
|
258
|
-
log.error(`v2: unknown verb '${verb}' (expected status|on|off|mode <v1|default|v2>|keep-native-v1 <on|off>|threads <n>|mode-hint <text|--clear>)`);
|
|
259
|
-
return 1;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const want = verb === "on";
|
|
263
|
-
if (want) {
|
|
264
|
-
const cfg = loadConfig();
|
|
265
|
-
if (requiresGlobalV2Disabled(cfg.multiAgentMode, cfg.keepNativeChatGptOnV1 === true)) {
|
|
266
|
-
log.error("v2 on: incompatible with keep-native-v1 while mode is v2 — Codex's global multi_agent_v2 overrides the native v1 catalog pin. Run 'ocx v2 keep-native-v1 off' first.");
|
|
267
|
-
return 1;
|
|
234
|
+
const port = findPort ? await findPort() : undefined;
|
|
235
|
+
const result = deps.sync ? await deps.sync(port)
|
|
236
|
+
: await (await import("../codex/sync")).syncModelsToCodex(port, undefined, parsed.json ? null : log);
|
|
237
|
+
sync = v2LocalSyncResult(result);
|
|
238
|
+
} catch { sync = { status: "failed", ok: false }; }
|
|
239
|
+
if (sync.ok && parsed.verb === "keep-native-v1" && unchangedKeep) {
|
|
240
|
+
const outcome = sync.status !== "skipped" && "catalog" in sync && sync.catalog?.converged
|
|
241
|
+
? "catalog re-synced." : "catalog sync skipped by policy.";
|
|
242
|
+
successLines = [`keep_native_chatgpt_on_v1 already ${parsed.value ? "ON" : "OFF"} — ${outcome}`];
|
|
268
243
|
}
|
|
244
|
+
return emit(sync.ok, sync.ok ? successLines
|
|
245
|
+
: ["catalog resync failed or is unverified; local V2 settings landed. Read ocx v2 status, then run ocx sync."]);
|
|
246
|
+
} catch {
|
|
247
|
+
changed = null;
|
|
248
|
+
return emit(false, ["Local V2 operation failed; native or saved settings may have changed. Read ocx v2 status before retrying; no rollback is implied."]);
|
|
269
249
|
}
|
|
270
|
-
const transition = transitionMultiAgentV2(want, enabled => runCodexFeatures(enabled ? "enable" : "disable", deps));
|
|
271
|
-
if (!transition.ok) {
|
|
272
|
-
log.error(`codex features ${want ? "enable" : "disable"} multi_agent_v2 failed: ${transition.error}`);
|
|
273
|
-
return 1;
|
|
274
|
-
}
|
|
275
|
-
if (!transition.changed) {
|
|
276
|
-
log.log(`multi_agent_v2 already ${want ? "ON" : "OFF"} — nothing to do.`);
|
|
277
|
-
return 0;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Resync catalog so multi-agent surface metadata stays fresh in both the
|
|
281
|
-
// on-disk catalog and models_cache.json after the toggle flip.
|
|
282
|
-
try {
|
|
283
|
-
const sync = deps.sync ?? (await import("../codex/sync")).syncModelsToCodex;
|
|
284
|
-
await sync(findPort ? await findPort() : undefined);
|
|
285
|
-
} catch (err) {
|
|
286
|
-
log.error(`catalog resync failed (flag IS flipped): ${err instanceof Error ? err.message : String(err)} — run 'ocx sync' manually.`);
|
|
287
|
-
return 1;
|
|
288
|
-
}
|
|
289
|
-
log.log(v2StatusLine(want));
|
|
290
|
-
log.log("Applies to NEW sessions; running sessions keep their pinned multi-agent version. Restart the Codex app (or wait out its picker cache) to see the ladder change.");
|
|
291
|
-
return 0;
|
|
292
250
|
}
|