@bitkyc08/opencodex 2.59.0 → 2.61.0-preview.20260922
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/AGENTS_INSTALL.md +64 -0
- package/README.md +28 -1
- package/bin/ocx.mjs +382 -209
- package/gui/dist/assets/App-E64Rzjap.js +50 -0
- package/gui/dist/assets/Tray-_nfzD8k4.js +1 -0
- package/gui/dist/assets/index-DpdfZWMK.js +86 -0
- package/gui/dist/assets/index-_bpvxJu0.css +1 -0
- package/gui/dist/assets/usage-companion-chart-DtoK7T6h.js +1 -0
- package/gui/dist/favicon.png +0 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/stepfun-color.svg +1 -0
- package/package.json +8 -1
- package/src/adapters/anthropic-image-codec.ts +16 -2
- package/src/adapters/anthropic-image-normalize.ts +49 -2
- package/src/adapters/anthropic.ts +20 -1
- package/src/adapters/base.ts +23 -0
- package/src/adapters/coding-agent/protocol.ts +36 -6
- package/src/adapters/coding-agent/turn.ts +32 -4
- package/src/adapters/command-code.ts +52 -4
- package/src/adapters/cursor/catalog.ts +51 -7
- package/src/adapters/cursor/checkpoint-store.ts +3 -0
- package/src/adapters/cursor/discovery.ts +11 -8
- package/src/adapters/cursor/live-transport.ts +26 -9
- package/src/adapters/cursor/protobuf-request.ts +6 -3
- package/src/adapters/cursor/request-builder.ts +20 -4
- package/src/adapters/cursor/transport.ts +19 -0
- package/src/adapters/cursor.ts +14 -3
- package/src/adapters/declaration-carrier.ts +45 -0
- package/src/adapters/devin/cloud-direct/chat.ts +3 -1
- package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +42 -5
- package/src/adapters/devin.ts +125 -36
- package/src/adapters/google-antigravity-replay.ts +1 -1
- package/src/adapters/google-antigravity-wire.ts +34 -7
- package/src/adapters/google-errors.ts +7 -1
- package/src/adapters/google-http.ts +49 -10
- package/src/adapters/google-tool-schema.ts +595 -31
- package/src/adapters/google-wire-compiler.ts +93 -10
- package/src/adapters/google-wire-shape.ts +461 -0
- package/src/adapters/google.ts +66 -11
- package/src/adapters/image.ts +4 -1
- package/src/adapters/input-media-guard.ts +21 -9
- package/src/adapters/kiro/usage.ts +3 -2
- package/src/adapters/kiro-tool-fallback.ts +1 -1
- package/src/adapters/ollama-native.ts +6 -0
- package/src/adapters/openai-chat/developer-role.ts +61 -0
- package/src/adapters/openai-chat/messages.ts +46 -27
- package/src/adapters/openai-chat/parallel-tool-calls.ts +32 -0
- package/src/adapters/openai-chat/passthrough.ts +33 -9
- package/src/adapters/openai-chat/reasoning-wire.ts +89 -0
- package/src/adapters/openai-chat-images.ts +3 -1
- package/src/adapters/openai-chat.ts +23 -58
- package/src/adapters/openai-responses/image-gen.ts +8 -6
- package/src/adapters/openai-responses/passthrough.ts +17 -3
- package/src/adapters/openai-responses/reasoning.ts +7 -0
- package/src/adapters/opencode-go-additional-tools.ts +12 -2
- package/src/adapters/registry.ts +3 -2
- package/src/adapters/run-turn-queue.ts +178 -29
- package/src/adapters/xai-web-search.ts +16 -1
- package/src/bridge/errors.ts +8 -2
- package/src/bridge/response-json.ts +9 -1
- package/src/bridge/sse.ts +13 -151
- package/src/chat/inbound.ts +141 -5
- package/src/claude/desktop-3p.ts +7 -1
- package/src/claude/desktop-first-party.ts +183 -0
- package/src/claude/desktop-gateway-state.ts +41 -0
- package/src/claude/inbound-content-options.ts +6 -0
- package/src/claude/inbound.ts +32 -6
- package/src/claude/intercept/connect-proxy.ts +179 -0
- package/src/claude/intercept/listener.ts +122 -0
- package/src/claude/intercept/local-ca.ts +298 -0
- package/src/claude/intercept/runtime.ts +98 -0
- package/src/claude/intercept/settings.ts +189 -0
- package/src/cli/access.ts +87 -0
- package/src/cli/account-auth.ts +19 -0
- package/src/cli/account-extended.ts +4 -4
- package/src/cli/capabilities.ts +31 -0
- package/src/cli/claude-desktop.ts +206 -16
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/companion.ts +56 -0
- package/src/cli/dispatch.ts +46 -7
- package/src/cli/doctor.ts +28 -9
- package/src/cli/ensure-desired-integrations.ts +43 -5
- package/src/cli/help.ts +7 -9
- package/src/cli/hub.ts +3 -2
- package/src/cli/index.ts +203 -59
- package/src/cli/init.ts +8 -0
- package/src/cli/integrations.ts +7 -1
- package/src/cli/opencode.ts +2 -2
- package/src/cli/provider.ts +13 -1
- package/src/cli/registry.ts +41 -2
- package/src/cli/resolve.ts +230 -0
- package/src/cli/root.ts +24 -1
- package/src/cli/start-ownership-publication.ts +56 -0
- package/src/cli/status-probes.ts +2 -18
- package/src/cli/status.ts +62 -0
- package/src/cli/stop-report.ts +143 -0
- package/src/cli/uninstall-plan.ts +9 -0
- package/src/client/machine-api.ts +2 -2
- package/src/client/machine-listener.ts +4 -7
- package/src/client/runtime.ts +26 -2
- package/src/clients/aside-profiles.ts +4 -0
- package/src/clients/config-export/zcode-store.ts +157 -0
- package/src/clients/config-export.ts +36 -0
- package/src/codex/account-store.ts +65 -0
- package/src/codex/app-server-processes.ts +72 -40
- package/src/codex/auth-api/account-list.ts +19 -11
- package/src/codex/auth-api/login-flow.ts +6 -1
- package/src/codex/auth-api/pool-quota-probe.ts +30 -7
- package/src/codex/autostart-health.ts +28 -0
- package/src/codex/catalog/build-entries.ts +2 -2
- package/src/codex/catalog/effort.ts +3 -3
- package/src/codex/catalog/gather-capture.ts +21 -2
- package/src/codex/catalog/model-hints.ts +29 -28
- package/src/codex/catalog/parsing.ts +7 -0
- package/src/codex/catalog/provider-models.ts +43 -17
- package/src/codex/catalog/retained-sync.ts +24 -28
- package/src/codex/catalog/routed-gather.ts +19 -0
- package/src/codex/context-compat.ts +5 -2
- package/src/codex/convergence.ts +2 -2
- package/src/codex/desired-state.ts +4 -1
- package/src/codex/history-job.ts +6 -6
- package/src/codex/history-provider.ts +31 -166
- package/src/codex/history-rollout-read.ts +174 -0
- package/src/codex/inject/config-toml.ts +41 -6
- package/src/codex/inject/paginated-openai-compat.ts +90 -0
- package/src/codex/inject.ts +18 -15
- package/src/codex/injected-marker.ts +18 -0
- package/src/codex/internal/catalog-writer.ts +33 -1
- package/src/codex/main-account.ts +6 -0
- package/src/codex/model-cache.ts +99 -6
- package/src/codex/model-entitlement-admission.ts +59 -0
- package/src/codex/model-entitlements.ts +116 -54
- package/src/codex/native-main-admission.ts +83 -0
- package/src/codex/observed-model-denials.ts +101 -8
- package/src/codex/prompt-text-probe.ts +9 -6
- package/src/codex/routing/health-store.ts +39 -0
- package/src/codex/routing/selection.ts +37 -1
- package/src/codex/routing.ts +12 -42
- package/src/codex/shim-templates.ts +29 -3
- package/src/codex/shim.ts +1 -1
- package/src/codex/subagent-model-fallback.ts +22 -4
- package/src/combos/failover.ts +3 -0
- package/src/companion/settings.ts +132 -0
- package/src/config/admitted-identity.ts +222 -0
- package/src/config/atomic-write.ts +117 -5
- package/src/config/diagnostics.ts +22 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/load-degrade.ts +52 -7
- package/src/config/process-state.ts +1 -1
- package/src/config/proxy-env.ts +8 -2
- package/src/config/schema/compaction-triggers.ts +11 -0
- package/src/config/schema/config-schema.ts +31 -1
- package/src/config/schema/leaf-validators.ts +59 -0
- package/src/config.ts +3 -3
- package/src/generated/compatibility-version.json +604 -280
- package/src/grok/reset-coupons.ts +38 -19
- package/src/images/loop.ts +6 -1
- package/src/integrations/aside-profile-context.ts +37 -3
- package/src/integrations/aside-profile-journal.ts +68 -3
- package/src/integrations/aside-profiles.ts +128 -3
- package/src/integrations/config-io.ts +44 -10
- package/src/integrations/merge.ts +120 -13
- package/src/integrations/mutation-plan.ts +921 -0
- package/src/integrations/registry.ts +38 -0
- package/src/integrations/state.ts +78 -45
- package/src/integrations/target.ts +208 -0
- package/src/integrations/writer.ts +134 -110
- package/src/lab/conformance/fixture-provider.ts +5 -0
- package/src/lab/live/transport.ts +4 -0
- package/src/lab/live/types.ts +5 -0
- package/src/lab/subject/behavior-fingerprint.ts +1 -1
- package/src/lib/admin-secrets.ts +9 -1
- package/src/lib/browser-launch-notice.ts +59 -0
- package/src/lib/bun-runtime.ts +6 -2
- package/src/lib/debug-log-buffer.ts +6 -1
- package/src/lib/debug.ts +63 -0
- package/src/lib/errors.ts +79 -0
- package/src/lib/http-response-semantics.ts +57 -0
- package/src/lib/lab-live-pinned-sender.ts +26 -12
- package/src/lib/open-url.ts +51 -7
- package/src/lib/package-tree-integrity.ts +2 -1
- package/src/lib/package-version.ts +8 -0
- package/src/lib/pinned-http.ts +142 -2
- package/src/lib/plain-data.ts +103 -0
- package/src/lib/process-control.ts +13 -5
- package/src/lib/provider-egress.ts +310 -0
- package/src/lib/provider-outbound.ts +109 -16
- package/src/lib/proxy-env.ts +82 -7
- package/src/lib/request-execution-budget.ts +72 -0
- package/src/lib/request-failure-attribution.ts +183 -0
- package/src/lib/request-failure-model.ts +236 -0
- package/src/lib/request-resend-gate.ts +138 -0
- package/src/lib/socks5-fetch.ts +136 -26
- package/src/lib/spend-ledger-owner.ts +364 -0
- package/src/lib/spend-reservation-ledger.ts +218 -27
- package/src/lib/standalone.ts +16 -0
- package/src/lib/upstream-retry.ts +167 -16
- package/src/lib/windows-system-proxy.ts +16 -11
- package/src/lib/winsw.ts +2 -2
- package/src/oauth/callback-server.ts +4 -3
- package/src/oauth/generic-account-failover.ts +1 -0
- package/src/oauth/health.ts +12 -1
- package/src/oauth/index.ts +27 -108
- package/src/oauth/login-cli.ts +80 -29
- package/src/oauth/login-flow-state.ts +127 -0
- package/src/providers/api-key-resolve.ts +133 -0
- package/src/providers/api-key-selection.ts +5 -1
- package/src/providers/derive.ts +34 -17
- package/src/providers/devin-cli-authmode-migration.ts +14 -10
- package/src/providers/key-failover.ts +97 -19
- package/src/providers/key-store.ts +34 -110
- package/src/providers/model-rename-fields.ts +147 -0
- package/src/providers/model-rename-migration.ts +179 -38
- package/src/providers/model-rename-startup.ts +7 -5
- package/src/providers/openai-virtual-models.ts +42 -2
- package/src/providers/quota/antigravity.ts +22 -2
- package/src/providers/quota/vendor-probes-key.ts +38 -23
- package/src/providers/reasoning-metadata.ts +43 -18
- package/src/providers/registry/entries-core.ts +47 -20
- package/src/providers/registry/entries-extended.ts +63 -4
- package/src/providers/registry/model-ids.ts +168 -0
- package/src/providers/registry/model-seeds.ts +56 -10
- package/src/providers/registry/types.ts +2 -0
- package/src/providers/resolved-model-policy-merge.ts +167 -0
- package/src/providers/resolved-model-policy.ts +406 -0
- package/src/providers/stale-vision-classification-migration.ts +137 -0
- package/src/providers/xai-transport.ts +12 -1
- package/src/reasoning-effort.ts +8 -0
- package/src/responses/apply-patch-envelope.ts +0 -12
- package/src/responses/freeform-wrapper-scan.ts +279 -0
- package/src/responses/function-call-compat.ts +38 -1
- package/src/responses/inline-document.ts +65 -0
- package/src/responses/input-media.ts +42 -8
- package/src/responses/legacy-dotted-tool-name-repair.ts +134 -0
- package/src/responses/muse-tool-name-alias.ts +19 -0
- package/src/responses/parser-content.ts +8 -2
- package/src/responses/parser-tools.ts +3 -0
- package/src/responses/parser.ts +3 -1
- package/src/responses/progressive-freeform-input.ts +130 -0
- package/src/responses/reasoning-envelope.ts +30 -0
- package/src/responses/schema.ts +3 -0
- package/src/responses/state.ts +5 -12
- package/src/responses/tool-name-aliases.ts +15 -1
- package/src/router.ts +108 -117
- package/src/routing/compatibility/behavior.ts +9 -0
- package/src/routing/compatibility/subject.ts +16 -1
- package/src/server/adapter-resolve.ts +9 -0
- package/src/server/admission-model-scope.ts +219 -0
- package/src/server/audio-live.ts +9 -3
- package/src/server/audio-upstream.ts +18 -0
- package/src/server/auth-cors.ts +29 -0
- package/src/server/chat-completions.ts +60 -4
- package/src/server/chat-native.ts +19 -4
- package/src/server/claude-messages.ts +61 -20
- package/src/server/effort-row.ts +11 -3
- package/src/server/grok-responses-control-frame.ts +160 -1
- package/src/server/grok-responses-snapshot-repair.ts +113 -11
- package/src/server/gui-freshness.ts +103 -0
- package/src/server/gui-static.ts +7 -9
- package/src/server/images.ts +59 -6
- package/src/server/index/claude-intercept-lifecycle.ts +49 -0
- package/src/server/index/serve-options.ts +142 -39
- package/src/server/index/spend-ledger-lifecycle.ts +92 -0
- package/src/server/index/startup-warnings.ts +24 -0
- package/src/server/index/websocket-handler.ts +6 -1
- package/src/server/index.ts +34 -38
- package/src/server/lifecycle.ts +4 -4
- package/src/server/live-call-bindings.ts +6 -0
- package/src/server/live.ts +88 -3
- package/src/server/management/agent-settings-routes.ts +121 -36
- package/src/server/management/aside-profile-routes.ts +266 -7
- package/src/server/management/companion-routes.ts +77 -0
- package/src/server/management/config-routes.ts +18 -2
- package/src/server/management/context.ts +3 -0
- package/src/server/management/integration-routes.ts +287 -5
- package/src/server/management/logs-usage-routes.ts +19 -0
- package/src/server/management/metrics-routes.ts +20 -0
- package/src/server/management/model-rows.ts +224 -12
- package/src/server/management/native-integration-routes.ts +103 -6
- package/src/server/management/oauth-account-routes.ts +45 -7
- package/src/server/management/route-registry.ts +20 -0
- package/src/server/management/shared.ts +28 -4
- package/src/server/management/system-restart.ts +7 -2
- package/src/server/management/system-routes.ts +2 -0
- package/src/server/management/usage-aggregate-cache.ts +4 -0
- package/src/server/management/usage-timeline-routes.ts +44 -0
- package/src/server/management-api.ts +10 -9
- package/src/server/management-auth.ts +15 -1
- package/src/server/proxy-liveness.ts +75 -0
- package/src/server/readiness.ts +29 -10
- package/src/server/relay-eager.ts +24 -2
- package/src/server/relay.ts +138 -12
- package/src/server/request-log-failure-attribution.ts +99 -0
- package/src/server/request-log.ts +169 -2
- package/src/server/request-metrics.ts +298 -0
- package/src/server/responses/adapter-continuation.ts +3 -3
- package/src/server/responses/adapter-dispatch.ts +11 -6
- package/src/server/responses/codex-ws-wire.ts +34 -8
- package/src/server/responses/combo-stream-preflight.ts +168 -6
- package/src/server/responses/compact.ts +43 -10
- package/src/server/responses/compaction-routing.ts +111 -0
- package/src/server/responses/core-codex-account.ts +8 -3
- package/src/server/responses/core-combo.ts +7 -7
- package/src/server/responses/core-normalize.ts +6 -12
- package/src/server/responses/core-opaque-recovery.ts +91 -0
- package/src/server/responses/core-options.ts +4 -0
- package/src/server/responses/encrypted-payload.ts +20 -2
- package/src/server/responses/fetch-helpers.ts +124 -8
- package/src/server/responses/input-admission.ts +10 -0
- package/src/server/responses/passthrough-delivery.ts +45 -15
- package/src/server/responses/passthrough-dispatch.ts +215 -44
- package/src/server/responses/passthrough-error.ts +27 -8
- package/src/server/responses/policy-fallback.ts +5 -13
- package/src/server/responses/request-prepare.ts +109 -18
- package/src/server/responses/request-send-budget.ts +17 -1
- package/src/server/responses/request-sidecar-auth.ts +1 -1
- package/src/server/responses/request-transport.ts +26 -6
- package/src/server/responses/reset-replay.ts +108 -0
- package/src/server/responses/run-turn-execution.ts +25 -3
- package/src/server/responses/sidecar-execution.ts +17 -2
- package/src/server/responses/ws-upstream.ts +14 -27
- package/src/server/responses-custom-tool-repair.ts +27 -54
- package/src/server/responses-request-tool-scope.ts +214 -0
- package/src/server/responses-undeclared-tool-guard.ts +35 -2
- package/src/server/search.ts +25 -1
- package/src/server/sse-payload-rewrite.ts +1 -1
- package/src/server/usage-ledger-retention.ts +73 -0
- package/src/service/cli.ts +48 -2
- package/src/service/health.ts +3 -2
- package/src/service/install-state-contract.d.mts +27 -0
- package/src/service/install-state-contract.mjs +34 -0
- package/src/service/launchd.ts +1 -1
- package/src/service/orchestration.ts +2 -4
- package/src/service/ownership-compatibility.ts +164 -0
- package/src/service/ownership-mutation-lease.d.mts +32 -0
- package/src/service/ownership-mutation-lease.mjs +211 -0
- package/src/service/repair.ts +45 -1
- package/src/service/state-lock.ts +269 -0
- package/src/service/state-record.d.mts +36 -0
- package/src/service/state-record.mjs +138 -0
- package/src/service/state.ts +582 -68
- package/src/service/windows-taskxml.ts +11 -10
- package/src/service.ts +7 -3
- package/src/tray/windows-tray.ps1 +156 -4
- package/src/types/config.ts +48 -3
- package/src/types/provider.ts +91 -0
- package/src/types/request.ts +30 -2
- package/src/types/tools.ts +33 -0
- package/src/types.ts +4 -0
- package/src/update/index.ts +207 -63
- package/src/update/job.ts +9 -5
- package/src/update/ownership-transaction.ts +47 -0
- package/src/update/restart-ownership.ts +54 -0
- package/src/update/runtime-ownership.d.mts +40 -0
- package/src/update/runtime-ownership.mjs +122 -0
- package/src/usage/attempt-delivery.ts +198 -0
- package/src/usage/cache-diagnostic.ts +305 -0
- package/src/usage/failure-fingerprint.ts +118 -0
- package/src/usage/failure-projection-cache.ts +174 -0
- package/src/usage/failure-projection.ts +174 -0
- package/src/usage/ledger-retention.ts +165 -0
- package/src/usage/log.ts +126 -79
- package/src/usage/request-outcome.ts +150 -0
- package/src/usage/retention-contract.ts +28 -0
- package/src/usage/summary.ts +2 -2
- package/src/usage/telemetry-contract.ts +237 -0
- package/src/usage/timeline.ts +236 -0
- package/src/vision/eligibility.ts +88 -9
- package/src/vision/plan.ts +34 -10
- package/src/web-search/alpha-search.ts +21 -1
- package/src/web-search/executor.ts +41 -2
- package/src/web-search/loop.ts +6 -1
- package/gui/dist/assets/index-C5IebErG.js +0 -136
- package/gui/dist/assets/index-OESInAjC.css +0 -1
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured summary for `ocx stop --json` (D4 of the app runtime ownership unit).
|
|
3
|
+
*
|
|
4
|
+
* The desktop shell drives the real `ocx stop` as a child process so the receipt-backed
|
|
5
|
+
* teardown, the drain, the Windows respawn verification and the client-config restore run
|
|
6
|
+
* exactly as they do from a terminal. This module makes that run's RESULT readable from
|
|
7
|
+
* outside: a StopRunRecord is threaded through the existing stop path in
|
|
8
|
+
* src/cli/index.ts (the same booleans that already decide the exit code), and
|
|
9
|
+
* summarizeStopRun turns it into one versioned JSON document.
|
|
10
|
+
*
|
|
11
|
+
* Nothing here re-decides anything. If a field of the record is wrong, the fix belongs
|
|
12
|
+
* in the stop path, not in the summarizer.
|
|
13
|
+
*/
|
|
14
|
+
import { STOP_HISTORY_DEFERRED_EXIT_CODE } from "../update/stop-contract.mjs";
|
|
15
|
+
|
|
16
|
+
/** Wire version of the stop summary document. */
|
|
17
|
+
export const STOP_SUMMARY_SCHEMA = "ocx-stop/1";
|
|
18
|
+
|
|
19
|
+
export type StopServiceOutcome =
|
|
20
|
+
| "absent"
|
|
21
|
+
| "stopped"
|
|
22
|
+
| "stopped-respawnable"
|
|
23
|
+
| "failed"
|
|
24
|
+
| "state-unknown"
|
|
25
|
+
| "error";
|
|
26
|
+
|
|
27
|
+
export type StopProxyOutcome =
|
|
28
|
+
| "stopped"
|
|
29
|
+
| "stopped-orphan"
|
|
30
|
+
| "not-running"
|
|
31
|
+
| "stop-failed"
|
|
32
|
+
| "ownership-refused"
|
|
33
|
+
| "unresolvable-pid"
|
|
34
|
+
| "respawned"
|
|
35
|
+
| "unknown";
|
|
36
|
+
|
|
37
|
+
export type StopSharedTeardownOutcome =
|
|
38
|
+
| "restored"
|
|
39
|
+
| "performed-by-proxy"
|
|
40
|
+
| "refused"
|
|
41
|
+
| "failed"
|
|
42
|
+
| "skipped";
|
|
43
|
+
|
|
44
|
+
/** Facts recorded where the stop path already decides them. */
|
|
45
|
+
export interface StopRunRecord {
|
|
46
|
+
/** What stopServiceIfInstalledDetailed returned, or "error" when it threw. */
|
|
47
|
+
service: StopServiceOutcome;
|
|
48
|
+
/** Which proxy path ran and how it ended. */
|
|
49
|
+
proxy: StopProxyOutcome;
|
|
50
|
+
/** Who ended up restoring shared client config (native Codex + Grok). */
|
|
51
|
+
sharedTeardown: StopSharedTeardownOutcome;
|
|
52
|
+
/** An inherited pending-teardown receipt blocked the restore. */
|
|
53
|
+
inheritedTeardownBlocks: boolean;
|
|
54
|
+
/** A discharged receipt could not be removed from disk. */
|
|
55
|
+
receiptClearFailed: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The internal booleans that already pick the process exit code. */
|
|
59
|
+
export interface StopRunSignals {
|
|
60
|
+
failed: boolean;
|
|
61
|
+
historyOnly: boolean;
|
|
62
|
+
historyDeferred: boolean;
|
|
63
|
+
exitCode: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface StopSummaryJson {
|
|
67
|
+
schema: typeof STOP_SUMMARY_SCHEMA;
|
|
68
|
+
/** Strict exit-code view: true only for exit 0. */
|
|
69
|
+
ok: boolean;
|
|
70
|
+
outcome: "stopped" | "not-running" | "history-incomplete" | "history-deferred" | "failed";
|
|
71
|
+
exitCode: number;
|
|
72
|
+
/** True when this stop left no proxy of this home running by its own paths. */
|
|
73
|
+
runtimeDown: boolean;
|
|
74
|
+
service: StopServiceOutcome;
|
|
75
|
+
proxy: StopProxyOutcome;
|
|
76
|
+
sharedTeardown: StopSharedTeardownOutcome;
|
|
77
|
+
/** One stable human-readable line for a caller's UI. */
|
|
78
|
+
message: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** What handleStop returns: the pre-existing boolean plus the structured twin. */
|
|
82
|
+
export interface StopOutcome {
|
|
83
|
+
/**
|
|
84
|
+
* The exact boolean the stop path returned before summaries existed (!stopFailed).
|
|
85
|
+
* It is NOT the same as summary.ok: 79/80 stops return true here, because the
|
|
86
|
+
* downtime warning that keys on it applies whenever the runtime went down.
|
|
87
|
+
*/
|
|
88
|
+
ok: boolean;
|
|
89
|
+
summary: StopSummaryJson;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function stopOutcome(record: StopRunRecord, signals: StopRunSignals): StopSummaryJson["outcome"] {
|
|
93
|
+
if (signals.failed) return "failed";
|
|
94
|
+
if (signals.historyOnly) return "history-incomplete";
|
|
95
|
+
if (signals.historyDeferred) {
|
|
96
|
+
// The deferred exit code is a proven claim; anything else means another obligation
|
|
97
|
+
// was sitting in the home, which the exit-code logic already reports as failure.
|
|
98
|
+
return signals.exitCode === STOP_HISTORY_DEFERRED_EXIT_CODE ? "history-deferred" : "failed";
|
|
99
|
+
}
|
|
100
|
+
if (record.proxy === "not-running") return "not-running";
|
|
101
|
+
if (record.proxy === "stopped" || record.proxy === "stopped-orphan") return "stopped";
|
|
102
|
+
return "failed";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function stopMessage(record: StopRunRecord, signals: StopRunSignals, outcome: StopSummaryJson["outcome"]): string {
|
|
106
|
+
if (record.proxy === "respawned") return "The proxy was respawned after the stop; it is still running.";
|
|
107
|
+
if (record.proxy === "ownership-refused") return "The proxy refused the stop; it belongs to a different opencodex home.";
|
|
108
|
+
if (record.proxy === "unresolvable-pid") return "A proxy is answering, but its process id could not be resolved, so it was not stopped.";
|
|
109
|
+
if (record.proxy === "stop-failed") return "The proxy process could not be stopped.";
|
|
110
|
+
if (record.service === "failed") return "The installed service manager did not stop and may respawn the proxy.";
|
|
111
|
+
if (record.service === "state-unknown") return "The service manager state could not be read.";
|
|
112
|
+
if (record.service === "error") return "Stopping the installed service failed.";
|
|
113
|
+
if (record.inheritedTeardownBlocks) return "An earlier stop left an outstanding shared teardown that could not be confirmed.";
|
|
114
|
+
if (record.sharedTeardown === "failed") return "The shared teardown failed; client configuration may still point at the stopped proxy.";
|
|
115
|
+
if (record.receiptClearFailed) return "The shared teardown finished, but its receipt could not be removed.";
|
|
116
|
+
if (record.sharedTeardown === "refused") return "The shared teardown was refused before it changed anything; it is still owed.";
|
|
117
|
+
if (signals.historyOnly) return "The proxy stopped; Codex history cleanup did not complete.";
|
|
118
|
+
if (outcome === "history-deferred") return "The proxy stopped; the shared teardown was deferred and is still owed.";
|
|
119
|
+
if (outcome === "not-running") return "No proxy was running.";
|
|
120
|
+
if (outcome === "stopped") return "The proxy stopped.";
|
|
121
|
+
return "The stop failed.";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Pure mapper from the recorded run to the wire document. */
|
|
125
|
+
export function summarizeStopRun(record: StopRunRecord, signals: StopRunSignals): StopSummaryJson {
|
|
126
|
+
const outcome = stopOutcome(record, signals);
|
|
127
|
+
return {
|
|
128
|
+
schema: STOP_SUMMARY_SCHEMA,
|
|
129
|
+
ok: signals.exitCode === 0,
|
|
130
|
+
outcome,
|
|
131
|
+
exitCode: signals.exitCode,
|
|
132
|
+
runtimeDown: record.proxy === "stopped" || record.proxy === "stopped-orphan" || record.proxy === "not-running",
|
|
133
|
+
service: record.service,
|
|
134
|
+
proxy: record.proxy,
|
|
135
|
+
sharedTeardown: record.sharedTeardown,
|
|
136
|
+
message: stopMessage(record, signals, outcome),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Emit the summary as exactly one JSON document on stdout. */
|
|
141
|
+
export function printStopSummary(summary: StopSummaryJson, stdout: { log: (s: string) => void } = console): void {
|
|
142
|
+
stdout.log(JSON.stringify(summary));
|
|
143
|
+
}
|
|
@@ -84,3 +84,12 @@ export function everyEndpointProvenDown(
|
|
|
84
84
|
if (endpoints.length === 0) return false;
|
|
85
85
|
return endpoints.every(e => probe(e) === "dead");
|
|
86
86
|
}
|
|
87
|
+
|
|
88
|
+
export async function everyEndpointProvenDownAsync(
|
|
89
|
+
endpoints: readonly ProbeEndpoint[],
|
|
90
|
+
probe: (e: ProbeEndpoint) => Promise<"live" | "dead" | "unknown"> | "live" | "dead" | "unknown",
|
|
91
|
+
): Promise<boolean> {
|
|
92
|
+
if (endpoints.length === 0) return false;
|
|
93
|
+
const results = await Promise.all(endpoints.map(e => probe(e)));
|
|
94
|
+
return results.every(result => result === "dead");
|
|
95
|
+
}
|
|
@@ -23,7 +23,7 @@ export interface MachineStatusV1 {
|
|
|
23
23
|
export interface MachineApiDeps {
|
|
24
24
|
sync: typeof syncConnectedClient;
|
|
25
25
|
disconnect: typeof disconnectClient;
|
|
26
|
-
scheduleStandaloneRecycle: () => void;
|
|
26
|
+
scheduleStandaloneRecycle: (disconnectedTokenFingerprint: string) => void;
|
|
27
27
|
hubReachability?: () => HubReachability;
|
|
28
28
|
setHubReachability?: (value: HubReachability) => void;
|
|
29
29
|
}
|
|
@@ -130,7 +130,7 @@ export async function handleMachineApi(
|
|
|
130
130
|
}
|
|
131
131
|
try {
|
|
132
132
|
const result = await deps.disconnect(input.keepCatalog === undefined ? {} : { keepCatalog: input.keepCatalog });
|
|
133
|
-
deps.scheduleStandaloneRecycle();
|
|
133
|
+
deps.scheduleStandaloneRecycle(state.tokenFingerprint);
|
|
134
134
|
return Response.json({ success: true, ...result }, { status: 202 });
|
|
135
135
|
} catch (error) {
|
|
136
136
|
return Response.json({ success: false, error: error instanceof Error ? error.message : "disconnect failed" }, { status: 409 });
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import type { Server } from "bun";
|
|
3
2
|
import { loadConfig } from "../config";
|
|
4
3
|
import { browserSecurityHeaders } from "../server/auth-cors";
|
|
@@ -16,11 +15,9 @@ import { readClientConnectionState } from "./state";
|
|
|
16
15
|
import { handleMachineApi, type HubReachability, type MachineApiDeps } from "./machine-api";
|
|
17
16
|
import { MACHINE_GUI_ORIGIN_HEADER, requireMachineAuth } from "./machine-auth";
|
|
18
17
|
import { relayHubManagementRequest } from "./hub-relay";
|
|
18
|
+
import { packageVersion } from "../lib/package-version";
|
|
19
19
|
|
|
20
|
-
const VERSION = (
|
|
21
|
-
try { return JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8")).version as string; }
|
|
22
|
-
catch { return "0.0.0"; }
|
|
23
|
-
})();
|
|
20
|
+
const VERSION = packageVersion("0.0.0");
|
|
24
21
|
const GUI_SPA_PATHS = new Set([
|
|
25
22
|
"/dashboard", "/startup", "/providers", "/models", "/subagents",
|
|
26
23
|
"/logs", "/usage", "/storage", "/codex-set", "/integrations",
|
|
@@ -75,8 +72,8 @@ export function startMachineListener(
|
|
|
75
72
|
const machineApiDeps: MachineApiDeps = {
|
|
76
73
|
sync: deps.machineApi?.sync ?? syncConnectedClient,
|
|
77
74
|
disconnect: deps.machineApi?.disconnect ?? disconnectClient,
|
|
78
|
-
scheduleStandaloneRecycle: deps.machineApi?.scheduleStandaloneRecycle ?? (
|
|
79
|
-
void import("./runtime").then(module => module.scheduleStandaloneRecycle());
|
|
75
|
+
scheduleStandaloneRecycle: deps.machineApi?.scheduleStandaloneRecycle ?? (tokenFingerprint => {
|
|
76
|
+
void import("./runtime").then(module => module.scheduleStandaloneRecycle(tokenFingerprint));
|
|
80
77
|
}),
|
|
81
78
|
hubReachability: deps.machineApi?.hubReachability ?? (() => hubReachability),
|
|
82
79
|
setHubReachability: deps.machineApi?.setHubReachability ?? (value => { hubReachability = value; }),
|
package/src/client/runtime.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { loadConfig } from "../config";
|
|
|
4
4
|
import { removePid, removeRuntimePort, writePid, writeRuntimePort } from "../config/process-state";
|
|
5
5
|
import { installCrashGuards } from "../lib/crash-guard";
|
|
6
6
|
import { selfLaunchArgv } from "../lib/self-launch-argv";
|
|
7
|
+
import { loadServiceTokenFromFile, serviceApiTokenFingerprint } from "../lib/service-secrets";
|
|
7
8
|
import { findAvailablePort } from "../server/ports";
|
|
8
9
|
import { startMachineListener } from "./machine-listener";
|
|
9
10
|
import { readClientConnectionState } from "./state";
|
|
@@ -17,7 +18,30 @@ function cleanup(): void {
|
|
|
17
18
|
removeRuntimePort(process.pid);
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
export function
|
|
21
|
+
export function standaloneRecycleEnv(
|
|
22
|
+
env: NodeJS.ProcessEnv,
|
|
23
|
+
disconnectedTokenFingerprint: string,
|
|
24
|
+
): NodeJS.ProcessEnv {
|
|
25
|
+
const childEnv = { ...env };
|
|
26
|
+
const admissionToken = childEnv.OPENCODEX_API_AUTH_TOKEN?.trim();
|
|
27
|
+
if (admissionToken && serviceApiTokenFingerprint(admissionToken) !== disconnectedTokenFingerprint) {
|
|
28
|
+
// A surviving env token shadows OCX_API_TOKEN_FILE entirely, so nothing below can
|
|
29
|
+
// reintroduce the disconnected token.
|
|
30
|
+
return childEnv;
|
|
31
|
+
}
|
|
32
|
+
if (admissionToken) delete childEnv.OPENCODEX_API_AUTH_TOKEN;
|
|
33
|
+
// The respawned `ocx start` loads OCX_API_TOKEN_FILE into OPENCODEX_API_AUTH_TOKEN when the
|
|
34
|
+
// env token is absent, so vet the file by content: drop the pointer when it is unreadable or
|
|
35
|
+
// holds the disconnected token, keep it when it holds a different operator credential.
|
|
36
|
+
if (!childEnv.OCX_API_TOKEN_FILE?.trim()) return childEnv;
|
|
37
|
+
const fileToken = loadServiceTokenFromFile(childEnv);
|
|
38
|
+
if (fileToken === null || serviceApiTokenFingerprint(fileToken) === disconnectedTokenFingerprint) {
|
|
39
|
+
delete childEnv.OCX_API_TOKEN_FILE;
|
|
40
|
+
}
|
|
41
|
+
return childEnv;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function scheduleStandaloneRecycle(disconnectedTokenFingerprint: string): void {
|
|
21
45
|
if (recycleScheduled) return;
|
|
22
46
|
recycleScheduled = true;
|
|
23
47
|
const timer = setTimeout(() => {
|
|
@@ -46,7 +70,7 @@ export function scheduleStandaloneRecycle(): void {
|
|
|
46
70
|
detached: true,
|
|
47
71
|
stdio: "ignore",
|
|
48
72
|
windowsHide: true,
|
|
49
|
-
env:
|
|
73
|
+
env: standaloneRecycleEnv(process.env, disconnectedTokenFingerprint),
|
|
50
74
|
});
|
|
51
75
|
child.unref();
|
|
52
76
|
}
|
|
@@ -201,6 +201,7 @@ export function guardAsideProfileIO(profile: AsideProfile, io: IntegrationIO, pr
|
|
|
201
201
|
const selected = { ...profile };
|
|
202
202
|
const registered = registeredProfiles(selected, profiles).map(peer => ({ ...peer }));
|
|
203
203
|
const captured = boundary(selected, registered, false);
|
|
204
|
+
const lstatProbe = io.lstatKind;
|
|
204
205
|
function check(path: string, directory: boolean, mutation: boolean): void {
|
|
205
206
|
if (path !== (directory ? selected.detectDir : selected.configPath)) {
|
|
206
207
|
refuse("IO attempted to access a different account path.");
|
|
@@ -214,6 +215,9 @@ export function guardAsideProfileIO(profile: AsideProfile, io: IntegrationIO, pr
|
|
|
214
215
|
return {
|
|
215
216
|
readText: path => { check(path, false, false); return io.readText(path); },
|
|
216
217
|
statKind: path => { check(path, path === selected.detectDir, false); return io.statKind(path); },
|
|
218
|
+
...(lstatProbe
|
|
219
|
+
? { lstatKind: (path: string) => { check(path, path === selected.detectDir, false); return lstatProbe(path); } }
|
|
220
|
+
: {}),
|
|
217
221
|
writeText: (path, text) => { check(path, false, true); io.writeText(path, text); },
|
|
218
222
|
removeFile: path => { check(path, false, true); io.removeFile(path); },
|
|
219
223
|
mkdirp: path => { check(path, true, true); io.mkdirp(path); },
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// ZCode personal provider store export.
|
|
2
|
+
import type { ExportContext, ManagedContribution, ManagedFragment } from "./contracts";
|
|
3
|
+
import { authoritativeContextWindow, inputModalitiesForClient, normalizeExportModels } from "./model-metadata";
|
|
4
|
+
import { OPENCODE_PROVIDER_ID, LOOPBACK_API_KEY_PLACEHOLDER } from "./constants";
|
|
5
|
+
import { formatSelectorConjunction } from "../../integrations/merge";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The file ZCode 3.14 and later actually read their custom providers from.
|
|
9
|
+
*
|
|
10
|
+
* `v2/config.json` — the file {@link buildZcodeContribution} writes — is still
|
|
11
|
+
* parsed by this client, but only by a one-shot `importLegacy` hook that runs
|
|
12
|
+
* when this store is missing. The client creates the store on first launch, so
|
|
13
|
+
* on any install that has ever run the import is already spent and a later
|
|
14
|
+
* write to the old file is read by nobody (#5348).
|
|
15
|
+
*
|
|
16
|
+
* The shape below is the client's own, quoted from the report that opened
|
|
17
|
+
* #5348: it is what the client's `importLegacyPersonalProviderConfig` produced
|
|
18
|
+
* when it migrated this project's block, so it is observed output rather than a
|
|
19
|
+
* schema this project invented. Everything the client persists that we have not
|
|
20
|
+
* seen it derive from our block is deliberately absent — an omitted field is a
|
|
21
|
+
* field the client still owns.
|
|
22
|
+
*/
|
|
23
|
+
export const ZCODE_STORE_SCHEMA_VERSION = 1;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* `group` places the provider among the user's own personal providers rather
|
|
27
|
+
* than the builtin list the client ships and rewrites. `api.type` is the same
|
|
28
|
+
* choice `kind: "openai"` makes in the legacy block: the OpenAI Responses
|
|
29
|
+
* protocol, which is the only surface this proxy speaks natively.
|
|
30
|
+
*/
|
|
31
|
+
export const ZCODE_STORE_PROVIDER_GROUP = "standard-personal";
|
|
32
|
+
export const ZCODE_STORE_API_TYPE = "openai-responses";
|
|
33
|
+
export const ZCODE_STORE_PROVIDER_NAME = "OpenCodex";
|
|
34
|
+
|
|
35
|
+
/** Where the store keeps one rule per provider, and one per (provider, model). */
|
|
36
|
+
export const ZCODE_STORE_PROVIDER_RULES_PATH = ["config", "providerConfigRules", "providerRules"] as const;
|
|
37
|
+
export const ZCODE_STORE_MODEL_RULES_PATH = ["config", "modelConfigRules", "providerModelRules"] as const;
|
|
38
|
+
|
|
39
|
+
export interface ZcodeStoreProviderRule {
|
|
40
|
+
providerId: string;
|
|
41
|
+
enabled: true;
|
|
42
|
+
providerName: string;
|
|
43
|
+
config: {
|
|
44
|
+
group: string;
|
|
45
|
+
access: { apiKey: string };
|
|
46
|
+
api: { type: string; baseUrl: string };
|
|
47
|
+
personalModelIds: string[];
|
|
48
|
+
modelOrder: string[];
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ZcodeStoreModelRule {
|
|
53
|
+
providerId: string;
|
|
54
|
+
modelId: string;
|
|
55
|
+
config: { properties: { contextWindow: number } };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Is this document a store whose schema we can write?
|
|
60
|
+
*
|
|
61
|
+
* Only the one version whose shape has been observed. A store carrying any
|
|
62
|
+
* other `schemaVersion` — or none, or a document that is not an object — is a
|
|
63
|
+
* file we cannot merge into without asserting a nesting we have never seen,
|
|
64
|
+
* and a wrong assertion there does not fail loudly: it replaces the provider
|
|
65
|
+
* list the user keeps in the same file. The integration reports the write as
|
|
66
|
+
* ineffective in that case instead of guessing.
|
|
67
|
+
*/
|
|
68
|
+
export function zcodeStoreSchemaEstablished(parsed: unknown): boolean {
|
|
69
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return false;
|
|
70
|
+
return (parsed as Record<string, unknown>).schemaVersion === ZCODE_STORE_SCHEMA_VERSION;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The selector addressing one model rule, or null when the id cannot be spelled.
|
|
75
|
+
*
|
|
76
|
+
* A rule is identified by the PAIR, so both fields are named: a selector
|
|
77
|
+
* carrying only the model would match another provider's rule for the same
|
|
78
|
+
* model. The conjunction grammar reserves `,` and `]`, and a model id holding
|
|
79
|
+
* either cannot be addressed unambiguously — that row ships without its
|
|
80
|
+
* context window rather than with a selector that points somewhere else.
|
|
81
|
+
*/
|
|
82
|
+
function modelRuleSelector(modelId: string): string | null {
|
|
83
|
+
return formatSelectorConjunction([
|
|
84
|
+
{ field: "providerId", value: OPENCODE_PROVIDER_ID },
|
|
85
|
+
{ field: "modelId", value: modelId },
|
|
86
|
+
]);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The rows this export publishes, filtered exactly as the legacy block filters them. */
|
|
90
|
+
function storeModels(ctx: ExportContext): string[] {
|
|
91
|
+
const ids: string[] = [];
|
|
92
|
+
for (const model of normalizeExportModels(ctx.models)) {
|
|
93
|
+
if (inputModalitiesForClient("pi", model.inputModalities) === null) continue;
|
|
94
|
+
ids.push(model.namespaced);
|
|
95
|
+
}
|
|
96
|
+
return ids;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The provider rule the client reads, built from the same context the legacy
|
|
101
|
+
* block is built from.
|
|
102
|
+
*
|
|
103
|
+
* `baseUrl` carries the `/v1` root for the same reason the legacy block does:
|
|
104
|
+
* this client appends `/responses` for the Responses protocol, so requests land
|
|
105
|
+
* on `/v1/responses`. The serialized credential is always the non-secret
|
|
106
|
+
* loopback placeholder.
|
|
107
|
+
*/
|
|
108
|
+
export function buildZcodeStoreProviderRule(ctx: ExportContext): ZcodeStoreProviderRule {
|
|
109
|
+
const ids = storeModels(ctx);
|
|
110
|
+
return {
|
|
111
|
+
providerId: OPENCODE_PROVIDER_ID,
|
|
112
|
+
enabled: true,
|
|
113
|
+
providerName: ZCODE_STORE_PROVIDER_NAME,
|
|
114
|
+
config: {
|
|
115
|
+
group: ZCODE_STORE_PROVIDER_GROUP,
|
|
116
|
+
access: { apiKey: LOOPBACK_API_KEY_PLACEHOLDER },
|
|
117
|
+
api: { type: ZCODE_STORE_API_TYPE, baseUrl: `${ctx.baseUrl.replace(/\/v1\/?$/, "")}/v1` },
|
|
118
|
+
personalModelIds: ids,
|
|
119
|
+
/*
|
|
120
|
+
* The client persists the picker order separately from membership. Ours is
|
|
121
|
+
* the catalog order the rest of this export already sorts by, so a refresh
|
|
122
|
+
* that adds a model puts it where every other surface puts it.
|
|
123
|
+
*/
|
|
124
|
+
modelOrder: [...ids],
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Everything this project owns inside the store: one provider rule, plus one
|
|
131
|
+
* model rule per row that has an authoritative context window.
|
|
132
|
+
*
|
|
133
|
+
* A row without one ships no model rule at all, which is the same
|
|
134
|
+
* authoritative-window rule the legacy block follows — an emitted stand-in
|
|
135
|
+
* would be a guessed capability. Reasoning ladders have no counterpart here
|
|
136
|
+
* that this project has observed the client produce, so none is written.
|
|
137
|
+
*/
|
|
138
|
+
export function buildZcodeStoreContribution(ctx: ExportContext): ManagedContribution {
|
|
139
|
+
const fragments: ManagedFragment[] = [{
|
|
140
|
+
path: [...ZCODE_STORE_PROVIDER_RULES_PATH, `[providerId=${OPENCODE_PROVIDER_ID}]`],
|
|
141
|
+
value: buildZcodeStoreProviderRule(ctx),
|
|
142
|
+
}];
|
|
143
|
+
for (const model of normalizeExportModels(ctx.models)) {
|
|
144
|
+
if (inputModalitiesForClient("pi", model.inputModalities) === null) continue;
|
|
145
|
+
const contextWindow = authoritativeContextWindow(model.contextWindow);
|
|
146
|
+
if (contextWindow === undefined) continue;
|
|
147
|
+
const selector = modelRuleSelector(model.namespaced);
|
|
148
|
+
if (selector === null) continue;
|
|
149
|
+
const rule: ZcodeStoreModelRule = {
|
|
150
|
+
providerId: OPENCODE_PROVIDER_ID,
|
|
151
|
+
modelId: model.namespaced,
|
|
152
|
+
config: { properties: { contextWindow } },
|
|
153
|
+
};
|
|
154
|
+
fragments.push({ path: [...ZCODE_STORE_MODEL_RULES_PATH, selector], value: rule });
|
|
155
|
+
}
|
|
156
|
+
return { clientId: "zcode", fragments };
|
|
157
|
+
}
|
|
@@ -35,6 +35,18 @@ export { OPENCODE_PROVIDER_ID, OPENCODE_CONFIG_SCHEMA, OPENCODE_API_KEY_ENV, OPE
|
|
|
35
35
|
export { normalizeExportModels } from "./config-export/model-metadata";
|
|
36
36
|
export type { OmpModelEntry, OmpProviderBlock, OmpGeneratedConfig } from "./config-export/omp";
|
|
37
37
|
export type { ZcodeModelEntry, ZcodeProviderBlock, ZcodeGeneratedConfig } from "./config-export/zcode";
|
|
38
|
+
export type { ZcodeStoreProviderRule, ZcodeStoreModelRule } from "./config-export/zcode-store";
|
|
39
|
+
export {
|
|
40
|
+
ZCODE_STORE_SCHEMA_VERSION,
|
|
41
|
+
ZCODE_STORE_PROVIDER_GROUP,
|
|
42
|
+
ZCODE_STORE_API_TYPE,
|
|
43
|
+
ZCODE_STORE_PROVIDER_NAME,
|
|
44
|
+
ZCODE_STORE_PROVIDER_RULES_PATH,
|
|
45
|
+
ZCODE_STORE_MODEL_RULES_PATH,
|
|
46
|
+
buildZcodeStoreProviderRule,
|
|
47
|
+
buildZcodeStoreContribution,
|
|
48
|
+
zcodeStoreSchemaEstablished,
|
|
49
|
+
} from "./config-export/zcode-store";
|
|
38
50
|
export type { DshReasoningEffort, DshWireReasoningEffort, DshModelEntry, DshProviderBlock, DshGeneratedConfig } from "./config-export/dsh";
|
|
39
51
|
export type { McodeProviderBlock, McodeModelEntry, McodeGeneratedConfig } from "./config-export/mcode";
|
|
40
52
|
export type { RaycastAbility, RaycastAbilityName, RaycastModelEntry, RaycastProviderEntry, RaycastGeneratedConfig } from "./config-export/raycast";
|
|
@@ -447,6 +459,30 @@ export function zcodeConfigPath(env: OpencodeLaunchEnv = process.env, home: stri
|
|
|
447
459
|
return join(zcodeHomeDir(env, home), "v2", "config.json");
|
|
448
460
|
}
|
|
449
461
|
|
|
462
|
+
/**
|
|
463
|
+
* The provider store a current ZCode reads, which is NOT the file above.
|
|
464
|
+
*
|
|
465
|
+
* ZCode 3.14 moved custom providers to `v2/provider_config.json` and left
|
|
466
|
+
* `v2/config.json` reachable only through a one-shot import that runs when the
|
|
467
|
+
* new file is missing. The client creates the new file on first launch, so on
|
|
468
|
+
* an install that has ever run, the import has already happened and never runs
|
|
469
|
+
* again — every later write to `v2/config.json` is read by nobody (#5348).
|
|
470
|
+
*
|
|
471
|
+
* This project does not write this file; it names it so the integration can
|
|
472
|
+
* tell whether its own write can still reach the client. The env override is
|
|
473
|
+
* ZCode's own (`ZCODE_PERSONAL_PROVIDER_CONFIG_FILE`), so an operator who
|
|
474
|
+
* relocated the store is measured against the file their client actually opens
|
|
475
|
+
* rather than the default location. A relative override is refused for the same
|
|
476
|
+
* reason `ZCODE_DATA_DIR` refuses one: we and the client would disagree about
|
|
477
|
+
* which file it names, and here that disagreement decides whether an apply is
|
|
478
|
+
* reported as effective.
|
|
479
|
+
*/
|
|
480
|
+
export function zcodeProviderStorePath(env: OpencodeLaunchEnv = process.env, home: string = homedir()): string {
|
|
481
|
+
const override = env.ZCODE_PERSONAL_PROVIDER_CONFIG_FILE?.trim();
|
|
482
|
+
if (override) return absoluteClientPath(override, home, "ZCODE_PERSONAL_PROVIDER_CONFIG_FILE");
|
|
483
|
+
return join(zcodeHomeDir(env, home), "v2", "provider_config.json");
|
|
484
|
+
}
|
|
485
|
+
|
|
450
486
|
/**
|
|
451
487
|
* Prime Agent resolves its agent directory from `PRIME_AGENT_CODING_AGENT_DIR`
|
|
452
488
|
* — the brand-derived spelling of the `PI_CODING_AGENT_DIR` that `ompAgentDir`
|
|
@@ -370,6 +370,23 @@ export function isCodexAccountGenerationLive(id: string, generation: number): bo
|
|
|
370
370
|
return !!record?.credential && record.deletedAt == null && record.generation === generation;
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
+
/**
|
|
374
|
+
* The same verdict as {@link isCodexAccountGenerationLive}, over ONE store load.
|
|
375
|
+
*
|
|
376
|
+
* `readCodexAccountRecord` is `loadCodexAccountRecordStore()[id]`, so asking it per row
|
|
377
|
+
* reloads, reparses and renormalizes the whole file per row — the exact shape
|
|
378
|
+
* {@link loadCodexAccountRecordSnapshot} was added to avoid. The denial reader resolves
|
|
379
|
+
* several accounts in one synchronous pass on the request path, so it opens one checker and
|
|
380
|
+
* closes over the snapshot instead.
|
|
381
|
+
*/
|
|
382
|
+
export function beginCodexAccountGenerationLiveCheck(): (id: string, generation: number) => boolean {
|
|
383
|
+
const snapshot = loadCodexAccountRecordSnapshot();
|
|
384
|
+
return (id, generation) => {
|
|
385
|
+
const record = snapshot[id];
|
|
386
|
+
return !!record?.credential && record.deletedAt == null && record.generation === generation;
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
373
390
|
export function saveCodexAccountCredentialIfGeneration(
|
|
374
391
|
id: string,
|
|
375
392
|
generation: number,
|
|
@@ -596,6 +613,41 @@ function withCredentialMutationLockSync<T>(fn: () => T): T {
|
|
|
596
613
|
}
|
|
597
614
|
|
|
598
615
|
type CodexTokenResult = { accessToken: string; chatgptAccountId: string; generation: number };
|
|
616
|
+
type CodexRefreshGenerationHandoff = (accountId: string, fromGeneration: number, toGeneration: number) => void;
|
|
617
|
+
// var (hoisted, initialized to undefined) rather than const: a registrar reached
|
|
618
|
+
// through an import cycle can register while this module body is still evaluating,
|
|
619
|
+
// and the lazily created Set must be reachable rather than in the temporal dead zone.
|
|
620
|
+
var refreshGenerationHandoffs: Set<CodexRefreshGenerationHandoff> | undefined;
|
|
621
|
+
function refreshHandoffs(): Set<CodexRefreshGenerationHandoff> {
|
|
622
|
+
return refreshGenerationHandoffs ??= new Set();
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/** Register process-local state that must follow a credential refresh generation. */
|
|
626
|
+
export function registerCodexRefreshGenerationHandoff(handoff: CodexRefreshGenerationHandoff): () => void {
|
|
627
|
+
const set = refreshHandoffs();
|
|
628
|
+
set.add(handoff);
|
|
629
|
+
return () => set.delete(handoff);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Invoke every registered handoff for one committed `fromGeneration` to `toGeneration` move.
|
|
634
|
+
*
|
|
635
|
+
* Each listener runs independently and a throw is contained: by the time handoffs run the
|
|
636
|
+
* rotated credential is already persisted, so a failing listener must not reject the shared
|
|
637
|
+
* refresh promise (surviving waiters would see a refresh failure that never happened), must
|
|
638
|
+
* not starve the remaining listeners, and must not block plan reconciliation. The warning
|
|
639
|
+
* carries no account id or token material — the same scrub refresh error messages get.
|
|
640
|
+
*/
|
|
641
|
+
function dispatchRefreshGenerationHandoffs(accountId: string, fromGeneration: number, toGeneration: number): void {
|
|
642
|
+
for (const handoff of refreshHandoffs()) {
|
|
643
|
+
try {
|
|
644
|
+
handoff(accountId, fromGeneration, toGeneration);
|
|
645
|
+
} catch (error) {
|
|
646
|
+
console.warn("[codex-auth] a refresh generation handoff listener failed", error);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
599
651
|
type CodexRefreshResult = CodexTokenResult & {
|
|
600
652
|
credential?: CodexAccountCredentials;
|
|
601
653
|
/**
|
|
@@ -1377,6 +1429,19 @@ async function resolveCodexToken(
|
|
|
1377
1429
|
* committed result, for every waiter, including none.
|
|
1378
1430
|
*/
|
|
1379
1431
|
const refreshPromise = fetchPromise.then(async (result): Promise<CodexRefreshResult> => {
|
|
1432
|
+
// Generation-dependent completion belongs to the flight, not to its initiating
|
|
1433
|
+
// request. The owner may stop waiting after a disconnect while this detached work
|
|
1434
|
+
// still commits G+1; advance process-local affinities before any waiter observes
|
|
1435
|
+
// the result (and even when there are no surviving waiters).
|
|
1436
|
+
if (result.selfRefreshed) {
|
|
1437
|
+
dispatchRefreshGenerationHandoffs(id, result.generation - 1, result.generation);
|
|
1438
|
+
// A propagated alias committed at its OWN generation, so its affinities sit at
|
|
1439
|
+
// alias.generation - 1: without this handoff they fail the exact-generation
|
|
1440
|
+
// liveness check on the very next request that reads them.
|
|
1441
|
+
for (const alias of result.propagatedAliases ?? []) {
|
|
1442
|
+
dispatchRefreshGenerationHandoffs(alias.id, alias.generation - 1, alias.generation);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1380
1445
|
await notePlanFromRefreshedAccessToken(id, result.accessToken, result.generation);
|
|
1381
1446
|
// One settlement path for the whole flight: the refreshing account, then any dormant alias that
|
|
1382
1447
|
// adopted the same rotated JWT. An alias holds the identical access token, so a changed
|