@bitkyc08/opencodex 2.86.0 → 2.88.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/gui/dist/assets/App-CHC89EXQ.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-DyR9yBee.js → Tray-B5bC7AWL.js} +1 -1
- package/gui/dist/assets/index-B5bsvxor.js +94 -0
- package/gui/dist/assets/index-DPeB9HpV.css +1 -0
- package/gui/dist/assets/quota-summary-D8y2wTsB.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic/beta-allowlist.ts +9 -8
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/passthrough.ts +7 -23
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic-rejection-memo.ts +71 -0
- package/src/adapters/anthropic-replay.ts +135 -0
- package/src/adapters/anthropic-tool-catalog.ts +11 -0
- package/src/adapters/anthropic.ts +151 -33
- package/src/adapters/base.ts +2 -0
- package/src/adapters/claude-cli/adapter.ts +111 -23
- package/src/adapters/claude-cli/profiles.ts +2 -1
- package/src/adapters/claude-cli/stable-replay.ts +108 -0
- package/src/adapters/claude-cli/usage-admission.ts +230 -0
- package/src/adapters/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +18 -2
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google-progress-history.ts +53 -0
- package/src/adapters/google.ts +52 -9
- package/src/adapters/openai-chat/messages.ts +4 -3
- package/src/adapters/openai-chat/passthrough.ts +8 -2
- package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
- package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
- package/src/adapters/openai-responses/passthrough.ts +57 -125
- package/src/adapters/openai-responses/request-strips.ts +25 -3
- package/src/adapters/openai-responses/transport.ts +81 -0
- package/src/adapters/qoder/adapter.ts +4 -2
- package/src/adapters/registry.ts +5 -0
- package/src/adapters/zed.ts +26 -1
- package/src/advisor/activity.ts +55 -0
- package/src/advisor/budget.ts +62 -0
- package/src/advisor/consult.ts +4 -1
- package/src/advisor/context.ts +16 -4
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +79 -31
- package/src/advisor/settings.ts +11 -2
- package/src/bridge/errors.ts +19 -1
- package/src/bridge/internal.ts +7 -1
- package/src/bridge/response-json.ts +18 -3
- package/src/bridge/sse.ts +59 -8
- package/src/bridge/terminal-refusal.ts +59 -0
- package/src/claude/desktop-3p-library.ts +32 -7
- package/src/claude/desktop-3p.ts +55 -13
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +77 -0
- package/src/claude/inbound.ts +33 -0
- package/src/claude/intercept/picker-ca-startup.ts +2 -1
- package/src/claude/intercept/picker-runtime.ts +6 -5
- package/src/claude/intercept/picker-trust-windows.ts +338 -0
- package/src/claude/intercept/picker-trust.ts +19 -0
- package/src/claude/intercept/port-policy.ts +30 -0
- package/src/claude/intercept/runtime.ts +97 -94
- package/src/claude/outbound.ts +46 -13
- package/src/cli/account-api.ts +1 -0
- package/src/cli/account-policy-dto.ts +6 -0
- package/src/cli/account-policy.ts +34 -5
- package/src/cli/account-preservation.ts +44 -0
- package/src/cli/account.ts +8 -2
- package/src/cli/advisor.ts +17 -3
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +5 -2
- package/src/cli/capabilities-base.ts +38 -10
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- package/src/cli/codex-cli-update.ts +151 -41
- package/src/cli/codex-provider.ts +44 -0
- package/src/cli/combo-input.ts +10 -1
- package/src/cli/combo-result.ts +1 -1
- package/src/cli/combo.ts +2 -1
- package/src/cli/cross-home-owner.ts +33 -16
- package/src/cli/dispatch.ts +25 -4
- package/src/cli/doctor.ts +6 -1
- package/src/cli/ensure-desktop-cold-start.ts +60 -7
- package/src/cli/help.ts +3 -2
- package/src/cli/index.ts +1 -1
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/log-history.ts +172 -0
- package/src/cli/loopback-family.ts +31 -0
- package/src/cli/models-runtime-subcommands.ts +1 -0
- package/src/cli/models-runtime.ts +72 -8
- package/src/cli/observe-stream.ts +2 -0
- package/src/cli/observe.ts +16 -4
- package/src/cli/provider.ts +27 -5
- package/src/cli/registry.ts +28 -8
- package/src/cli/runtime-api.ts +5 -3
- package/src/cli/system-command.ts +40 -8
- package/src/cli/uninstall-client-state.ts +32 -0
- package/src/cli/usage-compare.ts +309 -0
- package/src/cli/usage-model-search.ts +4 -4
- package/src/cli/usage-report.ts +2 -2
- package/src/cli/v2.ts +1 -1
- package/src/client/machine-listener.ts +1 -3
- package/src/client/runtime.ts +1 -0
- package/src/clients/client-path.ts +51 -0
- package/src/clients/config-export/contracts.ts +2 -1
- package/src/clients/config-export.ts +18 -44
- package/src/clients/gentle-shell-path.ts +87 -0
- package/src/codex/account-auto-switch.ts +2 -0
- package/src/codex/account-identity.ts +29 -0
- package/src/codex/account-label.ts +1 -1
- package/src/codex/account-lifecycle.ts +21 -5
- package/src/codex/account-preservation.ts +28 -0
- package/src/codex/account-use-remaining.ts +23 -0
- package/src/codex/app-server-processes.ts +97 -7
- package/src/codex/app-server-restart-service.ts +55 -4
- package/src/codex/auth-api/account-list.ts +4 -0
- package/src/codex/auth-api/account-pause-group.ts +6 -50
- package/src/codex/auth-api/account-preservation.ts +57 -0
- package/src/codex/auth-api/routes.ts +8 -0
- package/src/codex/auth-api/use-remaining-route.ts +36 -0
- package/src/codex/auth-api.ts +0 -1
- package/src/codex/auth-context.ts +2 -2
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +9 -1
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +1 -1
- package/src/codex/catalog/gather-capture.ts +2 -0
- package/src/codex/catalog/metadata.ts +11 -9
- package/src/codex/catalog/model-hints.ts +21 -16
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/parsing.ts +30 -86
- package/src/codex/catalog/provider-models.ts +36 -34
- package/src/codex/catalog/routed-gather.ts +14 -13
- package/src/codex/cli-install-provenance.ts +105 -17
- package/src/codex/cli-update-plan.ts +545 -0
- package/src/codex/config-write-lock.ts +23 -2
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app-restart.ts +21 -0
- package/src/codex/desktop-compatibility/installed-build.ts +24 -6
- package/src/codex/desktop-compatibility/routing-preflight.ts +1 -1
- package/src/codex/desktop-compatibility/runtime.ts +12 -3
- package/src/codex/desktop-compatibility/windows-key-protection.ts +115 -9
- package/src/codex/features.ts +85 -45
- package/src/codex/inject/history-failure.ts +27 -0
- package/src/codex/inject/restore.ts +24 -2
- package/src/codex/inject/routing-classify.ts +4 -3
- package/src/codex/inject.ts +18 -27
- package/src/codex/main-account-hard-lock.ts +30 -1
- package/src/codex/native-profile-startup.ts +1 -1
- package/src/codex/ocx-compaction-history.ts +12 -10
- package/src/codex/paths.ts +86 -1
- package/src/codex/provider-routing.ts +174 -0
- package/src/codex/refresh.ts +2 -2
- package/src/codex/routing/health-store.ts +1 -1
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- package/src/codex/signed-in-identity.ts +43 -0
- package/src/codex/subagent-model-fallback.ts +1 -1
- package/src/codex/sync.ts +6 -1
- package/src/codex/user-identity.ts +16 -5
- package/src/codex/websocket-activity.ts +161 -0
- package/src/codex/websocket-registry.ts +25 -1
- package/src/codex/windows-local-appdata.ts +84 -0
- package/src/combos/failover.ts +6 -1
- package/src/combos/index.ts +1 -0
- package/src/combos/resolve.ts +46 -10
- package/src/combos/types.ts +15 -0
- package/src/config/diagnostics.ts +33 -6
- package/src/config/live-reconcile.ts +38 -2
- package/src/config/load-degrade.ts +17 -0
- package/src/config/opencode-wire-upgrade.ts +54 -0
- package/src/config/opencode-zen-upgrade.ts +53 -0
- package/src/config/owner-registry.ts +22 -17
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +22 -0
- package/src/config/schema/leaf-validators.ts +38 -9
- package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
- package/src/config/schema/native-reasoning-retention.ts +26 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +657 -297
- package/src/grok/sync.ts +1 -1
- package/src/images/loop.ts +116 -27
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/mutation-plan.ts +1 -0
- package/src/integrations/registry.ts +8 -0
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-upstream-tunnel.ts +18 -53
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +19 -3
- package/src/lib/gcp-adc.ts +96 -9
- package/src/lib/gcp-credential-input.ts +12 -0
- package/src/lib/lab-live-route-production.ts +5 -0
- package/src/lib/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/redact.ts +2 -2
- package/src/lib/request-failure-model.ts +2 -0
- package/src/oauth/account-quota-rank.ts +69 -2
- package/src/oauth/anthropic-account-refusal.ts +20 -2
- package/src/oauth/anthropic-oauth-definitions.ts +3 -1
- package/src/oauth/anthropic-routing.ts +1 -21
- package/src/oauth/anthropic.ts +38 -7
- package/src/oauth/callback-server.ts +27 -1
- package/src/oauth/chatgpt-device.ts +1 -1
- package/src/oauth/chatgpt-shared.ts +83 -0
- package/src/oauth/chatgpt.ts +8 -72
- package/src/oauth/dsh.ts +203 -0
- package/src/oauth/generic-account-failover.ts +40 -11
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +184 -109
- package/src/oauth/local-token-detect.ts +21 -1
- package/src/oauth/login-cli-errors.ts +44 -0
- package/src/oauth/login-cli.ts +69 -7
- package/src/oauth/login-flow-state.ts +371 -23
- package/src/oauth/meta-muse-errors.ts +41 -0
- package/src/oauth/meta-muse-keychain.ts +52 -0
- package/src/oauth/meta-muse.ts +51 -84
- package/src/oauth/model-discovery-auth.ts +3 -0
- package/src/oauth/pool-settings-capability.ts +4 -2
- package/src/oauth/refresh-flight-retirement.ts +48 -0
- package/src/oauth/session-affinity-key.ts +23 -0
- package/src/oauth/store-anthropic-instance.ts +9 -2
- package/src/oauth/store.ts +12 -5
- package/src/protocols/encoders/adapter-events.ts +6 -5
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/provider-summary.ts +2 -1
- package/src/providers/api-key-cooldowns.ts +49 -0
- package/src/providers/api-key-resolve.ts +11 -6
- package/src/providers/api-key-response-ownership.ts +57 -0
- package/src/providers/api-key-selection.ts +31 -6
- package/src/providers/api-keys.ts +214 -3
- package/src/providers/claude-cli-usage.ts +96 -0
- package/src/providers/dsh-account-destination.ts +31 -0
- package/src/providers/dsh-account-models.ts +41 -0
- package/src/providers/gcp-credential-cleanup.ts +59 -0
- package/src/providers/key-failover.ts +54 -50
- package/src/providers/key-store.ts +7 -2
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/model-rename-migration.ts +2 -2
- package/src/providers/openai-tiers-destination.ts +11 -2
- package/src/providers/openai-tiers.ts +5 -12
- package/src/providers/opencode-go-transport.ts +3 -1
- package/src/providers/opencode-zen-rate-limit.ts +7 -2
- package/src/providers/quota/account-cache.ts +6 -5
- package/src/providers/quota/account-row.ts +34 -0
- package/src/providers/quota/antigravity.ts +28 -7
- package/src/providers/quota/dsh-account.ts +70 -0
- package/src/providers/quota/vendor-probes-oauth.ts +1 -93
- package/src/providers/quota-types.ts +2 -0
- package/src/providers/quota.ts +14 -20
- package/src/providers/registry/entries-core.ts +37 -8
- package/src/providers/registry/types.ts +1 -1
- package/src/providers/request-pacing.ts +77 -19
- package/src/providers/routed-config.ts +339 -0
- package/src/providers/service-tier.ts +2 -1
- package/src/quota/window-mapping.ts +5 -2
- package/src/responses/anthropic-progress.ts +34 -0
- package/src/responses/assistant-replay.ts +35 -0
- package/src/responses/compaction-history-framing.ts +65 -0
- package/src/responses/compaction.ts +6 -1
- package/src/responses/incomplete-reason.ts +31 -0
- package/src/responses/parser.ts +50 -5
- package/src/responses/reasoning-envelope.ts +4 -0
- package/src/responses/reasoning-replay-cache.ts +74 -6
- package/src/responses/reasoning-retention.ts +173 -0
- package/src/responses/retained-compaction.ts +46 -0
- package/src/responses/schema.ts +23 -0
- package/src/router.ts +8 -321
- package/src/routing/analytics.ts +7 -15
- package/src/routing/health.ts +37 -22
- package/src/routing/history/indexer.ts +6 -5
- package/src/routing/history/ingest.ts +6 -2
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +17 -0
- package/src/server/auth-cors.ts +11 -0
- package/src/server/chat-completions.ts +7 -1
- package/src/server/chat-native-reasoning.ts +123 -0
- package/src/server/chat-native-sse.ts +110 -7
- package/src/server/chat-native.ts +76 -17
- package/src/server/claude-messages.ts +56 -414
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/live-sideband.ts +3 -2
- package/src/server/index/model-alias-id.ts +36 -0
- package/src/server/index/optional-listeners.ts +2 -0
- package/src/server/index/serve-options.ts +20 -6
- package/src/server/index/startup-warnings.ts +2 -0
- package/src/server/index/websocket-handler.ts +18 -4
- package/src/server/index.ts +8 -8
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/lifecycle.ts +13 -1
- package/src/server/management/advisor-routes.ts +15 -4
- package/src/server/management/claude-desktop-picker-routes.ts +1 -1
- package/src/server/management/combo-routes.ts +176 -50
- package/src/server/management/companion-routes.ts +1 -1
- package/src/server/management/config-routes.ts +75 -4
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/model-routes.ts +11 -6
- package/src/server/management/model-rows.ts +14 -0
- package/src/server/management/native-integration-routes.ts +2 -7
- package/src/server/management/native-reasoning-retention-routes.ts +41 -0
- package/src/server/management/oauth-account-routes.ts +87 -14
- package/src/server/management/provider-gcp-credential-routes.ts +29 -0
- package/src/server/management/provider-key-pacing-routes.ts +31 -0
- package/src/server/management/provider-overwrite-carry.ts +7 -0
- package/src/server/management/provider-routes.ts +25 -30
- package/src/server/management/route-registry.ts +10 -1
- package/src/server/management-api.ts +3 -1
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +117 -24
- package/src/server/messages-shared.ts +431 -0
- package/src/server/relay.ts +1 -1
- package/src/server/request-log-terminal-status.ts +1 -1
- package/src/server/request-log.ts +6 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +47 -19
- package/src/server/responses/adapter-delivery.ts +72 -12
- package/src/server/responses/adapter-dispatch.ts +46 -3
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +73 -41
- package/src/server/responses/anthropic-thinking-recovery.ts +153 -0
- package/src/server/responses/codex-ws-wire.ts +15 -8
- package/src/server/responses/combo-stream-preflight.ts +3 -2
- package/src/server/responses/compact.ts +303 -125
- package/src/server/responses/compaction-recovery.ts +20 -10
- package/src/server/responses/compaction-retention.ts +61 -0
- package/src/server/responses/context-overflow.ts +11 -6
- package/src/server/responses/core-auth.ts +6 -0
- package/src/server/responses/core-codex-account.ts +11 -1
- package/src/server/responses/core-combo-failure.ts +2 -0
- package/src/server/responses/core-combo.ts +121 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +8 -4
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +7 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +23 -3
- package/src/server/responses/core.ts +2 -115
- package/src/server/responses/fetch-helpers.ts +73 -19
- package/src/server/responses/main-hard-lock-routing.ts +109 -0
- package/src/server/responses/native-response-control.ts +4 -2
- package/src/server/responses/passthrough-delivery.ts +3 -1
- package/src/server/responses/passthrough-dispatch.ts +15 -13
- package/src/server/responses/passthrough-execution.ts +3 -2
- package/src/server/responses/request-prepare.ts +77 -12
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +104 -27
- package/src/server/responses/response-effects.ts +2 -0
- package/src/server/responses/response-tool-ownership.ts +21 -0
- package/src/server/responses/run-turn-execution.ts +96 -30
- package/src/server/responses/sidecar-execution.ts +63 -9
- package/src/server/responses/sidecar-send-budget.ts +2 -2
- package/src/server/responses/terminal-guard.ts +2 -16
- package/src/server/responses-undeclared-tool-guard.ts +1 -1
- package/src/server/ws-bridge.ts +2 -0
- package/src/service/auth-preflight.ts +71 -0
- package/src/service/cli.ts +4 -2
- package/src/service/definition.ts +97 -0
- package/src/service/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +76 -57
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +72 -9
- package/src/service/repair.ts +2 -1
- package/src/service/state.ts +7 -8
- package/src/service/systemd.ts +1 -1
- package/src/service/windows-guarded-manager.ts +2 -1
- package/src/service/windows-ops.ts +1 -1
- package/src/service/windows-scheduler.ts +2 -7
- package/src/service/windows-taskxml.ts +2 -2
- package/src/service/windows-wscript.ts +14 -0
- package/src/service.ts +4 -2
- package/src/sidecar/auth.ts +2 -0
- package/src/types/config.ts +64 -3
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +35 -0
- package/src/types/provider.ts +32 -6
- package/src/types/request.ts +31 -2
- package/src/types/wire.ts +31 -37
- package/src/types.ts +2 -0
- package/src/update/npm-invocation.mjs +12 -2
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +3 -0
- package/src/usage/ledger-retention.ts +2 -1
- package/src/usage/ledger-scanner.ts +6 -5
- package/src/usage/log.ts +17 -8
- package/src/usage/summary.ts +30 -11
- package/src/usage/telemetry-contract.ts +1 -0
- package/src/usage/user-cost-overlays.ts +14 -19
- package/src/usage/user-cost-validation.ts +38 -0
- package/src/vision/eligibility.ts +13 -0
- package/src/web-search/anthropic-history.ts +59 -0
- package/src/web-search/loop.ts +236 -73
- package/src/web-search/progress-stream.ts +15 -2
- package/src/web-search/run-turn-loop.ts +9 -1
- package/gui/dist/assets/App-BjlttVLy.js +0 -58
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-BMssP4k3.js +0 -94
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +0 -1
package/src/service/launchd.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { BUN_RUNTIME_PATH_ENV, BUN_RUNTIME_SOURCE_ENV, durableBunRuntime, type D
|
|
|
11
11
|
import { serviceApiTokenFilePath } from "../lib/service-secrets";
|
|
12
12
|
import { recordOwnedConfigPath } from "../lib/config-ownership";
|
|
13
13
|
import { writeServiceApiTokenFile, assertLiveServiceManagerAllowed } from "./guards";
|
|
14
|
-
import { resolveServiceListenPort, buildServiceShellCommand, buildServiceLauncherShellCommand,
|
|
14
|
+
import { resolveServiceListenPort, buildServiceShellCommand, buildServiceLauncherShellCommand, resolvedProxyEnv } from "./definition";
|
|
15
15
|
import { SERVICE_MANAGED_ENV, LABEL, cliEntry, filterTransientServicePath, logPath, serviceStatePath, currentCodexSqliteHomeAbsolute, type ServiceInstallState, writeServiceInstallState, readServiceInstallState } from "./state";
|
|
16
16
|
import { writeServiceDefinitionFile } from "./windows-ops";
|
|
17
17
|
import { readTextOrNull } from "./windows-taskxml";
|
|
@@ -359,8 +359,11 @@ export function probeLaunchdLoadState(deps: {
|
|
|
359
359
|
for (const domain of [`gui/${uid}`, `user/${uid}`]) {
|
|
360
360
|
const printed = run(["print", `${domain}/${LABEL}`]);
|
|
361
361
|
if (printed.status === 0) {
|
|
362
|
+
// Use this backend's plist, then the configured service port. Foreign artifacts
|
|
363
|
+
// can exist on macOS: systemdListenPort has no platform gate, so the general
|
|
364
|
+
// installed-artifact chain could incorrectly select a copied Linux unit's port.
|
|
362
365
|
const expected = (deps.expectedCommand
|
|
363
|
-
?? (() => expectedLaunchdCommand(
|
|
366
|
+
?? (() => expectedLaunchdCommand(launchdListenPort() ?? resolveServiceListenPort())))();
|
|
364
367
|
const printedText = `${printed.stdout}\n${printed.stderr}`;
|
|
365
368
|
return {
|
|
366
369
|
state: printedText.includes(expected) ? "loaded-current" : "loaded-stale",
|
|
@@ -382,8 +385,16 @@ export function probeLaunchdLoadState(deps: {
|
|
|
382
385
|
return { state: "not-loaded" };
|
|
383
386
|
}
|
|
384
387
|
|
|
385
|
-
/**
|
|
386
|
-
|
|
388
|
+
/**
|
|
389
|
+
* Up to 125 × 200 ms = 25 s: long enough for an evicted job to finish EXITING. Measured on
|
|
390
|
+
* macOS 26: `bootout` returns at once, the label stays registered (`state = SIGTERMed`) until
|
|
391
|
+
* the process exits, and `bootstrap` fails with 5 for that whole window. The proxy drains on
|
|
392
|
+
* SIGTERM (`shutdownTimeoutMs`, 5 s default) behind a launcher that waits for it, so a 1 s
|
|
393
|
+
* settle bootstrapped into that window, the retry and the rollback raced it too, and the old
|
|
394
|
+
* job then exited leaving nothing loaded. launchd SIGKILLs at its default `ExitTimeOut` of
|
|
395
|
+
* 20 s, which bounds this wait.
|
|
396
|
+
*/
|
|
397
|
+
const LAUNCHD_SETTLE_ATTEMPTS = 125;
|
|
387
398
|
|
|
388
399
|
const LAUNCHD_SETTLE_DELAY_MS = 200;
|
|
389
400
|
|
|
@@ -769,6 +780,7 @@ export function restartLaunchdJob(deps: {
|
|
|
769
780
|
export function startLaunchd(deps: {
|
|
770
781
|
launchctl?: typeof runLaunchctl;
|
|
771
782
|
matches?: typeof launchdJobMatchesPlist;
|
|
783
|
+
readPlist?: () => string;
|
|
772
784
|
} = {}): void {
|
|
773
785
|
const run = deps.launchctl ?? runLaunchctl;
|
|
774
786
|
const p = plistPath();
|
|
@@ -778,16 +790,24 @@ export function startLaunchd(deps: {
|
|
|
778
790
|
// already be bootstrapped from THIS plist, which is a no-op rather than an error.
|
|
779
791
|
// `install` can assume a stale job (it just rewrote the plist); `start` cannot, and
|
|
780
792
|
// throwing here would break `ocx service start` on every healthy service.
|
|
793
|
+
// A live job can outlast its deleted or unreadable plist. Only a readable
|
|
794
|
+
// definition can justify the no-op; use the same snapshot for port parsing.
|
|
795
|
+
let plist: string | null;
|
|
796
|
+
try { plist = (deps.readPlist ?? (() => readFileSync(p, "utf8")))(); }
|
|
797
|
+
catch { plist = null; }
|
|
798
|
+
// Match the same launchd-only port choice as probeLaunchdLoadState above.
|
|
781
799
|
const live = (deps.matches ?? launchdJobMatchesPlist)(
|
|
782
|
-
expectedLaunchdCommand(
|
|
800
|
+
expectedLaunchdCommand(launchdListenPort({ readPlist: () => plist ?? "" }) ?? resolveServiceListenPort()),
|
|
783
801
|
);
|
|
784
|
-
if (live.loaded && live.matchesPlist) {
|
|
802
|
+
if (plist !== null && live.loaded && live.matchesPlist) {
|
|
785
803
|
console.log("ℹ️ service was already loaded from the current plist; nothing to do.");
|
|
786
804
|
return;
|
|
787
805
|
}
|
|
788
806
|
throw new Error(
|
|
789
807
|
`launchctl could not load ${p}: ${loaded.stderr || "load reported failure"}\n`
|
|
790
|
-
+ (
|
|
808
|
+
+ (plist === null
|
|
809
|
+
? "The service plist is missing or unreadable. Run 'ocx service repair' to restore it."
|
|
810
|
+
: live.loaded
|
|
791
811
|
? `launchd is running an OLDER plist. Fix:\n launchctl bootout ${launchdGuiDomain()}/${LABEL}\n ocx service repair`
|
|
792
812
|
: "The job is not loaded. Run 'ocx service repair' to reload it."),
|
|
793
813
|
);
|
package/src/service/logging.ts
CHANGED
|
@@ -1,24 +1,81 @@
|
|
|
1
|
-
import { closeSync, fstatSync, ftruncateSync, openSync, readSync, renameSync, writeSync } from "node:fs";
|
|
1
|
+
import { closeSync, constants, fstatSync, fsyncSync, ftruncateSync, lstatSync, openSync, readSync, realpathSync, renameSync, statSync, writeSync, type BigIntStats } from "node:fs";
|
|
2
2
|
import { formatWithOptions } from "node:util";
|
|
3
|
-
import { dirname } from "node:path";
|
|
3
|
+
import { basename, dirname, join } from "node:path";
|
|
4
4
|
import { recordOwnedConfigPath } from "../lib/config-ownership";
|
|
5
5
|
|
|
6
6
|
export const SERVICE_LOG_MAX_BYTES = 20 * 1024 * 1024;
|
|
7
7
|
export const SERVICE_LOG_CHECK_INTERVAL_MS = 60_000;
|
|
8
8
|
|
|
9
|
+
function regularLeaf(path: string): BigIntStats | undefined {
|
|
10
|
+
try {
|
|
11
|
+
const info = lstatSync(path, { bigint: true });
|
|
12
|
+
if (!info.isFile() || info.nlink !== 1n || info.ino === 0n) throw new Error("Unsafe service log leaf");
|
|
13
|
+
return info;
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function sameFile(left: BigIntStats | undefined, right: BigIntStats | undefined): boolean {
|
|
21
|
+
return left !== undefined && right !== undefined && left.dev === right.dev && left.ino === right.ino;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function matchesDescriptor(path: string, fd: number, expected: BigIntStats): boolean {
|
|
25
|
+
const info = fstatSync(fd, { bigint: true });
|
|
26
|
+
return info.isFile() && info.nlink === 1n && sameFile(expected, info)
|
|
27
|
+
&& sameFile(info, regularLeaf(path));
|
|
28
|
+
}
|
|
29
|
+
|
|
9
30
|
/** Preserve the manager's open append descriptor; retain at most two bounded archives. */
|
|
10
|
-
export function rotateServiceLog(
|
|
31
|
+
export function rotateServiceLog(requestedPath: string, maxBytes = SERVICE_LOG_MAX_BYTES): boolean {
|
|
11
32
|
let source: number | undefined;
|
|
12
33
|
let archive: number | undefined;
|
|
13
34
|
try {
|
|
14
35
|
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) return false;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
36
|
+
// Resolve only the parent: exclusive creation through a Windows junction
|
|
37
|
+
// can report EEXIST for a missing leaf. Never resolve away a leaf symlink.
|
|
38
|
+
const parent = dirname(requestedPath);
|
|
39
|
+
const directory = statSync(parent, { bigint: true });
|
|
40
|
+
if (!directory.isDirectory() || directory.ino === 0n) return false;
|
|
41
|
+
const canonicalParent = realpathSync(parent);
|
|
42
|
+
const path = join(canonicalParent, basename(requestedPath));
|
|
43
|
+
const sameDirectory = () => {
|
|
44
|
+
const alias = statSync(parent, { bigint: true });
|
|
45
|
+
const canonical = statSync(canonicalParent, { bigint: true });
|
|
46
|
+
return alias.isDirectory() && canonical.isDirectory()
|
|
47
|
+
&& sameFile(directory, alias) && sameFile(directory, canonical);
|
|
48
|
+
};
|
|
49
|
+
const matches = (suffix: string, fd: number, expected: BigIntStats) => sameDirectory()
|
|
50
|
+
&& matchesDescriptor(`${path}${suffix}`, fd, expected)
|
|
51
|
+
&& matchesDescriptor(`${requestedPath}${suffix}`, fd, expected);
|
|
52
|
+
if (!sameDirectory()) return false;
|
|
53
|
+
const original = regularLeaf(requestedPath);
|
|
54
|
+
if (!original) return false;
|
|
55
|
+
// Windows does not expose O_NOFOLLOW: descriptor/leaf identity checks still
|
|
56
|
+
// apply there. Do not reject legitimate junctions in the home path.
|
|
57
|
+
const guardedFlags = process.platform === "win32" ? 0 : constants.O_NOFOLLOW | constants.O_NONBLOCK;
|
|
58
|
+
source = openSync(path, constants.O_RDWR | guardedFlags);
|
|
59
|
+
if (!matches("", source, original)) return false;
|
|
60
|
+
const size = Number(fstatSync(source, { bigint: true }).size);
|
|
61
|
+
if (!Number.isSafeInteger(size) || size <= maxBytes) return false;
|
|
62
|
+
const previous = regularLeaf(`${path}.1`);
|
|
63
|
+
const oldest = regularLeaf(`${path}.2`);
|
|
64
|
+
if (!matches("", source, original)) return false;
|
|
65
|
+
if (previous) {
|
|
66
|
+
if (!sameFile(previous, regularLeaf(`${path}.1`))) return false;
|
|
67
|
+
const currentOldest = regularLeaf(`${path}.2`);
|
|
68
|
+
if (oldest ? !sameFile(oldest, currentOldest) : currentOldest !== undefined) return false;
|
|
69
|
+
if (!sameDirectory()) return false;
|
|
70
|
+
renameSync(`${path}.1`, `${path}.2`);
|
|
71
|
+
if (!sameFile(previous, regularLeaf(`${path}.2`))) return false;
|
|
20
72
|
}
|
|
21
|
-
|
|
73
|
+
// Never truncate an existing archive, even if another rotation or a path
|
|
74
|
+
// replacement wins the gap after rename. Failure leaves the source intact.
|
|
75
|
+
if (!sameDirectory()) return false;
|
|
76
|
+
archive = openSync(`${path}.1`, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | guardedFlags, 0o600);
|
|
77
|
+
const created = fstatSync(archive, { bigint: true });
|
|
78
|
+
if (!matches(".1", archive, created)) return false;
|
|
22
79
|
// A legacy unbounded file may be huge. Copy only its newest cap-sized tail,
|
|
23
80
|
// using fixed memory, before truncating the original inode.
|
|
24
81
|
const buffer = Buffer.alloc(Math.min(64 * 1024, maxBytes));
|
|
@@ -28,12 +85,18 @@ export function rotateServiceLog(path: string, maxBytes = SERVICE_LOG_MAX_BYTES)
|
|
|
28
85
|
if (read === 0) return false;
|
|
29
86
|
let written = 0;
|
|
30
87
|
while (written < read) {
|
|
88
|
+
if (!matches(".1", archive, created)) return false;
|
|
31
89
|
const count = writeSync(archive, buffer, written, read - written);
|
|
32
90
|
if (count === 0) return false;
|
|
33
91
|
written += count;
|
|
34
92
|
}
|
|
35
93
|
position += read;
|
|
36
94
|
}
|
|
95
|
+
fsyncSync(archive);
|
|
96
|
+
// Revalidate both names and open objects at the destructive boundary. These
|
|
97
|
+
// checks detect observed replacement; they are not an atomic namespace lock.
|
|
98
|
+
if (!matches(".1", archive, created)
|
|
99
|
+
|| !matches("", source, original)) return false;
|
|
37
100
|
ftruncateSync(source, 0);
|
|
38
101
|
return true;
|
|
39
102
|
} catch {
|
package/src/service/repair.ts
CHANGED
|
@@ -6,7 +6,8 @@ import { randomUUID } from "node:crypto";
|
|
|
6
6
|
import { defaultWinswEntry, installWinswService } from "../lib/winsw";
|
|
7
7
|
import { resolveWindowsTaskDiagnosticUserId, diagnoseService } from "./diagnostics";
|
|
8
8
|
import type { ServiceDiagnostic } from "./diagnostics";
|
|
9
|
-
import {
|
|
9
|
+
import { assertServiceAuthEnvironment } from "./auth-preflight";
|
|
10
|
+
import { assertServiceEnvironmentMatchesInstall } from "./guards";
|
|
10
11
|
import { installLaunchd, restartLaunchdJob } from "./launchd";
|
|
11
12
|
import type { LaunchdInstallOutcome } from "./launchd";
|
|
12
13
|
import { TASK, writeServiceInstallState, serviceSourceDir, resolveServiceOwnership, serviceLauncherPathDiagnostic } from "./state";
|
package/src/service/state.ts
CHANGED
|
@@ -172,20 +172,19 @@ function defaultOpenCodexHome(): string {
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
export function serviceStatePathsForOpenCodexHome(opencodexHome: string): string[] {
|
|
175
|
-
|
|
175
|
+
// Bun caches the startup home: parallel workers inherit the wrapper's HOME even
|
|
176
|
+
// after the preload gives each one a sandbox. Tests must not share that mirror,
|
|
177
|
+
// including serial cases that select different OPENCODEX_HOME fixtures.
|
|
178
|
+
return serviceStateFilesFor(opencodexHome, isTestHomeGuardArmed() ? opencodexHome : defaultOpenCodexHome());
|
|
176
179
|
}
|
|
177
180
|
|
|
178
181
|
export function serviceStatePaths(): string[] {
|
|
179
182
|
const paths = serviceStatePathsForOpenCodexHome(currentOpenCodexHome());
|
|
180
183
|
if (!isTestHomeGuardArmed()) return paths;
|
|
181
184
|
/*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
* points at a sandbox still writes their live install state — observed while building
|
|
186
|
-
* the launchd repair coverage: one case replaced the real record's codexHome and
|
|
187
|
-
* opencodexHome with temp-directory paths. Drop it rather than deny the write, so the
|
|
188
|
-
* sandbox path keeps working and the real one is simply not in the list.
|
|
185
|
+
* The test resolver omits the legacy mirror; still reject an explicit or fallback
|
|
186
|
+
* OPENCODEX_HOME that names the developer's real home. A missing fixture pin must
|
|
187
|
+
* never allow a test to read or replace their live service installation.
|
|
189
188
|
*
|
|
190
189
|
* The predicate is the guard's own, not a local `resolve()` compare: the guard
|
|
191
190
|
* canonicalizes through `realpath`, and on macOS a sandbox under `/var/folders/...`
|
package/src/service/systemd.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { BUN_RUNTIME_PATH_ENV, BUN_RUNTIME_SOURCE_ENV, durableBunRuntime, type D
|
|
|
8
8
|
import { recordOwnedConfigPath } from "../lib/config-ownership";
|
|
9
9
|
import { systemdProperty } from "../service-manager-probe";
|
|
10
10
|
import { writeServiceApiTokenFile, sh } from "./guards";
|
|
11
|
-
import { shellQuote, buildServiceShellCommand, buildServiceLauncherShellCommand, resolvedProxyEnv } from "./
|
|
11
|
+
import { shellQuote, buildServiceShellCommand, buildServiceLauncherShellCommand, resolvedProxyEnv } from "./definition";
|
|
12
12
|
import type { ServiceInstallCleanupOps } from "./orchestration";
|
|
13
13
|
import { SERVICE_MANAGED_ENV, TASK, cliEntry, filterTransientServicePath, stableLauncherEntry, logPath, serviceStatePath, currentCodexSqliteHomeAbsolute, writeServiceInstallState } from "./state";
|
|
14
14
|
import { writeServiceDefinitionFile } from "./windows-ops";
|
|
@@ -29,7 +29,8 @@ import { cachedCurrentWindowsIdentity, resolveCurrentWindowsPrincipal, WINDOWS_P
|
|
|
29
29
|
import { statusWinswRaw, winswExePath, WINSW_SERVICE_ID, type WinswStatus } from "../lib/winsw";
|
|
30
30
|
import type { GuardedManagerStopped, GuardedManagerTarget } from "./guarded-manager-target";
|
|
31
31
|
import { TASK, windowsLauncherVbsPath, windowsServiceScriptPath } from "./state";
|
|
32
|
-
import { probeWindowsSchedulerTask, querySchtasks,
|
|
32
|
+
import { probeWindowsSchedulerTask, querySchtasks, type WindowsSchedulerTaskProbe } from "./windows-scheduler";
|
|
33
|
+
import { windowsWscript } from "./windows-wscript";
|
|
33
34
|
import { windowsTaskRegistrationHealthy, windowsTaskRegistrationRefreshableLegacy } from "./windows-taskxml";
|
|
34
35
|
|
|
35
36
|
/** Runtime table of one Win32 process; a null parent means the chain is unreadable. */
|
|
@@ -5,7 +5,7 @@ import { winswXmlPath } from "../lib/winsw";
|
|
|
5
5
|
import { hardenSecretPath } from "../lib/windows-secret-acl";
|
|
6
6
|
import { durableBunRuntime, type DurableBunRuntime } from "../lib/bun-runtime";
|
|
7
7
|
import { assertSelectedRuntimeWritable, type RuntimePreflightDeps } from "../lib/bun-runtime-preflight";
|
|
8
|
-
import { parseBakedListenPort } from "./
|
|
8
|
+
import { parseBakedListenPort } from "./definition";
|
|
9
9
|
import { windowsServiceScriptPath } from "./state";
|
|
10
10
|
import { execFileSync } from "node:child_process";
|
|
11
11
|
import { existsSync, mkdirSync, mkdtempSync, rmdirSync, unlinkSync } from "node:fs";
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { findLiveProxy } from "../server/proxy-liveness";
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
-
import { join, resolve } from "node:path";
|
|
5
4
|
import { randomUUID } from "node:crypto";
|
|
6
5
|
import { ELEVATION_REQUEST_TIMEOUT_MS, OCX_ELEVATED_PROTOCOL_FAILED, raceWithTimeout, resolveTrustedWindowsSchtasksExe, startElevatedSchtasksCreateAndRun, runWindowsElevated, toWindowsSchtasksError, WindowsElevationError, type ElevatedSchedulerOutcome, type ElevatedSchtasksCreateAndRunExecution, type ElevatedSchtasksCreateAndRunResult } from "../lib/windows-elevation";
|
|
7
6
|
import { statusWinswRaw } from "../lib/winsw";
|
|
@@ -10,9 +9,10 @@ import { isTestHomeGuardArmed } from "../lib/test-home-guard";
|
|
|
10
9
|
import { TASK, windowsServiceScriptPath, windowsLauncherVbsPath, windowsTaskXmlPath, writeServiceInstallState } from "./state";
|
|
11
10
|
import { buildWindowsSchtasksCreateArgs, windowsTaskRegistrationOwnedByAttempt, windowsTaskRegistrationHealthy } from "./windows-taskxml";
|
|
12
11
|
import type { ExpectedWindowsTaskUserId } from "./windows-taskxml";
|
|
13
|
-
import { win32 } from "node:path";
|
|
14
12
|
import { WINSW_SERVICE_ID } from "../lib/winsw";
|
|
15
13
|
|
|
14
|
+
export { windowsWscript } from "./windows-wscript";
|
|
15
|
+
|
|
16
16
|
/**
|
|
17
17
|
* Decode schtasks stdout. `/query /xml` emits UTF-16LE (often with BOM) because the
|
|
18
18
|
* registered task document is UTF-16; reading that as UTF-8 makes every health check
|
|
@@ -69,11 +69,6 @@ function windowsSchtasks(): string {
|
|
|
69
69
|
return resolveTrustedWindowsSchtasksExe();
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
export function windowsWscript(): string {
|
|
73
|
-
const candidate = join(process.env.SystemRoot ?? "C:\\Windows", "System32", "wscript.exe");
|
|
74
|
-
return existsSync(candidate) ? candidate : "wscript.exe";
|
|
75
|
-
}
|
|
76
|
-
|
|
77
72
|
let querySchtasksForTests: ((args: string[]) => string) | null = null;
|
|
78
73
|
|
|
79
74
|
export function querySchtasks(args: string[]): string {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { WINDOWS_WRAPPER_PROTOCOL_ENV, WINDOWS_WRAPPER_STAY_OUT_EXIT_CODE } from "./windows-wrapper-exit";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { TASK, windowsServiceScriptPath, windowsLauncherVbsPath, windowsTaskXmlPath } from "./state";
|
|
4
|
-
import { windowsWscript } from "./windows-
|
|
4
|
+
import { windowsWscript } from "./windows-wscript";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { BUN_RUNTIME_PATH_ENV, BUN_RUNTIME_SOURCE_ENV } from "../lib/bun-runtime";
|
|
7
7
|
import { REAL_BUN_MIN_BYTES } from "../lib/bun-binary-validator.mjs";
|
|
8
8
|
import { serviceApiTokenFilePath } from "../lib/service-secrets";
|
|
9
9
|
import { windowsEnvIndirectBatchPathList, windowsEnvIndirectBatchValue } from "../lib/win-paths";
|
|
10
10
|
import { cachedCurrentWindowsIdentity, resolveCurrentWindowsPrincipal, WINDOWS_PRINCIPAL_LOOKUP_TIMEOUT_MS } from "../lib/windows-user-principal";
|
|
11
|
-
import { resolveServiceListenPort, resolvedProxyEnv } from "./
|
|
11
|
+
import { resolveServiceListenPort, resolvedProxyEnv } from "./definition";
|
|
12
12
|
import { cliEntry, filterTransientServicePath, serviceLogPath, currentCodexSqliteHomeAbsolute } from "./state";
|
|
13
13
|
|
|
14
14
|
function windowsBatchValue(value: string): string {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `wscript.exe` the Task Scheduler XML must name.
|
|
3
|
+
*
|
|
4
|
+
* Leaf module on purpose: `windows-taskxml.ts` needs it to render the task document,
|
|
5
|
+
* and taking it from `windows-scheduler.ts` made the two one cyclic component.
|
|
6
|
+
* `windows-scheduler` owns the schtasks process surface, not this one path lookup.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
|
|
11
|
+
export function windowsWscript(): string {
|
|
12
|
+
const candidate = join(process.env.SystemRoot ?? "C:\\Windows", "System32", "wscript.exe");
|
|
13
|
+
return existsSync(candidate) ? candidate : "wscript.exe";
|
|
14
|
+
}
|
package/src/service.ts
CHANGED
|
@@ -13,8 +13,10 @@ export { acquireOwnershipMutationLease, withOwnershipMutationLease } from "./ser
|
|
|
13
13
|
export type { ManagingCliRole, ManagingCliObservation, RegisteredManagingCliInvocation, ServiceTakeoverCompatibilityInput, ServiceTakeoverCompatibility } from "./service/ownership-compatibility";
|
|
14
14
|
export { registeredManagingCliInvocation, assessServiceTakeoverCompatibility, sameServiceTakeoverCompatibility } from "./service/ownership-compatibility";
|
|
15
15
|
export type { ServiceApiTokenOrigin, ProvisionedServiceApiToken } from "./service/guards";
|
|
16
|
-
export { ServiceOwnershipError, isServiceOwnershipError, serviceEnvironmentOwnedHere, assertServiceEnvironmentMatchesInstall,
|
|
17
|
-
export {
|
|
16
|
+
export { ServiceOwnershipError, isServiceOwnershipError, serviceEnvironmentOwnedHere, assertServiceEnvironmentMatchesInstall, assertNotAdminToken, writeServiceApiTokenFile, assertLiveServiceManagerAllowed } from "./service/guards";
|
|
17
|
+
export { serviceRetryCommand, assertServiceAuthEnvironment } from "./service/auth-preflight";
|
|
18
|
+
export { resolveServiceListenPort, resolvedProxyEnv } from "./service/definition";
|
|
19
|
+
export { installedServiceListenPort, SERVICE_INSTALL_HEALTH_MS, SERVICE_INSTALL_HEALTH_WINDOWS_MS, serviceInstallHealthMs, confirmServiceServing, reportServiceServing } from "./service/health";
|
|
18
20
|
export type { LaunchdLoadState, LaunchdLoadProbe, LaunchdInstallOutcome } from "./service/launchd";
|
|
19
21
|
export { buildPlist, reusePreviousPlistPathVariable, expectedLaunchdCommand, launchdListenPort, runLaunchctl, launchctlLoadFailed, launchdJobMatchesPlist, launchdEvictionTargets, probeLaunchdLoadState, installLaunchd, restartLaunchdJob, startLaunchd } from "./service/launchd";
|
|
20
22
|
export { systemdListenPort, buildUnit, systemdNeedsDaemonReload, uninstallSystemd, systemdServiceInstallCleanupOps } from "./service/systemd";
|
package/src/sidecar/auth.ts
CHANGED
|
@@ -134,6 +134,8 @@ function findAnthropicAuthProvider(
|
|
|
134
134
|
// its token path and the token guardian refuse an unmarked row of that name, so offering such a
|
|
135
135
|
// custom row here would advertise credentials no helper executor will spend.
|
|
136
136
|
if (providerName === "anthropic2") continue;
|
|
137
|
+
// The DSH account token is pinned to its own adapter and DeepSeek host; no helper may spend it.
|
|
138
|
+
if (providerName === "dsh-account") continue;
|
|
137
139
|
if (provider.disabled === true) continue;
|
|
138
140
|
if (provider.adapter !== "anthropic" || provider.authMode !== "oauth") continue;
|
|
139
141
|
const set = getAccountSet(providerName);
|
package/src/types/config.ts
CHANGED
|
@@ -349,6 +349,12 @@ export interface OcxApiKeyEntry {
|
|
|
349
349
|
* that is the only point at which the model about to be billed is known.
|
|
350
350
|
*/
|
|
351
351
|
allowedModels?: string[];
|
|
352
|
+
/**
|
|
353
|
+
* Lets this key reach combo targets marked `metered`. Absent means no: an issued key stops
|
|
354
|
+
* at the last unmetered target. The environment token never qualifies. A loopback bind
|
|
355
|
+
* reads no key; local operator consent is instead declared on the combo.
|
|
356
|
+
*/
|
|
357
|
+
allowMeteredComboTargets?: boolean;
|
|
352
358
|
}
|
|
353
359
|
|
|
354
360
|
export interface OcxPendingApiKeyRotation {
|
|
@@ -569,6 +575,21 @@ export interface OcxConfig {
|
|
|
569
575
|
};
|
|
570
576
|
/** Enable the shipped model alias patterns for providers without an override. */
|
|
571
577
|
defaultModelAliases?: boolean;
|
|
578
|
+
/**
|
|
579
|
+
* When `true`, the raw OpenAI-compatible `/v1/models` list publishes a safe alias (e.g.
|
|
580
|
+
* `SEK-glm-4.7-flash`) as the row `id` instead of `provider/nativeId`
|
|
581
|
+
* (e.g. `Seek/glm-4.7-flash`). The original `provider/nativeId` moves to a
|
|
582
|
+
* new `native_id` field so clients that need it can still read it.
|
|
583
|
+
*
|
|
584
|
+
* **Default: `false`** — the historical `provider/nativeId` form remains the
|
|
585
|
+
* published `id`, and the alias is in the informational `alias_of` field.
|
|
586
|
+
*
|
|
587
|
+
* Codex (`client_version`) and Anthropic-specific discovery retain their own
|
|
588
|
+
* identities and are unchanged. Ambiguous or shadowed short aliases retain the
|
|
589
|
+
* qualified ID, as do aliases with unparseable synthetic Fast siblings.
|
|
590
|
+
* Existing requests using `provider/nativeId` continue to work unchanged.
|
|
591
|
+
*/
|
|
592
|
+
preferModelAliasIds?: boolean;
|
|
572
593
|
/**
|
|
573
594
|
* Opt-in Cursor Private Inference compatibility rows. When true, `/v1/models`
|
|
574
595
|
* adds `<base-id>--<effort>` selectors for reasoning-capable model ids absent
|
|
@@ -605,6 +626,13 @@ export interface OcxConfig {
|
|
|
605
626
|
codexMainAccountHardLock?: boolean;
|
|
606
627
|
/** Per-window percentages: short defaults to 90, long to 98; integers 80..100, short <= long. */
|
|
607
628
|
codexMainAccountHardLockThresholds?: { short?: number; long?: number };
|
|
629
|
+
/**
|
|
630
|
+
* Extended hard-lock settings: while the lock blocks and no Codex account can serve a
|
|
631
|
+
* qualifying request, the request reroutes to `model` (with `reasoningEffort` when set)
|
|
632
|
+
* instead of failing. The lock itself stays on and releases on its own as before; an
|
|
633
|
+
* absent section or model means the refusal behavior is unchanged.
|
|
634
|
+
*/
|
|
635
|
+
codexMainAccountHardLockFallback?: { enabled?: boolean; model?: string; reasoningEffort?: string };
|
|
608
636
|
/**
|
|
609
637
|
* Who refreshes the native main ChatGPT login in CODEX_HOME (#2999). "codex" asks the Codex
|
|
610
638
|
* binary through app-server `getAuthStatus` and never writes auth.json; "opencodex" keeps the
|
|
@@ -612,6 +640,8 @@ export interface OcxConfig {
|
|
|
612
640
|
* is found, otherwise "opencodex". "codex" also falls back when no such binary exists.
|
|
613
641
|
*/
|
|
614
642
|
nativeMainRefresh?: "codex" | "opencodex";
|
|
643
|
+
/** Use included quota to 100% for these accounts; preserves thresholds and credit-spending policy. */
|
|
644
|
+
codexUseRemainingQuotaAccountIds?: string[];
|
|
615
645
|
/** Explicit top-level deletion intent used by stale whole-config rebases. */
|
|
616
646
|
configRebaseProvenance?: OcxConfigRebaseProvenance | Record<string, unknown>;
|
|
617
647
|
/** OpenAI provider-contract migration marker (v2 = single `openai` provider with account mode). */
|
|
@@ -783,6 +813,13 @@ export interface OcxConfig {
|
|
|
783
813
|
};
|
|
784
814
|
/** Opt-in failure-only recovery; never replaces the initial compaction model. */
|
|
785
815
|
compactionRecovery?: { enabled: boolean; model: string; allowDevinInvalidArgument?: boolean };
|
|
816
|
+
/** Explicit opt-in (enabled === true), default off, for routed v1/v2 readable reasoning retention.
|
|
817
|
+
* Defaults: 20% of the compacting model's context window and 100,000 tokens (the smaller limit).
|
|
818
|
+
* Unknown windows use maxTokens alone; overflow is archived locally with a path notice.
|
|
819
|
+
* Native and opaque compaction output keep their existing protocols. */
|
|
820
|
+
reasoningRetention?: { enabled?: boolean; maxContextPercent?: number; maxTokens?: number };
|
|
821
|
+
/** Canonical ChatGPT reasoning replay allowances; both default off. */
|
|
822
|
+
nativeReasoningRetention?: { modelSwitch?: boolean; accountSwitch?: boolean };
|
|
786
823
|
/** Explicit opt-in; resumes Observe only using an existing trusted certificate and endpoints. */
|
|
787
824
|
desktopCompatibility?: { startOnProxyStart: boolean };
|
|
788
825
|
/**
|
|
@@ -842,6 +879,19 @@ export interface OcxConfig {
|
|
|
842
879
|
/** Optional override of intercepted source-model prefixes (default: gpt-6-luna, gpt-5.6-luna). */
|
|
843
880
|
sourceModels?: string[];
|
|
844
881
|
};
|
|
882
|
+
/**
|
|
883
|
+
* Global auto-review (approval reviewer) selector for every catalog row, written by the
|
|
884
|
+
* Models settings panel. During catalog sync OpenCodex stamps it as
|
|
885
|
+
* `auto_review_model_override` on rows that carry no provider-scoped stamp, so an enabled block
|
|
886
|
+
* wins over Codex's own root `auto_review_model`; disabling it restores that fallback.
|
|
887
|
+
* `autoReviewModel` keeps its precedence on its own provider's rows.
|
|
888
|
+
*/
|
|
889
|
+
autoReviewOverride?: {
|
|
890
|
+
/** When true, the configured model replaces the reviewer on every unowned row. */
|
|
891
|
+
enabled?: boolean;
|
|
892
|
+
/** Public catalog selector in `provider/model` form; blank or absent clears the override. */
|
|
893
|
+
model?: string;
|
|
894
|
+
};
|
|
845
895
|
/**
|
|
846
896
|
* Optional map of blocked model IDs to their replacement model IDs.
|
|
847
897
|
* When configured, incoming requests targeting a blocked model (including
|
|
@@ -1207,6 +1257,8 @@ export interface OcxConfig {
|
|
|
1207
1257
|
autoSwitchThreshold?: number;
|
|
1208
1258
|
/** Opt-in: return bound quota-strategy tasks to recovered higher-priority accounts. */
|
|
1209
1259
|
codexAccountPriorityFailback?: boolean;
|
|
1260
|
+
/** Prefer other identities, reserving the current native sign-in as pool fallback. */
|
|
1261
|
+
codexPreserveSignedInAccount?: boolean;
|
|
1210
1262
|
/** New-session account rotation strategy for the Codex pool. Default quota (today's behaviour). */
|
|
1211
1263
|
accountPoolStrategy?: OcxAccountPoolRotationStrategy | "reset-first";
|
|
1212
1264
|
/** Successful new-session binds retained on one round-robin selection. Default 1; range 1..100. */
|
|
@@ -1338,10 +1390,18 @@ export interface OcxComboTarget {
|
|
|
1338
1390
|
* see `OcxComboConfig.cooldownWaitPolicy` (#5691).
|
|
1339
1391
|
*/
|
|
1340
1392
|
lastResort?: boolean;
|
|
1393
|
+
/**
|
|
1394
|
+
* Pay-per-use target (for example an API-key row behind subscription targets). Only the
|
|
1395
|
+
* configured keys with `allowMeteredComboTargets: true`, or a loopback-only combo
|
|
1396
|
+
* with `allowMeteredTargets: true`, may reach it.
|
|
1397
|
+
*/
|
|
1398
|
+
metered?: boolean;
|
|
1341
1399
|
}
|
|
1342
1400
|
|
|
1343
1401
|
export interface OcxComboConfig {
|
|
1344
1402
|
targets: OcxComboTarget[];
|
|
1403
|
+
/** Operator consent for metered targets on a loopback-only proxy. Default false; public listeners still require key consent. */
|
|
1404
|
+
allowMeteredTargets?: boolean;
|
|
1345
1405
|
/** Ordered failover (default), round-robin, weighted random, least-used, or quota reset-window selection. */
|
|
1346
1406
|
strategy?: OcxComboStrategy;
|
|
1347
1407
|
/** Successful requests retained on one RR selection batch. Default 1; range 1..100. */
|
|
@@ -1578,8 +1638,7 @@ export interface OcxVisionSidecarConfig {
|
|
|
1578
1638
|
}
|
|
1579
1639
|
|
|
1580
1640
|
/**
|
|
1581
|
-
* Advisor sidecar configuration.
|
|
1582
|
-
* (failedValidations / noProgressCycles / semanticClassifier) — those belong to a later PR.
|
|
1641
|
+
* Advisor sidecar configuration. Automatic triggers require deterministic task evidence.
|
|
1583
1642
|
*/
|
|
1584
1643
|
export interface OcxAdvisorConfig {
|
|
1585
1644
|
/** Master switch. Default: false — the advisor stays off the request path entirely. */
|
|
@@ -1598,7 +1657,9 @@ export interface OcxAdvisorConfig {
|
|
|
1598
1657
|
* consultation per task once the task has produced orientation evidence (an assistant tool call
|
|
1599
1658
|
* or a tool result after the latest user message).
|
|
1600
1659
|
*/
|
|
1601
|
-
policy?: "manual" | "preflight";
|
|
1660
|
+
policy?: "manual" | "preflight" | "adaptive";
|
|
1661
|
+
/** Adaptive task consultation attempts (manual + automatic). Integer 1–3, default 3. */
|
|
1662
|
+
maxConsultationsPerTask?: number;
|
|
1602
1663
|
/** Advisor fetch timeout (ms). Default 120000. */
|
|
1603
1664
|
timeoutMs?: number;
|
|
1604
1665
|
/**
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** Exact-model protocols from https://opencode.ai/v2/docs/console/go/#endpoints (2026-10-15).
|
|
2
|
+
* Unknown models retain Chat. Responses entries are overridable registry defaults;
|
|
3
|
+
* the native wires are hard pins because modelAdapters cannot select those adapters.
|
|
4
|
+
*/
|
|
5
|
+
export const OPENCODE_GO_BASE_URL = "https://opencode.ai/zen/go/v1";
|
|
6
|
+
|
|
7
|
+
export const OPENCODE_GO_MODEL_WIRES: Readonly<Record<string, "google" | "anthropic" | "openai-responses">> = Object.freeze({
|
|
8
|
+
"gemini-4-argon-contributor": "google",
|
|
9
|
+
"claude-haiku-5-5": "anthropic",
|
|
10
|
+
"minimax-m2.7": "anthropic",
|
|
11
|
+
"minimax-m3": "anthropic",
|
|
12
|
+
"qwen3.7-plus": "anthropic",
|
|
13
|
+
"qwen3.8-max": "anthropic",
|
|
14
|
+
"qwen3.8-flash": "anthropic",
|
|
15
|
+
"gpt-6-luna": "openai-responses",
|
|
16
|
+
"gpt-5.6-luna": "openai-responses",
|
|
17
|
+
"grok-4.6": "openai-responses",
|
|
18
|
+
"grok-4.7": "openai-responses",
|
|
19
|
+
"muse-spark-1.2-contributor": "openai-responses",
|
|
20
|
+
"muse-spark-1.3-contributor": "openai-responses",
|
|
21
|
+
// Retain previously supported Go models absent from the current endpoint table (#4847).
|
|
22
|
+
"minimax-m2.5": "anthropic",
|
|
23
|
+
"union-alpha": "anthropic",
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
/** Compatibility: these five pins predate destination scoping. New native pins require Go. */
|
|
27
|
+
export const OPENCODE_GO_LEGACY_NAME_PINS: ReadonlySet<string> = new Set([
|
|
28
|
+
"claude-haiku-5-5", "minimax-m2.5", "minimax-m2.7", "minimax-m3", "union-alpha",
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
export const OPENCODE_GO_RESPONSES_DEFAULTS = Object.freeze(Object.fromEntries(
|
|
32
|
+
Object.entries(OPENCODE_GO_MODEL_WIRES).filter(([, wire]) => wire === "openai-responses"),
|
|
33
|
+
));
|
|
34
|
+
|
|
35
|
+
export interface OpenCodeGoDestination {
|
|
36
|
+
readonly baseUrl?: unknown;
|
|
37
|
+
readonly authMode?: unknown;
|
|
38
|
+
readonly googleMode?: unknown;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A provider name alone must never authorize redirecting a credential to OpenCode. */
|
|
42
|
+
export function isOpenCodeGoGeminiDestination(provider: OpenCodeGoDestination | undefined): boolean {
|
|
43
|
+
if (!provider || typeof provider.baseUrl !== "string") return false;
|
|
44
|
+
if (provider.authMode !== undefined && provider.authMode !== "key") return false;
|
|
45
|
+
if (provider.googleMode !== undefined && provider.googleMode !== "ai-studio") return false;
|
|
46
|
+
try {
|
|
47
|
+
const url = new URL(provider.baseUrl.trim());
|
|
48
|
+
return url.origin === "https://opencode.ai"
|
|
49
|
+
&& !url.username && !url.password && !url.search && !url.hash
|
|
50
|
+
&& ["/zen/go/v1", "/inference/go/v1"].includes(url.pathname.replace(/\/+$/, ""));
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Return the fixed Gemini endpoint only for the exact model and an eligible Go destination. */
|
|
57
|
+
export function openCodeGoGeminiEndpoint(provider: OpenCodeGoDestination, modelId: string): string | undefined {
|
|
58
|
+
return Object.hasOwn(OPENCODE_GO_MODEL_WIRES, modelId) && OPENCODE_GO_MODEL_WIRES[modelId] === "google" && isOpenCodeGoGeminiDestination(provider)
|
|
59
|
+
? `https://opencode.ai/inference/go/v1/models/${encodeURIComponent(modelId)}`
|
|
60
|
+
: undefined;
|
|
61
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Fixed-key OpenCode Zen destinations supporting native model protocols. */
|
|
2
|
+
export interface OpenCodeZenDestination {
|
|
3
|
+
readonly baseUrl?: unknown;
|
|
4
|
+
readonly authMode?: unknown;
|
|
5
|
+
readonly googleMode?: unknown;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Match the official Zen destination and API-key authentication. */
|
|
9
|
+
export function openCodeZenKeyBaseUrl(provider: OpenCodeZenDestination | undefined): string | undefined {
|
|
10
|
+
if (!provider || typeof provider.baseUrl !== "string") return undefined;
|
|
11
|
+
if (provider.authMode !== undefined && provider.authMode !== "key") return undefined;
|
|
12
|
+
try {
|
|
13
|
+
const url = new URL(provider.baseUrl.trim());
|
|
14
|
+
const pathname = url.pathname.replace(/\/+$/, "");
|
|
15
|
+
if (url.origin !== "https://opencode.ai" || url.username || url.password || url.search || url.hash) return undefined;
|
|
16
|
+
if (pathname !== "/zen/v1" && pathname !== "/inference/v1") return undefined;
|
|
17
|
+
return url.origin + pathname;
|
|
18
|
+
} catch {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Gemini's native endpoint applies only in AI Studio mode. */
|
|
24
|
+
export function openCodeZenBaseUrl(provider: OpenCodeZenDestination | undefined): string | undefined {
|
|
25
|
+
if (provider?.googleMode !== undefined && provider.googleMode !== "ai-studio") return undefined;
|
|
26
|
+
return openCodeZenKeyBaseUrl(provider);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Keep the selected destination; Zen omits AI Studio's /v1beta segment. */
|
|
30
|
+
export function openCodeZenGeminiEndpoint(provider: OpenCodeZenDestination, modelId: string): string | undefined {
|
|
31
|
+
const baseUrl = openCodeZenBaseUrl(provider);
|
|
32
|
+
return baseUrl && modelId.toLowerCase().startsWith("gemini-")
|
|
33
|
+
? baseUrl + "/models/" + encodeURIComponent(modelId)
|
|
34
|
+
: undefined;
|
|
35
|
+
}
|