@bitkyc08/opencodex 2.81.0 → 2.83.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 +166 -73
- package/gui/dist/assets/App-Ba8uG696.js +54 -0
- package/gui/dist/assets/{Tray-EwAOsP0S.js → Tray-IImbNPRy.js} +1 -1
- package/gui/dist/assets/{index-Brk6F3fn.css → index-BAbcj51J.css} +1 -1
- package/gui/dist/assets/{index-RsoaAxXY.js → index-BHRZdJuv.js} +13 -13
- package/gui/dist/assets/{quota-summary-D2lN8tDm.js → quota-summary-DiJO6sYY.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +1 -1
- package/src/adapters/cursor/catalog.ts +2 -2
- package/src/adapters/cursor/effort-map.ts +1 -1
- package/src/adapters/devin/live-models.ts +2 -2
- package/src/adapters/google-truncation.ts +22 -0
- package/src/adapters/google.ts +7 -3
- package/src/adapters/ollama-native.ts +18 -14
- package/src/adapters/openai-responses/compaction-search-history.ts +109 -0
- package/src/adapters/openai-responses/passthrough.ts +11 -4
- package/src/adapters/openai-responses/plaintext-reasoning-notice.ts +85 -0
- package/src/adapters/openai-responses/reasoning.ts +11 -1
- package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
- package/src/bridge/errors.ts +7 -1
- package/src/claude/agents-inject.ts +14 -3
- package/src/claude/context-windows.ts +22 -20
- package/src/claude/desktop-3p.ts +88 -12
- package/src/claude/desktop-picker.ts +1 -1
- package/src/claude/desktop-profile.ts +2 -1
- package/src/claude/gateway-cache.ts +18 -1
- package/src/claude/inbound.ts +77 -3
- package/src/claude/intercept/cli-catalog.ts +3 -0
- package/src/claude/intercept/picker-ca-persistence.ts +7 -6
- package/src/claude/intercept/picker-models.ts +14 -6
- package/src/claude/intercept/settings.ts +3 -0
- package/src/claude/long-context.ts +66 -0
- package/src/claude/model-info.ts +19 -16
- package/src/claude/outbound.ts +94 -9
- package/src/claude/subagent-model.ts +40 -11
- package/src/cli/account-auth.ts +20 -12
- package/src/cli/agent.ts +9 -5
- package/src/cli/aside-profile-recovery.ts +33 -0
- package/src/cli/capabilities-base.ts +5 -5
- package/src/cli/capabilities-integrations.ts +2 -2
- package/src/cli/cli-path-diagnostics.ts +132 -0
- package/src/cli/desktop-runtime-guidance.ts +65 -0
- package/src/cli/dispatch.ts +5 -0
- package/src/cli/doctor.ts +16 -3
- package/src/cli/index.ts +14 -9
- package/src/cli/integration-aside-sync.ts +2 -1
- package/src/cli/integrations.ts +16 -3
- package/src/cli/resolve.ts +26 -3
- package/src/cli/root.ts +4 -2
- package/src/cli/status.ts +64 -12
- package/src/cli/update-restart-child.ts +4 -2
- package/src/cli/update-restart-home.ts +28 -10
- package/src/cli/update-restart-service-record.ts +116 -0
- package/src/cli/update-restart-supervision.ts +96 -0
- package/src/cli/update-restart.ts +59 -13
- package/src/cli/v2.ts +27 -20
- package/src/cli/version-skew-notice.ts +77 -0
- package/src/clients/config-export/model-metadata.ts +16 -0
- package/src/clients/config-export/zcode-store.ts +46 -11
- package/src/clients/config-export/zcode.ts +3 -5
- package/src/clients/config-export.ts +1 -1
- package/src/clients/lazycodex.ts +1 -1
- package/src/clients/omo-role-models.ts +32 -7
- package/src/codex/auth-api/account-selection.ts +107 -0
- package/src/codex/auth-api/pool-mode-gate.ts +4 -5
- package/src/codex/auth-api/routes.ts +10 -49
- package/src/codex/auth-context.ts +28 -4
- package/src/codex/autostart-health.ts +12 -3
- package/src/codex/catalog/build-entries.ts +18 -0
- package/src/codex/catalog/bundled.ts +155 -26
- package/src/codex/catalog/combo-member.ts +3 -2
- package/src/codex/catalog/effort.ts +26 -5
- package/src/codex/catalog/gather-capture.ts +5 -0
- package/src/codex/catalog/model-hints.ts +15 -8
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/native-models.ts +1 -1
- package/src/codex/catalog/operator-tiers.ts +38 -0
- package/src/codex/catalog/provider-models.ts +68 -35
- package/src/codex/catalog/retained-sync.ts +7 -5
- package/src/codex/catalog/routed-gather.ts +15 -1
- package/src/codex/catalog-auto-refresh-sources.ts +3 -4
- package/src/codex/catalog-auto-refresh.ts +19 -0
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-home-owner.ts +3 -0
- package/src/codex/config-write-lock.ts +333 -0
- package/src/codex/convergence.ts +2 -1
- package/src/codex/desktop-app/darwin.ts +1 -1
- package/src/codex/desktop-app/handoff.ts +1 -1
- package/src/codex/desktop-app/linux.ts +1 -1
- package/src/codex/desktop-app/lock.ts +1 -1
- package/src/codex/desktop-app/types.ts +1 -1
- package/src/codex/desktop-app/windows.ts +1 -1
- package/src/codex/desktop-app-restart.ts +1 -1
- package/src/codex/features.ts +257 -200
- package/src/codex/inject/config-write-section.ts +34 -0
- package/src/codex/inject/multi-agent-v2.ts +14 -6
- package/src/codex/inject/remove.ts +36 -11
- package/src/codex/inject/restore.ts +85 -20
- package/src/codex/inject-coordination.ts +15 -6
- package/src/codex/inject.ts +62 -20
- package/src/codex/journal.ts +62 -17
- package/src/codex/main-account-cache.ts +43 -0
- package/src/codex/main-account.ts +6 -7
- package/src/codex/paths.ts +2 -0
- package/src/codex/prepared-home.ts +18 -0
- package/src/codex/prompt-journal.ts +25 -12
- package/src/codex/prompt-layers.ts +49 -26
- package/src/codex/prompt-lock-claim.ts +134 -0
- package/src/codex/prompt-lock-evidence.ts +26 -0
- package/src/codex/prompt-lock-io.ts +16 -0
- package/src/codex/prompt-lock-owner.ts +130 -0
- package/src/codex/prompt-lock.ts +84 -53
- package/src/codex/routing/failure-window.ts +146 -0
- package/src/codex/routing/selection.ts +4 -1
- package/src/codex/routing.ts +13 -17
- package/src/codex/runtime.ts +106 -18
- package/src/codex/shim.ts +45 -19
- package/src/combos/jev-service-exchange.ts +281 -0
- package/src/combos/jev.ts +34 -220
- package/src/config/atomic-write.ts +1 -1
- package/src/config/codex-ws-reuse-setting.ts +10 -0
- package/src/config/live-reconcile.ts +17 -0
- package/src/config/load-degrade.ts +8 -0
- package/src/config/rebase-provenance.ts +11 -0
- package/src/config/schema/config-schema.ts +4 -0
- package/src/generated/compatibility-version.json +375 -227
- package/src/generated/model-metadata.ts +2 -2
- package/src/grok/inject.ts +276 -206
- package/src/grok/reset-coupon-ledger.ts +223 -57
- package/src/grok/reset-coupons.ts +13 -6
- package/src/grok/sync.ts +2 -1
- package/src/images/loop.ts +29 -11
- package/src/integrations/aside-profiles.ts +10 -3
- package/src/integrations/catalog-refresh.ts +18 -2
- package/src/integrations/cursor-detect.ts +1 -1
- package/src/integrations/writer.ts +9 -3
- package/src/lib/bun-path-runtime.d.mts +32 -0
- package/src/lib/bun-path-runtime.mjs +95 -0
- package/src/lib/bun-runtime-preflight.ts +140 -0
- package/src/lib/desktop-cli-handoff.d.mts +20 -0
- package/src/lib/desktop-cli-handoff.mjs +92 -0
- package/src/lib/desktop-cli-record.d.mts +29 -0
- package/src/lib/desktop-cli-record.mjs +102 -0
- package/src/lib/errors.ts +25 -0
- package/src/lib/redact.ts +2 -2
- package/src/lib/request-execution-budget.ts +8 -0
- package/src/lib/synced-state-location.ts +46 -6
- package/src/lib/test-home-guard.ts +87 -1
- package/src/lib/translator-budget.ts +17 -4
- package/src/lib/windows-atomic-replace.ts +38 -16
- package/src/lib/windows-elevation.ts +6 -0
- package/src/lib/windows-secret-acl.ts +92 -25
- package/src/lib/winsw.ts +2 -3
- package/src/oauth/anthropic-account-refusal.ts +39 -1
- package/src/oauth/anthropic-routing.ts +1 -1
- package/src/oauth/anthropic-send-ownership.ts +3 -3
- package/src/oauth/chatgpt.ts +113 -41
- package/src/oauth/index.ts +7 -3
- package/src/oauth/meta-muse-device.ts +1 -1
- package/src/oauth/store.ts +1 -1
- package/src/oauth/types.ts +1 -1
- package/src/providers/anthropic-fast.ts +1 -1
- package/src/providers/anthropic-reset-grant-ledger.ts +1 -1
- package/src/providers/azure-model-metadata.ts +115 -0
- package/src/providers/derive.ts +21 -0
- package/src/providers/devin-cli-authmode-migration.ts +1 -1
- package/src/providers/devin-provider-merge-migration.ts +1 -1
- package/src/providers/github-copilot-auto.ts +169 -0
- package/src/providers/model-presets.ts +1 -1
- package/src/providers/muse-subscription-usage.ts +1 -1
- package/src/providers/quota/vendor-probes-key.ts +127 -17
- package/src/providers/quota.ts +1 -1
- package/src/providers/registry/entries-core.ts +6 -6
- package/src/providers/registry/entries-extended.ts +4 -0
- package/src/providers/registry/model-seeds.ts +3 -3
- package/src/providers/xai-fast-model.ts +1 -1
- package/src/responses/reasoning-envelope.ts +29 -1
- package/src/responses/reasoning-replay-cache.ts +17 -1
- package/src/responses/spill-store.ts +15 -2
- package/src/responses/state/metrics.ts +8 -0
- package/src/responses/state/spill-failure.ts +12 -0
- package/src/responses/state/spill-queue.ts +12 -5
- package/src/responses/state.ts +18 -10
- package/src/responses/task-input.ts +17 -0
- package/src/server/background-lifecycle.ts +27 -0
- package/src/server/chat-completions.ts +1 -0
- package/src/server/chat-native.ts +28 -12
- package/src/server/claude-messages.ts +43 -6
- package/src/server/inbound-body-admission.ts +25 -7
- package/src/server/index/claude-intercept-lifecycle.ts +3 -1
- package/src/server/index/link-listener.ts +30 -17
- package/src/server/index/serve-options.ts +18 -4
- package/src/server/index/spend-ledger-lifecycle.ts +2 -1
- package/src/server/index/startup-warnings.ts +19 -0
- package/src/server/index.ts +2 -1
- package/src/server/lifecycle.ts +31 -4
- package/src/server/management/agent-settings-routes.ts +93 -32
- package/src/server/management/codex-agent-role-routes.ts +4 -2
- package/src/server/management/codex-prompt-routes.ts +1 -0
- package/src/server/management/codex-role-auto-assign.ts +13 -2
- package/src/server/management/config-routes.ts +59 -8
- package/src/server/management/context.ts +2 -2
- package/src/server/management/grok-coupon-routes.ts +277 -52
- package/src/server/management/link-routes.ts +42 -20
- package/src/server/management/route-registry.ts +2 -2
- package/src/server/management/shared.ts +3 -3
- package/src/server/messages-native-oauth.ts +9 -8
- package/src/server/messages-native.ts +49 -13
- package/src/server/messages-response-headers.ts +14 -0
- package/src/server/relay-eager.ts +2 -0
- package/src/server/relay.ts +18 -7
- package/src/server/request-log-conversation.ts +48 -5
- package/src/server/request-log-failure-attribution.ts +3 -0
- package/src/server/request-log-terminal-status.ts +186 -0
- package/src/server/request-log.ts +15 -89
- package/src/server/responses/adapter-continuation.ts +11 -3
- package/src/server/responses/adapter-dispatch.ts +99 -19
- package/src/server/responses/codex-ws-exchange.ts +17 -2
- package/src/server/responses/codex-ws-pool.ts +100 -13
- package/src/server/responses/codex-ws-wire.ts +9 -0
- package/src/server/responses/combo-requested-effort.ts +3 -1
- package/src/server/responses/compact.ts +3 -2
- package/src/server/responses/core-codex-account.ts +22 -1
- package/src/server/responses/core-combo-native.ts +20 -3
- package/src/server/responses/core-combo.ts +22 -14
- package/src/server/responses/core-lifetime.ts +3 -36
- package/src/server/responses/core-opaque-recovery.ts +5 -5
- package/src/server/responses/core-options.ts +5 -0
- package/src/server/responses/core-replay.ts +73 -0
- package/src/server/responses/core.ts +9 -9
- package/src/server/responses/empty-completion-guard.ts +18 -2
- package/src/server/responses/fetch-helpers.ts +26 -3
- package/src/server/responses/jev-model-invoke.ts +1 -1
- package/src/server/responses/passthrough-delivery.ts +24 -2
- package/src/server/responses/passthrough-dispatch.ts +134 -62
- package/src/server/responses/passthrough-execution.ts +3 -3
- package/src/server/responses/request-prepare.ts +4 -0
- package/src/server/responses/request-send-budget.ts +112 -49
- package/src/server/responses/request-transport.ts +125 -5
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +15 -5
- package/src/server/responses/sidecar-execution.ts +30 -16
- package/src/server/responses/sidecar-send-budget.ts +58 -0
- package/src/server/responses/spend-storage-error.ts +33 -0
- package/src/server/responses/ws-upstream.ts +19 -6
- package/src/server/startup-health-cache.ts +2 -1
- package/src/server/workflow-refusal.ts +3 -0
- package/src/service/cli.ts +20 -4
- package/src/service/desktop-command-guard.ts +32 -0
- package/src/service/desktop-startup.ts +66 -40
- package/src/service/desktop-supervision.d.mts +26 -0
- package/src/service/desktop-supervision.mjs +140 -0
- package/src/service/managing-cli.ts +1 -0
- package/src/service/orchestration.ts +46 -20
- package/src/service/repair.ts +25 -9
- package/src/service/state.ts +4 -3
- package/src/service/systemd.ts +17 -8
- package/src/service/windows-ops.ts +61 -22
- package/src/storage/policy-job.ts +5 -1
- package/src/storage/policy-worker.ts +3 -1
- package/src/storage/policy.ts +9 -0
- package/src/types/config.ts +21 -0
- package/src/types/request.ts +10 -0
- package/src/types/wire.ts +1 -0
- package/src/update/index.ts +57 -9
- package/src/update/restart-ownership.ts +10 -3
- package/src/update/runtime-ownership.d.mts +6 -0
- package/src/update/runtime-ownership.mjs +23 -7
- package/src/usage/expected-prices.ts +1 -1
- package/src/web-search/loop.ts +5 -2
- package/gui/dist/assets/App-7yhoys8a.js +0 -54
|
@@ -7,21 +7,23 @@ import { isRealBunBinary } from "../lib/bun-binary-validator.mjs";
|
|
|
7
7
|
import { isProcessAlive } from "../lib/process-control";
|
|
8
8
|
import { parseStrictSemver } from "../lib/strict-semver";
|
|
9
9
|
import { startArgv } from "../lib/self-launch-argv";
|
|
10
|
-
import {
|
|
11
|
-
import { inspectGuardedManagerTarget } from "../service/guarded-manager-target";
|
|
10
|
+
import { inspectGuardedManagerTarget, type GuardedManagerDeps } from "../service/guarded-manager-target";
|
|
12
11
|
import { acquireOwnershipMutationLease, unprivilegedOwnershipMutationEnvironment } from "../service/ownership-mutation-lease.mjs";
|
|
13
12
|
import { serviceStatePaths } from "../service/state";
|
|
14
13
|
import { findLiveProxy, probeEndpointLiveness, probeHostname, type LiveProxy } from "../server/proxy-liveness";
|
|
15
14
|
import { waitForPortAvailable } from "../server/ports";
|
|
16
15
|
import { UPDATE_RESTART_CHILD_ENV, type UpdateRestartChildMarker } from "./update-restart-child";
|
|
17
|
-
import { assertUpdateRestartConfiguration, assertUpdateRestartHome, readUpdateRestartHome, type UpdateRestartHome } from "./update-restart-home";
|
|
16
|
+
import { assertUpdateRestartConfiguration, assertUpdateRestartHome, readUpdateRestartHome, type UpdateRestartHome, type UpdateRestartHomeDeps } from "./update-restart-home";
|
|
18
17
|
import { observeAttestedUpdateReplacement, stopAttestedUpdateTarget } from "./update-restart-transport";
|
|
19
18
|
import type { UpdateRestartCandidate } from "./update-restart-candidate";
|
|
20
19
|
import { INCOMPLETE_INSTALL_RECOVERY } from "./restart-failure";
|
|
21
20
|
import { computeVersionSkew } from "./version-skew";
|
|
21
|
+
import { createSupervisionLatch, inspectDesktopSupervision } from "../service/desktop-supervision.mjs";
|
|
22
|
+
import { classifyUpdateRestartSystemdSupervision, probeUpdateRestartSupervision, resolveUpdateRestartSupervisor, runBoundedUpdateRestartSupervisor, UPDATE_RESTART_SYSTEMD_ARGS, type UpdateRestartSupervisionDeps } from "./update-restart-supervision";
|
|
22
23
|
|
|
23
24
|
export interface UpdateRestartChild { pid?: number; exitCode: number | null; signalCode: string | null }
|
|
24
25
|
export interface UpdateRestartIo {
|
|
26
|
+
inspectSupervision?: typeof inspectDesktopSupervision;
|
|
25
27
|
now(): number;
|
|
26
28
|
acquire(): { release(): void };
|
|
27
29
|
home(): UpdateRestartHome;
|
|
@@ -37,7 +39,7 @@ export interface UpdateRestartIo {
|
|
|
37
39
|
wait(ms: number): Promise<void>;
|
|
38
40
|
}
|
|
39
41
|
const ELIGIBILITY_REASONS = ["windows", "unsupported_platform", "foreground", "service", "shared_or_client",
|
|
40
|
-
"package_tree_fenced", "unverifiable_ancestry", "target_changed", "configuration_changed"] as const;
|
|
42
|
+
"package_tree_fenced", "unverifiable_ancestry", "target_changed", "configuration_changed", "desktop"] as const;
|
|
41
43
|
export type UpdateRestartEligibilityReason = (typeof ELIGIBILITY_REASONS)[number];
|
|
42
44
|
|
|
43
45
|
/** Carries only a closed reason code across the eligibility/transport boundary. */
|
|
@@ -56,6 +58,7 @@ function sameRuntime(candidate: UpdateRestartCandidate, current: RuntimePortStat
|
|
|
56
58
|
|
|
57
59
|
/** A terminal update transaction: it never enters generic restart recovery. */
|
|
58
60
|
export async function runUpdateRestart(candidate: UpdateRestartCandidate, deadlineAt: number, io: UpdateRestartIo): Promise<UpdateRestartResult> {
|
|
61
|
+
const latch = createSupervisionLatch();
|
|
59
62
|
let phase = "eligibility";
|
|
60
63
|
let lease: { release(): void } | undefined;
|
|
61
64
|
// Recorded here because the stop transport sanitizes anything thrown by beforeStop.
|
|
@@ -79,6 +82,13 @@ export async function runUpdateRestart(candidate: UpdateRestartCandidate, deadli
|
|
|
79
82
|
eligibilityReason = currentRuntime?.siblingOfPort !== undefined ? "shared_or_client" : "target_changed";
|
|
80
83
|
throw new Error("target");
|
|
81
84
|
}
|
|
85
|
+
const supervision = (io.inspectSupervision ?? inspectDesktopSupervision)({ targetPid: candidate.target.pid });
|
|
86
|
+
const evidence = supervision.kind === "desktop" && supervision.runtimePid !== candidate.target.pid
|
|
87
|
+
? { kind: "unknown" as const, reason: "unrelated-target", desktopSeen: false } : supervision;
|
|
88
|
+
if (latch.observe(evidence)) {
|
|
89
|
+
eligibilityReason = "desktop";
|
|
90
|
+
throw new UpdateRestartEligibilityError("desktop");
|
|
91
|
+
}
|
|
82
92
|
try {
|
|
83
93
|
if (!io.standalone(candidate.target)) throw new Error("target");
|
|
84
94
|
} catch (error) {
|
|
@@ -147,6 +157,7 @@ export function describeUpdateRestartFailure(code: string, reason?: UpdateRestar
|
|
|
147
157
|
switch (reason) {
|
|
148
158
|
case "windows": return "Restart from a newer CLI is unavailable on Windows. Run `ocx status`, then restart through the owning service or desktop app.";
|
|
149
159
|
case "unsupported_platform": return "Restart from a newer CLI requires macOS or Linux. Run `ocx status`, then use the owning lifecycle manager.";
|
|
160
|
+
case "desktop": return "OpenCodex Desktop supervises this proxy. Use the app's updater (tray → Check for Updates), or quit OpenCodex before updating and restarting from this CLI.";
|
|
150
161
|
case "service": return "An installed or active service owns lifecycle control. Run `ocx status`, then use the owning installation's `ocx service restart`.";
|
|
151
162
|
case "foreground": return "The proxy is attached to a parent process. Run `ocx status`; if it runs in a terminal, restart it in that terminal, otherwise use its owning app or supervisor.";
|
|
152
163
|
case "shared_or_client": return "This is a shared or connected-client runtime. Run `ocx status` and restart through its owning Hub, service or desktop app.";
|
|
@@ -180,29 +191,64 @@ export function describeUpdateRestartFailure(code: string, reason?: UpdateRestar
|
|
|
180
191
|
}
|
|
181
192
|
}
|
|
182
193
|
|
|
183
|
-
|
|
184
|
-
|
|
194
|
+
export interface UpdateRestartStandaloneDeps {
|
|
195
|
+
platform?: NodeJS.Platform;
|
|
196
|
+
expectedHome?: UpdateRestartHome;
|
|
197
|
+
checkConfiguration?: typeof assertUpdateRestartConfiguration;
|
|
198
|
+
command?: typeof readProcessCommandLine;
|
|
199
|
+
parent?: (pid: number) => string;
|
|
200
|
+
home?: UpdateRestartHomeDeps;
|
|
201
|
+
supervision?: UpdateRestartSupervisionDeps;
|
|
202
|
+
manager?: GuardedManagerDeps;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function standalone(target: UpdateRestartCandidate["target"], deadlineAt: number, deps: UpdateRestartStandaloneDeps = {}): boolean {
|
|
206
|
+
const platform = deps.platform ?? process.platform;
|
|
207
|
+
try { (deps.checkConfiguration ?? assertUpdateRestartConfiguration)(target.hostname ?? ""); }
|
|
185
208
|
catch { throw new UpdateRestartEligibilityError("configuration_changed"); }
|
|
186
|
-
if (
|
|
187
|
-
throw new UpdateRestartEligibilityError(
|
|
209
|
+
if (platform !== "darwin" && platform !== "linux") {
|
|
210
|
+
throw new UpdateRestartEligibilityError(platform === "win32" ? "windows" : "unsupported_platform");
|
|
188
211
|
}
|
|
189
212
|
const host = probeHostname(target.hostname).replace(/^\[|\]$/g, "");
|
|
190
213
|
if (target.role === "client") throw new UpdateRestartEligibilityError("shared_or_client");
|
|
191
214
|
if (target.packageTreeFenced) throw new UpdateRestartEligibilityError("package_tree_fenced");
|
|
192
215
|
if (!isIP(host) || target.source !== "runtime") throw new UpdateRestartEligibilityError("unverifiable_ancestry");
|
|
193
|
-
const command = readProcessCommandLine(target.pid);
|
|
216
|
+
const command = (deps.command ?? readProcessCommandLine)(target.pid);
|
|
194
217
|
if (!command || !isOcxStartCommandLine(command)) throw new UpdateRestartEligibilityError("unverifiable_ancestry");
|
|
195
|
-
|
|
196
|
-
|
|
218
|
+
let home: UpdateRestartHome;
|
|
219
|
+
try {
|
|
220
|
+
home = readUpdateRestartHome(deps.home);
|
|
221
|
+
if (deps.expectedHome && JSON.stringify(home) !== JSON.stringify(deps.expectedHome)) throw new Error("changed");
|
|
222
|
+
}
|
|
223
|
+
catch { throw new UpdateRestartEligibilityError("service"); }
|
|
224
|
+
const supervision = { ...deps.supervision, platform };
|
|
225
|
+
const managerCommand = resolveUpdateRestartSupervisor(supervision);
|
|
226
|
+
if (!managerCommand) throw new UpdateRestartEligibilityError("service");
|
|
227
|
+
if (probeUpdateRestartSupervision(deadlineAt, supervision, managerCommand) !== "inactive") throw new UpdateRestartEligibilityError("service");
|
|
228
|
+
const manager = inspectGuardedManagerTarget(target.pid, target.port, {
|
|
229
|
+
...deps.manager, platform,
|
|
230
|
+
launchctl: args => {
|
|
231
|
+
const result = runBoundedUpdateRestartSupervisor(managerCommand, args, deadlineAt, supervision);
|
|
232
|
+
return { ...result, ok: result.status === 0 };
|
|
233
|
+
},
|
|
234
|
+
systemdShow: () => {
|
|
235
|
+
const result = runBoundedUpdateRestartSupervisor(managerCommand, UPDATE_RESTART_SYSTEMD_ARGS, deadlineAt, supervision);
|
|
236
|
+
if (classifyUpdateRestartSystemdSupervision(result) !== "inactive") throw new Error("update_restart_supervision_unverified");
|
|
237
|
+
return result.stdout;
|
|
238
|
+
},
|
|
239
|
+
});
|
|
197
240
|
if (manager.kind !== "absent") throw new UpdateRestartEligibilityError(manager.kind === "bound" ? "service" : "unverifiable_ancestry");
|
|
198
241
|
let parent: string;
|
|
199
242
|
try {
|
|
200
|
-
parent = execFileSync("ps", ["-o", "ppid=", "-p", String(target.pid)], {
|
|
243
|
+
parent = deps.parent ? deps.parent(target.pid) : execFileSync("ps", ["-o", "ppid=", "-p", String(target.pid)], {
|
|
201
244
|
encoding: "utf8", timeout: 1000, stdio: ["ignore", "pipe", "ignore"],
|
|
202
245
|
}).trim();
|
|
203
246
|
} catch { throw new UpdateRestartEligibilityError("unverifiable_ancestry"); }
|
|
204
247
|
if (!/^[1-9]\d*$/.test(parent)) throw new UpdateRestartEligibilityError("unverifiable_ancestry");
|
|
205
248
|
if (parent !== "1") throw new UpdateRestartEligibilityError("foreground");
|
|
249
|
+
try {
|
|
250
|
+
if (JSON.stringify(readUpdateRestartHome(deps.home)) !== JSON.stringify(home)) throw new Error("changed");
|
|
251
|
+
} catch { throw new UpdateRestartEligibilityError("service"); }
|
|
206
252
|
return true;
|
|
207
253
|
}
|
|
208
254
|
|
|
@@ -213,7 +259,7 @@ export function restartFromCurrentInstallation(candidate: UpdateRestartCandidate
|
|
|
213
259
|
return runUpdateRestart(candidate, deadlineAt, {
|
|
214
260
|
now: Date.now,
|
|
215
261
|
acquire: () => acquireOwnershipMutationLease(serviceStatePaths(), { waitMs: Math.max(0, Math.min(2000, deadlineAt - Date.now())) }),
|
|
216
|
-
home: readUpdateRestartHome, checkHome: assertUpdateRestartHome, runtime: readRuntimePort, standalone,
|
|
262
|
+
home: readUpdateRestartHome, checkHome: home => assertUpdateRestartHome(home, deadlineAt), runtime: readRuntimePort, standalone: target => standalone(target, deadlineAt, { expectedHome: candidate.home }),
|
|
217
263
|
runtimeReady: () => isRealBunBinary(executable),
|
|
218
264
|
stop: async (target, deadline, beforeStop) => {
|
|
219
265
|
const token = configuredAdminToken();
|
package/src/cli/v2.ts
CHANGED
|
@@ -15,15 +15,14 @@ import { parseV2Command, V2_USAGE, type V2ParsedCommand } from "./v2-input";
|
|
|
15
15
|
import { handleV2RuntimeCommand } from "./v2-runtime";
|
|
16
16
|
import { localV2State, v2LocalSyncResult, type V2LocalSync } from "./v2-local-output";
|
|
17
17
|
import { execFileSync } from "node:child_process";
|
|
18
|
-
import {
|
|
19
|
-
import { activeCodexConfigPath, getAgentsEnabled, getAgentsMaxDepth, getLogicalMaxThreads, getMultiAgentModeHintText, getSubagentDeveloperInstructions, hasAgentsMaxThreads, isMultiAgentV2Enabled, setMultiAgentModeHintText, transitionMultiAgentV2 } from "../codex/features";
|
|
18
|
+
import { getAgentsEnabled, getAgentsMaxDepth, getLogicalMaxThreads, getMultiAgentModeHintText, getSubagentDeveloperInstructions, hasAgentsMaxThreads, isMultiAgentV2Enabled, setMultiAgentModeHintText, transitionMultiAgentV2 } from "../codex/features";
|
|
20
19
|
|
|
21
20
|
import { commandInvocation, type SpawnInvocation } from "../lib/win-exec";
|
|
22
21
|
import { deleteConfigTopLevelKey, loadConfig, saveConfig } from "../config";
|
|
23
22
|
import { resolveAndPersistCodexRuntime, type ResolveCodexRuntimeDeps } from "../codex/runtime";
|
|
24
23
|
|
|
25
24
|
export interface V2CliDeps {
|
|
26
|
-
execFile?: (file: string, args: string[], options?: SpawnInvocation["options"]) => void;
|
|
25
|
+
execFile?: (file: string, args: string[], options?: SpawnInvocation["options"] & { env?: NodeJS.ProcessEnv }) => void;
|
|
27
26
|
isEnabled?: typeof isMultiAgentV2Enabled;
|
|
28
27
|
hasMaxThreads?: typeof hasAgentsMaxThreads;
|
|
29
28
|
sync?: (port?: number) => Promise<unknown>;
|
|
@@ -35,7 +34,8 @@ export interface V2CliDeps {
|
|
|
35
34
|
|
|
36
35
|
export type CodexFeaturesInvocationDeps =
|
|
37
36
|
& Parameters<typeof commandInvocation>[3]
|
|
38
|
-
& Pick<ResolveCodexRuntimeDeps, "existsSync" | "execFileSync" | "configDir" | "readFileSync"
|
|
37
|
+
& Pick<ResolveCodexRuntimeDeps, "existsSync" | "execFileSync" | "configDir" | "readFileSync">
|
|
38
|
+
& { requireAvailable?: boolean };
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* Shared invocation for `codex features enable|disable <feature>` — the single
|
|
@@ -51,14 +51,18 @@ export function codexFeaturesInvocation(
|
|
|
51
51
|
platform: NodeJS.Platform = process.platform,
|
|
52
52
|
deps: CodexFeaturesInvocationDeps = {},
|
|
53
53
|
): SpawnInvocation {
|
|
54
|
-
const
|
|
54
|
+
const resolved = resolveAndPersistCodexRuntime({
|
|
55
55
|
env: deps.env ?? process.env,
|
|
56
56
|
platform,
|
|
57
57
|
existsSync: deps.existsSync,
|
|
58
58
|
execFileSync: deps.execFileSync,
|
|
59
59
|
configDir: deps.configDir,
|
|
60
60
|
readFileSync: deps.readFileSync,
|
|
61
|
-
})
|
|
61
|
+
});
|
|
62
|
+
if (deps.requireAvailable && resolved.runtime.source === "fallback" && resolved.runtime.version === null) {
|
|
63
|
+
throw Object.assign(new Error("spawn codex ENOENT"), { code: "ENOENT" });
|
|
64
|
+
}
|
|
65
|
+
const command = resolved.runtime.command || "codex";
|
|
62
66
|
return commandInvocation(command, ["features", action, feature], platform, deps);
|
|
63
67
|
}
|
|
64
68
|
|
|
@@ -70,27 +74,30 @@ export function codexFeaturesInvocation(
|
|
|
70
74
|
*/
|
|
71
75
|
export function runCodexFeaturesCommand(
|
|
72
76
|
action: "enable" | "disable",
|
|
73
|
-
feature: string
|
|
77
|
+
feature: string,
|
|
78
|
+
env: NodeJS.ProcessEnv,
|
|
79
|
+
validateBeforeSpawn: () => void,
|
|
80
|
+
preparedInvocation?: SpawnInvocation,
|
|
74
81
|
): void {
|
|
75
|
-
const inv = codexFeaturesInvocation(action, feature);
|
|
82
|
+
const inv = preparedInvocation ?? codexFeaturesInvocation(action, feature, process.platform, { env });
|
|
83
|
+
validateBeforeSpawn();
|
|
76
84
|
execFileSync(inv.file, inv.args,
|
|
77
85
|
{
|
|
78
86
|
stdio: ["ignore", "pipe", "pipe"], timeout: 15_000, windowsHide: true, encoding: "utf8",
|
|
79
|
-
// The reader resolves $CODEX_HOME at call time (including the WSL Windows-home
|
|
80
|
-
// detection); force the same home on the child so it never toggles a different
|
|
81
|
-
// config than the one the postcondition re-reads.
|
|
82
|
-
env: { ...process.env, CODEX_HOME: dirname(activeCodexConfigPath()) },
|
|
83
87
|
...inv.options,
|
|
88
|
+
env,
|
|
84
89
|
});
|
|
85
90
|
}
|
|
86
91
|
|
|
87
|
-
function runCodexFeatures(action: "enable" | "disable", deps: V2CliDeps): void {
|
|
92
|
+
function runCodexFeatures(action: "enable" | "disable", deps: V2CliDeps, env: NodeJS.ProcessEnv, validateBeforeSpawn: () => void): void {
|
|
88
93
|
if (deps.execFile) {
|
|
89
|
-
const inv =
|
|
90
|
-
|
|
94
|
+
const inv = deps.featuresInvocation ? deps.featuresInvocation(action)
|
|
95
|
+
: codexFeaturesInvocation(action, "multi_agent_v2", process.platform, { env });
|
|
96
|
+
validateBeforeSpawn();
|
|
97
|
+
deps.execFile(inv.file, inv.args, { ...inv.options, env });
|
|
91
98
|
return;
|
|
92
99
|
}
|
|
93
|
-
runCodexFeaturesCommand(action);
|
|
100
|
+
runCodexFeaturesCommand(action, "multi_agent_v2", env, validateBeforeSpawn);
|
|
94
101
|
}
|
|
95
102
|
|
|
96
103
|
export function v2StatusLine(enabled: boolean): string {
|
|
@@ -182,7 +189,7 @@ export async function cmdV2(args: string[], deps: V2CliDeps = {}, findPort?: ()
|
|
|
182
189
|
}
|
|
183
190
|
if (parsed.verb === "threads") {
|
|
184
191
|
const enabled = (deps.isEnabled ?? isMultiAgentV2Enabled)();
|
|
185
|
-
const result = transitionMultiAgentV2(enabled, next => runCodexFeatures(next ? "enable" : "disable", deps), { threadLimit: parsed.value });
|
|
192
|
+
const result = transitionMultiAgentV2(enabled, (next, env, validate) => runCodexFeatures(next ? "enable" : "disable", deps, env, validate), { threadLimit: parsed.value });
|
|
186
193
|
if (!result.ok) return emit(false, ["Unable to update the native thread limit. Read ocx v2 status before retrying."]);
|
|
187
194
|
changed = result.changed;
|
|
188
195
|
return emit(true, [changed ? `max_threads = ${parsed.value} (${enabled ? "v2" : "v1"}) — applies to new sessions.` : `max_threads already ${parsed.value} — nothing to do.`]);
|
|
@@ -194,7 +201,7 @@ export async function cmdV2(args: string[], deps: V2CliDeps = {}, findPort?: ()
|
|
|
194
201
|
changed = (config.multiAgentMode ?? "default") !== parsed.value;
|
|
195
202
|
if (parsed.value !== "default") {
|
|
196
203
|
const target = parsed.value === "v2" && config.keepNativeChatGptOnV1 !== true;
|
|
197
|
-
const transition = transitionMultiAgentV2(target, next => runCodexFeatures(next ? "enable" : "disable", deps));
|
|
204
|
+
const transition = transitionMultiAgentV2(target, (next, env, validate) => runCodexFeatures(next ? "enable" : "disable", deps, env, validate));
|
|
198
205
|
if (!transition.ok) { changed = false; return emit(false, ["Native mode transition failed. Read ocx v2 status before retrying."]); }
|
|
199
206
|
changed ||= transition.changed;
|
|
200
207
|
}
|
|
@@ -207,7 +214,7 @@ export async function cmdV2(args: string[], deps: V2CliDeps = {}, findPort?: ()
|
|
|
207
214
|
unchangedKeep = (config.keepNativeChatGptOnV1 === true) === parsed.value;
|
|
208
215
|
changed = !unchangedKeep;
|
|
209
216
|
if (parsed.value && requiresGlobalV2Disabled(config.multiAgentMode, true)) {
|
|
210
|
-
const transition = transitionMultiAgentV2(false, next => runCodexFeatures(next ? "enable" : "disable", deps));
|
|
217
|
+
const transition = transitionMultiAgentV2(false, (next, env, validate) => runCodexFeatures(next ? "enable" : "disable", deps, env, validate));
|
|
211
218
|
if (!transition.ok) { changed = false; return emit(false, ["Native hybrid-mode transition failed. Read ocx v2 status before retrying."]); }
|
|
212
219
|
changed ||= transition.changed;
|
|
213
220
|
}
|
|
@@ -222,7 +229,7 @@ export async function cmdV2(args: string[], deps: V2CliDeps = {}, findPort?: ()
|
|
|
222
229
|
if (want && requiresGlobalV2Disabled(config.multiAgentMode, config.keepNativeChatGptOnV1 === true)) {
|
|
223
230
|
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."]);
|
|
224
231
|
}
|
|
225
|
-
const transition = transitionMultiAgentV2(want, next => runCodexFeatures(next ? "enable" : "disable", deps));
|
|
232
|
+
const transition = transitionMultiAgentV2(want, (next, env, validate) => runCodexFeatures(next ? "enable" : "disable", deps, env, validate));
|
|
226
233
|
if (!transition.ok) return emit(false, ["Native feature transition failed. Read ocx v2 status before retrying."]);
|
|
227
234
|
changed = transition.changed;
|
|
228
235
|
if (!changed) return emit(true, [`multi_agent_v2 already ${want ? "ON" : "OFF"} — nothing to do.`]);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { readConfigDiagnostics } from "../config/diagnostics";
|
|
2
|
+
import { readRuntimePort } from "../config/process-state";
|
|
3
|
+
import { packageVersion } from "./help";
|
|
4
|
+
import { proxyIdentityAt } from "../server/proxy-liveness";
|
|
5
|
+
import { computeVersionSkew } from "./version-skew";
|
|
6
|
+
|
|
7
|
+
const LIFECYCLE = new Set(["start", "stop", "restart", "update", "service"]);
|
|
8
|
+
const NOTICE_DEADLINE_MS = 200;
|
|
9
|
+
let printed = false;
|
|
10
|
+
|
|
11
|
+
export function shouldNoticeVersionSkew(command: string | undefined, args: string[]): boolean {
|
|
12
|
+
const boundary = args.indexOf("--");
|
|
13
|
+
const prefix = boundary < 0 ? args : args.slice(0, boundary);
|
|
14
|
+
return LIFECYCLE.has(command ?? "")
|
|
15
|
+
&& !prefix.some(arg => arg === "--json" || arg === "--help" || arg === "-h")
|
|
16
|
+
&& prefix[1] !== "help";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface VersionSkewNoticeIo {
|
|
20
|
+
readRuntime?: typeof readRuntimePort;
|
|
21
|
+
readConfig?: typeof readConfigDiagnostics;
|
|
22
|
+
probe?: typeof proxyIdentityAt;
|
|
23
|
+
cliVersion?: () => string;
|
|
24
|
+
now?: () => number;
|
|
25
|
+
warn?: (message: string) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* One proxyIdentityAt (src/server/proxy-liveness.ts) call validates /healthz identity,
|
|
30
|
+
* expected PID and terminal-safe version within the total diagnostic budget.
|
|
31
|
+
*/
|
|
32
|
+
export async function maybeNoticeVersionSkew(
|
|
33
|
+
command: string | undefined, args: string[], io: VersionSkewNoticeIo = {},
|
|
34
|
+
): Promise<void> {
|
|
35
|
+
if (printed || !shouldNoticeVersionSkew(command, args)) return;
|
|
36
|
+
const now = io.now ?? Date.now;
|
|
37
|
+
const deadlineAt = now() + NOTICE_DEADLINE_MS;
|
|
38
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
39
|
+
try {
|
|
40
|
+
const runtime = (io.readRuntime ?? readRuntimePort)();
|
|
41
|
+
let port: number;
|
|
42
|
+
let hostname: string | undefined;
|
|
43
|
+
let expectedPid: number | undefined;
|
|
44
|
+
if (runtime) {
|
|
45
|
+
({ port, hostname } = runtime);
|
|
46
|
+
expectedPid = runtime.pid;
|
|
47
|
+
} else {
|
|
48
|
+
const diagnostics = (io.readConfig ?? readConfigDiagnostics)();
|
|
49
|
+
if (diagnostics.error) return;
|
|
50
|
+
port = diagnostics.config.port ?? 10100;
|
|
51
|
+
hostname = diagnostics.config.hostname;
|
|
52
|
+
}
|
|
53
|
+
const remaining = Math.floor(deadlineAt - now());
|
|
54
|
+
if (remaining <= 0) return;
|
|
55
|
+
const cliVersion = (io.cliVersion ?? packageVersion)();
|
|
56
|
+
if (now() >= deadlineAt) return;
|
|
57
|
+
const identity = await Promise.race([
|
|
58
|
+
(io.probe ?? proxyIdentityAt)(port, { hostname, expectedPid }, {
|
|
59
|
+
timeoutMs: Math.max(1, Math.floor(deadlineAt - now())),
|
|
60
|
+
attempts: 1, deadlineAt, nowFn: now,
|
|
61
|
+
}),
|
|
62
|
+
new Promise<null>(resolve => {
|
|
63
|
+
timer = setTimeout(() => resolve(null), Math.max(1, deadlineAt - now()));
|
|
64
|
+
}),
|
|
65
|
+
]);
|
|
66
|
+
if (printed || !identity || now() >= deadlineAt) return;
|
|
67
|
+
const skew = computeVersionSkew(cliVersion, identity.version);
|
|
68
|
+
if (!skew.skewed) return;
|
|
69
|
+
printed = true;
|
|
70
|
+
(io.warn ?? console.error)(`ocx ${cliVersion} does not match the running proxy ${identity.version}. `
|
|
71
|
+
+ "Check which opencodex installation you meant to use (`ocx status` shows both).");
|
|
72
|
+
} catch {
|
|
73
|
+
// Best-effort diagnostics must not fail a lifecycle command or change its exit code.
|
|
74
|
+
} finally {
|
|
75
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { getModelMetadata, resolveMetadataProvider } from "../../generated/model-metadata";
|
|
3
3
|
import { SCHEMA_REQUIRED_OUTPUT_BUDGET } from "./constants";
|
|
4
4
|
import { expandFastExportModels } from "./fast-models";
|
|
5
|
+
import { sanitizeCodexReasoningEfforts } from "../../reasoning-effort";
|
|
5
6
|
import type { OpencodeCatalogModel, ExportModel, ExportClientId, ManagedContribution } from "./contracts";
|
|
6
7
|
import type { OcxConfig } from "../../types";
|
|
7
8
|
import { shouldInjectApiAuthHeader } from "../../codex/inject";
|
|
@@ -84,6 +85,21 @@ export function inputModalitiesForClient(
|
|
|
84
85
|
return kept.length > 0 ? kept : null;
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Effort ladder a ZCode surface can offer as picker values, or undefined when the row
|
|
90
|
+
* has nothing selectable.
|
|
91
|
+
*
|
|
92
|
+
* Both ZCode exports — the legacy `v2/config.json` block's `reasoning.variants` and the
|
|
93
|
+
* `v2/provider_config.json` store's `optionSpecs.reasoningLevel.values` — carry the same
|
|
94
|
+
* Codex vocabulary: the client validates the chosen level against the emitted list and
|
|
95
|
+
* forwards it verbatim, and `/v1/responses` reads that vocabulary natively. `none` is a
|
|
96
|
+
* Codex omit-sentinel, not a picker option, so it never survives into the values.
|
|
97
|
+
*/
|
|
98
|
+
export function zcodeSelectableEfforts(efforts: readonly string[] | undefined): string[] | undefined {
|
|
99
|
+
const selectable = sanitizeCodexReasoningEfforts(efforts)?.filter(effort => effort !== "none");
|
|
100
|
+
return selectable && selectable.length > 0 ? selectable : undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
87
103
|
/**
|
|
88
104
|
* Input modalities opencode's model schema accepts (opencode.ai/config.json, both
|
|
89
105
|
* `modalities.input` and `modalities.output`). Wider than our internal `text | image | audio`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// ZCode personal provider store export.
|
|
2
2
|
import type { ExportContext, ManagedContribution, ManagedFragment } from "./contracts";
|
|
3
|
-
import { authoritativeContextWindow, inputModalitiesForClient, normalizeExportModels } from "./model-metadata";
|
|
3
|
+
import { authoritativeContextWindow, inputModalitiesForClient, normalizeExportModels, zcodeSelectableEfforts } from "./model-metadata";
|
|
4
4
|
import { OPENCODE_PROVIDER_ID, LOOPBACK_API_KEY_PLACEHOLDER } from "./constants";
|
|
5
5
|
import { formatSelectorConjunction } from "../../integrations/merge";
|
|
6
6
|
|
|
@@ -49,10 +49,30 @@ export interface ZcodeStoreProviderRule {
|
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* One model rule, as the client's own store loader accepts it.
|
|
54
|
+
*
|
|
55
|
+
* The capability fields are the client's schema, not ours: ZCode 3.14's CLI
|
|
56
|
+
* bundle parses each `providerModelRules` entry with a strict object whose
|
|
57
|
+
* `config.properties` admits `inputFormat.{supportsText..supportsPdf}` (booleans)
|
|
58
|
+
* and whose `config.optionSpecs` admits `reasoningLevel.values` (non-empty,
|
|
59
|
+
* deduplicated) plus an optional compiled `map`. With no `map`, the chosen level
|
|
60
|
+
* is validated against `values` and forwarded verbatim to the wire field the
|
|
61
|
+
* `openai-responses` api type uses — `reasoning.effort`, which is exactly the
|
|
62
|
+
* Codex vocabulary our ladder already carries, so no mapping is emitted.
|
|
63
|
+
*/
|
|
52
64
|
export interface ZcodeStoreModelRule {
|
|
53
65
|
providerId: string;
|
|
54
66
|
modelId: string;
|
|
55
|
-
config: {
|
|
67
|
+
config: {
|
|
68
|
+
properties: {
|
|
69
|
+
contextWindow?: number;
|
|
70
|
+
inputFormat?: { supportsImage: true };
|
|
71
|
+
};
|
|
72
|
+
optionSpecs?: {
|
|
73
|
+
reasoningLevel: { values: string[] };
|
|
74
|
+
};
|
|
75
|
+
};
|
|
56
76
|
}
|
|
57
77
|
|
|
58
78
|
/**
|
|
@@ -77,7 +97,7 @@ export function zcodeStoreSchemaEstablished(parsed: unknown): boolean {
|
|
|
77
97
|
* carrying only the model would match another provider's rule for the same
|
|
78
98
|
* model. The conjunction grammar reserves `,` and `]`, and a model id holding
|
|
79
99
|
* either cannot be addressed unambiguously — that row ships without its
|
|
80
|
-
*
|
|
100
|
+
* model rule rather than with a selector that points somewhere else.
|
|
81
101
|
*/
|
|
82
102
|
function modelRuleSelector(modelId: string): string | null {
|
|
83
103
|
return formatSelectorConjunction([
|
|
@@ -128,12 +148,18 @@ export function buildZcodeStoreProviderRule(ctx: ExportContext): ZcodeStoreProvi
|
|
|
128
148
|
|
|
129
149
|
/**
|
|
130
150
|
* Everything this project owns inside the store: one provider rule, plus one
|
|
131
|
-
* model rule per row that has
|
|
151
|
+
* model rule per row that has anything authoritative to assert.
|
|
132
152
|
*
|
|
133
|
-
* A
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
153
|
+
* A model rule now exists when the row has any of: an authoritative context
|
|
154
|
+
* window, image input, or a selectable effort ladder. A row with none of the
|
|
155
|
+
* three ships no rule at all, which is the same no-guessed-capability rule the
|
|
156
|
+
* legacy block follows. Image input is asserted only when the catalog declares
|
|
157
|
+
* it — `supportsImage: true` is what gates the client's attachment UI, and a
|
|
158
|
+
* text-only row keeps the client's own defaults rather than being pinned to
|
|
159
|
+
* `false`. `reasoningLevel.values` carries the same ladder (minus the `none`
|
|
160
|
+
* sentinel) the legacy block's `reasoning.variants` carries, so both surfaces
|
|
161
|
+
* offer the picker the catalog declared instead of the store silently losing
|
|
162
|
+
* reasoning and vision the way #5348 lost the provider itself.
|
|
137
163
|
*/
|
|
138
164
|
export function buildZcodeStoreContribution(ctx: ExportContext): ManagedContribution {
|
|
139
165
|
const fragments: ManagedFragment[] = [{
|
|
@@ -141,15 +167,24 @@ export function buildZcodeStoreContribution(ctx: ExportContext): ManagedContribu
|
|
|
141
167
|
value: buildZcodeStoreProviderRule(ctx),
|
|
142
168
|
}];
|
|
143
169
|
for (const model of normalizeExportModels(ctx.models)) {
|
|
144
|
-
|
|
170
|
+
const input = inputModalitiesForClient("pi", model.inputModalities);
|
|
171
|
+
if (input === null) continue;
|
|
145
172
|
const contextWindow = authoritativeContextWindow(model.contextWindow);
|
|
146
|
-
|
|
173
|
+
const supportsImage = input.includes("image");
|
|
174
|
+
const efforts = zcodeSelectableEfforts(model.reasoningEfforts);
|
|
175
|
+
if (contextWindow === undefined && !supportsImage && efforts === undefined) continue;
|
|
147
176
|
const selector = modelRuleSelector(model.namespaced);
|
|
148
177
|
if (selector === null) continue;
|
|
149
178
|
const rule: ZcodeStoreModelRule = {
|
|
150
179
|
providerId: OPENCODE_PROVIDER_ID,
|
|
151
180
|
modelId: model.namespaced,
|
|
152
|
-
config: {
|
|
181
|
+
config: {
|
|
182
|
+
properties: {
|
|
183
|
+
...(contextWindow !== undefined ? { contextWindow } : {}),
|
|
184
|
+
...(supportsImage ? { inputFormat: { supportsImage: true } } : {}),
|
|
185
|
+
},
|
|
186
|
+
...(efforts !== undefined ? { optionSpecs: { reasoningLevel: { values: efforts } } } : {}),
|
|
187
|
+
},
|
|
153
188
|
};
|
|
154
189
|
fragments.push({ path: [...ZCODE_STORE_MODEL_RULES_PATH, selector], value: rule });
|
|
155
190
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// ZCode config export.
|
|
2
2
|
import type { ExportContext, ManagedContribution } from "./contracts";
|
|
3
|
-
import { normalizeExportModels, inputModalitiesForClient, exportModelLabel, authoritativeContextWindow, singleFragment } from "./model-metadata";
|
|
4
|
-
import { sanitizeCodexReasoningEfforts } from "../../reasoning-effort";
|
|
3
|
+
import { normalizeExportModels, inputModalitiesForClient, exportModelLabel, authoritativeContextWindow, singleFragment, zcodeSelectableEfforts } from "./model-metadata";
|
|
5
4
|
import { OPENCODE_PROVIDER_ID, LOOPBACK_API_KEY_PLACEHOLDER } from "./constants";
|
|
6
5
|
|
|
7
6
|
|
|
@@ -86,9 +85,8 @@ export function buildZcodeClientConfig(ctx: ExportContext): ZcodeGeneratedConfig
|
|
|
86
85
|
// `ultra` when present: ZCode forwards the selected variant to the wire field its
|
|
87
86
|
// kind uses — `reasoning.effort` on `openai`, which is what `/v1/responses` reads
|
|
88
87
|
// natively. Set `defaultVariant` only when it survives that filter.
|
|
89
|
-
const efforts =
|
|
90
|
-
|
|
91
|
-
if (efforts && efforts.length > 0) {
|
|
88
|
+
const efforts = zcodeSelectableEfforts(model.reasoningEfforts);
|
|
89
|
+
if (efforts !== undefined) {
|
|
92
90
|
const defaultVariant = model.defaultReasoningEffort?.trim().toLowerCase();
|
|
93
91
|
entry.reasoning = {
|
|
94
92
|
enabled: true,
|
|
@@ -1367,7 +1367,7 @@ function buildAsideContribution(ctx: ExportContext): ManagedContribution {
|
|
|
1367
1367
|
* the keyed `providers`, the `models` ARRAY whose identity is `id`, the
|
|
1368
1368
|
* `openai-completions` dialect, the loopback placeholder, and the
|
|
1369
1369
|
* `thinkingLevelMap` levels. Evidence:
|
|
1370
|
-
* `devlog/
|
|
1370
|
+
* `devlog/_fin/260912_omo_client_integration/001_omo_contract.md`.
|
|
1371
1371
|
*
|
|
1372
1372
|
* The flag is passed HERE as well as in the spec's `build`, which is the one
|
|
1373
1373
|
* thing Prime and Aside do not do. They pass the default on both paths, so they
|
package/src/clients/lazycodex.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Detection of Codex-based omo (LazyCodex), the variant that reads Codex role files and
|
|
3
|
-
* `codex.agents.<role>.model` in `~/.omo/omo.jsonc`.
|
|
3
|
+
* `"[codex]".agents.<role>.model` in `~/.omo/omo.jsonc`.
|
|
4
4
|
*
|
|
5
5
|
* "omo" names three products. Pi-based omo (senpi) owns `~/.omo/agent` and is the omo file
|
|
6
6
|
* integration in `src/integrations/registry.ts`; OpenCode-based omo (oh-my-opencode) keeps its
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* omo (Codex / LazyCodex)'s per-role model setting, `codex.agents.<role>.model` in `~/.omo/omo.jsonc`.
|
|
2
|
+
* omo (Codex / LazyCodex)'s per-role model setting, `"[codex]".agents.<role>.model` in `~/.omo/omo.jsonc`.
|
|
3
|
+
* The section name carries its brackets: LazyCodex reads `[codex]`, and its strict loader drops a
|
|
4
|
+
* bare `codex` key as unknown, so a pick written there never reaches a role.
|
|
3
5
|
*
|
|
4
6
|
* LazyCodex 5.1.1 and later reads it, and callers reach this only after `detectLazyCodex`
|
|
5
7
|
* says LazyCodex is installed. The file is omo's, so this writes only on an explicit
|
|
@@ -30,6 +32,17 @@ export function omoJsoncPath(env: NodeJS.ProcessEnv = process.env, home: string
|
|
|
30
32
|
|
|
31
33
|
type JsonObject = Record<string, unknown>;
|
|
32
34
|
|
|
35
|
+
const CODEX_SECTION = "[codex]";
|
|
36
|
+
|
|
37
|
+
/** The reasoning levels LazyCodex accepts for a role; it names Codex's `none` `off`. */
|
|
38
|
+
const LAZYCODEX_REASONING = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
39
|
+
|
|
40
|
+
/** The `reasoning` value LazyCodex reads for a Codex effort, or null when it has no such level. */
|
|
41
|
+
export function omoReasoningFor(effort: string): string | null {
|
|
42
|
+
const level = effort === "none" ? "off" : effort;
|
|
43
|
+
return LAZYCODEX_REASONING.has(level) ? level : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
33
46
|
function isObject(value: unknown): value is JsonObject {
|
|
34
47
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
35
48
|
}
|
|
@@ -71,7 +84,7 @@ function load(path: string): Loaded {
|
|
|
71
84
|
}
|
|
72
85
|
|
|
73
86
|
function agentsOf(doc: JsonObject): JsonObject | null | undefined {
|
|
74
|
-
const codex = doc
|
|
87
|
+
const codex = doc[CODEX_SECTION];
|
|
75
88
|
if (codex === undefined) return undefined;
|
|
76
89
|
if (!isObject(codex)) return null;
|
|
77
90
|
const agents = codex.agents;
|
|
@@ -104,21 +117,33 @@ function serialize(text: string, doc: JsonObject): string {
|
|
|
104
117
|
return text.startsWith("\ufeff") ? `\ufeff${out}` : out;
|
|
105
118
|
}
|
|
106
119
|
|
|
107
|
-
|
|
120
|
+
/**
|
|
121
|
+
* `reasoning` left undefined keeps the entry's level; null removes it, so a level LazyCodex cannot
|
|
122
|
+
* express does not leave a stale one behind for its next install to apply.
|
|
123
|
+
*/
|
|
124
|
+
export function writeOmoRoleModel(
|
|
125
|
+
role: string,
|
|
126
|
+
model: string,
|
|
127
|
+
path: string = omoJsoncPath(),
|
|
128
|
+
reasoning?: string | null,
|
|
129
|
+
): OmoRoleModelWriteStatus {
|
|
108
130
|
const loaded = load(path);
|
|
109
131
|
if (loaded.kind !== "document") return loaded.kind === "comments" ? "skipped_comments" : loaded.kind;
|
|
110
132
|
const { doc, text } = loaded;
|
|
111
133
|
// Only an absent key is created; an explicit null is a value the reader calls invalid.
|
|
112
|
-
const codex = doc
|
|
134
|
+
const codex = doc[CODEX_SECTION] === undefined ? {} : doc[CODEX_SECTION];
|
|
113
135
|
if (!isObject(codex)) return "invalid";
|
|
114
136
|
const agents = codex.agents === undefined ? {} : codex.agents;
|
|
115
137
|
if (!isObject(agents)) return "invalid";
|
|
116
138
|
const entry = agents[role] === undefined ? {} : agents[role];
|
|
117
139
|
if (!isObject(entry)) return "invalid";
|
|
118
|
-
|
|
119
|
-
|
|
140
|
+
const next: JsonObject = { ...entry, model };
|
|
141
|
+
if (reasoning === null) delete next.reasoning;
|
|
142
|
+
else if (reasoning !== undefined) next.reasoning = reasoning;
|
|
143
|
+
if (entry.model === model && entry.reasoning === next.reasoning) return "unchanged";
|
|
144
|
+
agents[role] = next;
|
|
120
145
|
codex.agents = agents;
|
|
121
|
-
doc
|
|
146
|
+
doc[CODEX_SECTION] = codex;
|
|
122
147
|
assertIntegrationWriteOwnership(path);
|
|
123
148
|
atomicWriteFileNoFollowUnclaimed(path, serialize(text, doc));
|
|
124
149
|
return "written";
|