@bitkyc08/opencodex 2.84.0 → 2.87.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/README.md +0 -4
- package/bin/ocx.mjs +61 -3
- package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-C9TGtwad.js → Tray-DCd-A1az.js} +1 -1
- package/gui/dist/assets/index-CfYDMj34.js +94 -0
- package/gui/dist/assets/index-DPeB9HpV.css +1 -0
- package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -2
- package/src/adapters/anthropic/beta-allowlist.ts +91 -11
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/client-identity.ts +1 -1
- package/src/adapters/anthropic/passthrough.ts +16 -3
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic.ts +108 -21
- package/src/adapters/azure.ts +19 -1
- 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/client-fingerprint.ts +24 -0
- package/src/adapters/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +40 -11
- package/src/adapters/cursor/arg-codec.ts +17 -5
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/cursor/freeform-args.ts +75 -0
- package/src/adapters/cursor/protobuf-events.ts +36 -23
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google-antigravity-wire.ts +22 -1
- package/src/adapters/google.ts +37 -10
- 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-chat/response-events.ts +59 -1
- package/src/adapters/openai-chat.ts +8 -5
- 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 +44 -3
- 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 +223 -0
- package/src/advisor/context.ts +238 -0
- package/src/advisor/disclosure.ts +20 -0
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +353 -0
- package/src/advisor/settings.ts +144 -0
- package/src/advisor/state.ts +369 -0
- package/src/advisor/synthetic-tool.ts +33 -0
- package/src/bridge/errors.ts +24 -1
- package/src/bridge/internal.ts +3 -1
- package/src/bridge/response-json.ts +18 -3
- package/src/bridge/sse.ts +58 -8
- package/src/bridge/terminal-refusal.ts +59 -0
- package/src/chat/inbound.ts +30 -6
- package/src/chat/outbound.ts +5 -0
- package/src/claude/desktop-3p-library.ts +72 -7
- package/src/claude/desktop-3p.ts +75 -19
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker-profile.ts +11 -4
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-profile.ts +10 -1
- package/src/claude/desktop-remote-store-io.ts +5 -3
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +11 -0
- package/src/claude/inbound.ts +154 -50
- package/src/claude/intercept/local-ca.ts +8 -1
- 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/model-default-settings.ts +154 -0
- package/src/claude/model-info.ts +7 -7
- package/src/claude/outbound.ts +51 -19
- 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 +165 -0
- package/src/cli/auxiliary-listener-preflight.ts +37 -0
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +23 -2
- package/src/cli/capabilities-base.ts +39 -11
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- package/src/cli/chatgpt-command.ts +35 -14
- package/src/cli/claude.ts +46 -6
- 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 +17 -14
- package/src/cli/dispatch.ts +30 -5
- package/src/cli/doctor.ts +15 -1
- package/src/cli/ensure-desktop-cold-start.ts +382 -0
- package/src/cli/export-command.ts +1 -1
- package/src/cli/help.ts +4 -2
- package/src/cli/index.ts +14 -5
- package/src/cli/integration-plan-dto.ts +3 -0
- package/src/cli/log-history.ts +172 -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 +41 -9
- package/src/cli/restart-scope.ts +3 -1
- 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 +3 -1
- package/src/clients/config-export/model-metadata.ts +3 -2
- package/src/clients/config-export/qoder.ts +93 -0
- package/src/clients/config-export.ts +35 -48
- 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/pool-mode-gate.ts +5 -2
- 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 +81 -7
- package/src/codex/autostart-health.ts +4 -0
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +19 -2
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +50 -0
- package/src/codex/catalog/effort.ts +4 -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 +42 -19
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/native-ultrafast.ts +106 -0
- package/src/codex/catalog/parsing.ts +35 -86
- package/src/codex/catalog/provider-models.ts +40 -34
- package/src/codex/catalog/retained-sync.ts +2 -0
- package/src/codex/catalog/routed-gather.ts +33 -16
- package/src/codex/catalog/vendor-context.ts +82 -0
- 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 +63 -4
- package/src/codex/convergence.ts +2 -0
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app/types.ts +2 -0
- package/src/codex/desktop-app/windows.ts +29 -16
- package/src/codex/desktop-app-restart.ts +25 -2
- package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
- package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
- package/src/codex/desktop-compatibility/connection-store.ts +87 -0
- package/src/codex/desktop-compatibility/installed-build.ts +73 -0
- package/src/codex/desktop-compatibility/json-body.ts +29 -0
- package/src/codex/desktop-compatibility/native-identity.ts +62 -0
- package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
- package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
- package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
- package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
- package/src/codex/desktop-compatibility/runtime.ts +182 -0
- package/src/codex/desktop-compatibility/service.ts +30 -0
- package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
- package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
- package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
- package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
- package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
- package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
- package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
- package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
- package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
- package/src/codex/desktop-compatibility/windows-key-protection.ts +165 -0
- package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
- package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
- package/src/codex/features.ts +85 -45
- package/src/codex/history-provider.ts +1 -1
- package/src/codex/inject/config-write-section.ts +4 -1
- package/src/codex/inject/history-failure.ts +27 -0
- package/src/codex/inject/remove.ts +18 -6
- 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/journal.ts +16 -1
- package/src/codex/main-account-hard-lock.ts +30 -1
- package/src/codex/main-account.ts +99 -15
- package/src/codex/native-main-refresh-mode.ts +217 -0
- 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 +2 -2
- package/src/codex/routing/pool-recovery.ts +79 -0
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- package/src/codex/shim-diagnostics.ts +6 -0
- package/src/codex/shim-overlay.ts +7 -2
- 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/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 +35 -7
- package/src/config/live-reconcile.ts +51 -2
- package/src/config/load-degrade.ts +23 -1
- package/src/config/opencode-wire-upgrade.ts +54 -0
- package/src/config/opencode-zen-upgrade.ts +52 -0
- package/src/config/owner-registry.ts +22 -17
- package/src/config/paths.ts +10 -3
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +39 -0
- package/src/config/schema/desktop-compatibility.ts +9 -0
- package/src/config/schema/leaf-validators.ts +39 -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 +962 -350
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/sync.ts +1 -1
- package/src/images/loop.ts +46 -8
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/config-io.ts +20 -2
- package/src/integrations/journal.ts +15 -1
- package/src/integrations/mutation-plan.ts +2 -0
- package/src/integrations/registry.ts +15 -0
- package/src/integrations/store.ts +3 -0
- package/src/integrations/writer.ts +10 -1
- package/src/lib/advisor-activation.ts +16 -0
- package/src/lib/background-work.ts +65 -0
- package/src/lib/bounded-file-read.ts +58 -0
- package/src/lib/bun-path-runtime.d.mts +10 -1
- package/src/lib/bun-path-runtime.mjs +62 -3
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-proxy-route.ts +30 -0
- package/src/lib/desktop-upstream-tunnel.ts +72 -0
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +26 -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/local-internal-call-capability.ts +55 -0
- package/src/lib/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/package-tree-integrity.ts +2 -2
- package/src/lib/redact.ts +2 -2
- package/src/lib/socks5-fetch.ts +48 -71
- package/src/lib/socks5-handshake.ts +112 -0
- package/src/lib/translator-budget.ts +7 -1
- 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 +62 -12
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +188 -111
- package/src/oauth/kiro-device-login.ts +2 -1
- package/src/oauth/kiro.ts +7 -1
- 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/codecs/chat-ir.ts +467 -0
- package/src/protocols/codecs/chat.ts +10 -1
- package/src/protocols/codecs/messages-ir.ts +500 -0
- package/src/protocols/encoders/adapter-events.ts +5 -4
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/features.ts +10 -2
- package/src/protocols/path.ts +11 -1
- package/src/protocols/plan-snapshot.ts +39 -16
- package/src/protocols/plan.ts +9 -1
- package/src/protocols/provider-summary.ts +2 -1
- package/src/protocols/settings.ts +8 -0
- package/src/providers/antigravity-account-roster.ts +102 -0
- package/src/providers/antigravity-models.ts +85 -7
- 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 +5 -0
- package/src/providers/model-rename-migration.ts +7 -5
- 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/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 +44 -14
- package/src/providers/registry/entries-extended.ts +19 -0
- 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 +78 -22
- package/src/responses/reasoning-envelope.ts +4 -0
- package/src/responses/reasoning-replay-cache.ts +24 -2
- 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/responses/state.ts +32 -29
- package/src/router.ts +8 -321
- package/src/routing/health.ts +37 -22
- package/src/routing/history/build-file.ts +92 -0
- package/src/routing/history/indexer.ts +254 -221
- package/src/routing/history/ingest.ts +242 -0
- package/src/routing/history/rebuild-job.ts +111 -0
- package/src/routing/history/rebuild-protocol.ts +15 -0
- package/src/routing/history/rebuild-worker.ts +34 -0
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +21 -1
- package/src/server/antigravity-direct-eligibility.ts +208 -0
- package/src/server/auth-cors.ts +15 -0
- package/src/server/chat-completions.ts +82 -10
- 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 +164 -433
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/desktop-compatibility-startup.ts +56 -0
- 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 +11 -3
- package/src/server/index/startup-warnings.ts +11 -0
- package/src/server/index/websocket-handler.ts +9 -0
- package/src/server/index.ts +14 -11
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/inference/client-encoder-delivery.ts +6 -5
- package/src/server/login-shell-path.ts +146 -0
- package/src/server/management/advisor-routes.ts +202 -0
- package/src/server/management/agent-settings-routes.ts +22 -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 +4 -0
- package/src/server/management/config-routes.ts +77 -6
- package/src/server/management/context.ts +4 -0
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/desktop-compatibility-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
- package/src/server/management/model-routes.ts +13 -8
- 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/protocol-settings-patch.ts +1 -0
- 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 +21 -1
- package/src/server/management/sibling-guard.ts +1 -0
- package/src/server/management-api.ts +9 -1
- package/src/server/messages-antigravity-direct.ts +195 -0
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +106 -22
- 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 +13 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +48 -19
- package/src/server/responses/adapter-delivery.ts +43 -8
- package/src/server/responses/adapter-dispatch.ts +48 -4
- package/src/server/responses/advisor-plan-slot.ts +27 -0
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +368 -0
- package/src/server/responses/code-mode-collab-surface.ts +116 -8
- package/src/server/responses/codex-ws-wire.ts +15 -8
- package/src/server/responses/compact.ts +303 -125
- package/src/server/responses/compaction-recovery.ts +18 -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 +12 -1
- package/src/server/responses/core-combo.ts +141 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +7 -2
- package/src/server/responses/core-normalize.ts +11 -0
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +51 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +25 -6
- package/src/server/responses/core.ts +3 -116
- 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 +19 -11
- package/src/server/responses/passthrough-dispatch.ts +29 -16
- package/src/server/responses/request-prepare.ts +144 -17
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +120 -27
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +166 -38
- package/src/server/responses/sidecar-execution.ts +98 -5
- package/src/server/responses/terminal-guard.ts +4 -17
- package/src/server/responses-undeclared-tool-guard.ts +30 -9
- package/src/server/startup-action-control.ts +16 -2
- package/src/server/startup-health-cache.ts +1 -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/desktop-startup.ts +14 -0
- package/src/service/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +99 -61
- package/src/service/health-sentinel-probe.ts +24 -7
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +135 -0
- 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/tray/windows-tray.ps1 +43 -24
- package/src/types/config.ts +124 -6
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +29 -0
- package/src/types/provider.ts +39 -6
- package/src/types/request.ts +38 -2
- package/src/types/tools.ts +2 -0
- package/src/types/wire.ts +29 -37
- package/src/types.ts +1 -0
- package/src/update/async-check.ts +1 -1
- package/src/update/badge.ts +2 -2
- package/src/update/index.ts +88 -15
- package/src/update/job-supervised-restart.ts +107 -0
- package/src/update/job.ts +32 -32
- package/src/update/notify.ts +1 -1
- package/src/update/npm-invocation.mjs +12 -2
- package/src/update/refresh-scheduler.ts +2 -2
- package/src/update/supervised-recovery.d.mts +27 -0
- package/src/update/supervised-recovery.mjs +157 -0
- package/src/usage/cache-diagnostic.ts +2 -0
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +15 -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 +22 -9
- 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/gemini-executor.ts +2 -2
- package/src/web-search/loop.ts +55 -5
- package/src/web-search/progress-stream.ts +4 -1
- package/src/web-search/run-turn-loop.ts +9 -1
- package/assets/sponsors/packycode.png +0 -0
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/App-CZtqiGcw.js +0 -56
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
- package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
|
@@ -7,6 +7,9 @@ import type { ResponsesEffects } from "./response-effects";
|
|
|
7
7
|
import type { ResponsesSendBudget } from "./request-send-budget";
|
|
8
8
|
import { formatErrorResponse } from "../../bridge";
|
|
9
9
|
import { planWebSearch, buildWebSearchTool, runWithWebSearch } from "../../web-search";
|
|
10
|
+
import { createRegisteredAdvisorPlan } from "./advisor-plan-slot";
|
|
11
|
+
import { ADVISOR_TOOL_NAME } from "./advisor-slot";
|
|
12
|
+
import { buildToolBridgeMaps } from "./collaboration";
|
|
10
13
|
import {
|
|
11
14
|
planImageBridge,
|
|
12
15
|
planVideoBridge,
|
|
@@ -18,7 +21,7 @@ import {
|
|
|
18
21
|
clampImageMaxRounds,
|
|
19
22
|
} from "../../images";
|
|
20
23
|
import type { ProviderAdapter } from "../../adapters/base";
|
|
21
|
-
import type { OcxParsedRequest } from "../../types";
|
|
24
|
+
import type { OcxParsedRequest, OcxUsage } from "../../types";
|
|
22
25
|
import { rotateProviderTransportOn429, rateLimitRetryPolicyFor } from "../../providers/key-failover";
|
|
23
26
|
import {
|
|
24
27
|
isGenericOAuthFailoverEnabled,
|
|
@@ -30,6 +33,8 @@ import {
|
|
|
30
33
|
} from "../../oauth/generic-account-failover";
|
|
31
34
|
import { classifyKiroRefusal } from "../../adapters/kiro-refusal";
|
|
32
35
|
import { isNonReplayableResponse } from "../../lib/upstream-retry";
|
|
36
|
+
import { unwrapUpstreamRetryEvidenceError } from "../../codex/auth-context";
|
|
37
|
+
import { AdmissionModelDeniedError, admissionModelDeniedResponse } from "../admission-model-scope";
|
|
33
38
|
import { noteKiroMonthlyRefusal, noteKiroServedSuccess } from "../../providers/kiro-usage";
|
|
34
39
|
import { persistKiroAccountState } from "../../providers/kiro-account-state-disk";
|
|
35
40
|
import { anthropicPoolCooldownResponse, readDisplaySafeErrorText } from "./core-errors";
|
|
@@ -45,6 +50,7 @@ import { providerFetch } from "./fetch-helpers";
|
|
|
45
50
|
import type { AttemptRecoveryKind } from "../../usage/log";
|
|
46
51
|
import { recordAdapterReasoning, recordAdapterTier } from "../request-log";
|
|
47
52
|
import { normalizeLogConversationId } from "../request-log-conversation";
|
|
53
|
+
import { attemptAccountChanged } from "../request-log-account-rotation";
|
|
48
54
|
import { rememberResponseState } from "../../responses/state";
|
|
49
55
|
import { trackStreamLifetime } from "../lifecycle";
|
|
50
56
|
import { isAntigravityRawValidationRefusal } from "./antigravity-validation-refusal";
|
|
@@ -60,6 +66,8 @@ export async function executeResponsesSidecars(
|
|
|
60
66
|
| "inboundWire"
|
|
61
67
|
| "selectedForwardHeaders"
|
|
62
68
|
| "translatorBudget"
|
|
69
|
+
| "clientToolCatalogRestriction"
|
|
70
|
+
| "toolBridgeMaps"
|
|
63
71
|
| "rememberKiroDeliveredFinalAnswer"
|
|
64
72
|
| "responseStateOptions"
|
|
65
73
|
>,
|
|
@@ -169,6 +177,25 @@ export async function executeResponsesSidecars(
|
|
|
169
177
|
}
|
|
170
178
|
}
|
|
171
179
|
|
|
180
|
+
// Advisor sidecar plan (optional subsystem; null when disabled, unconfigured, or when this
|
|
181
|
+
// request IS an advisor loopback consultation — the recursion fence). The plan carries
|
|
182
|
+
// request-scoped state only; preflight keeps fixed policy separate from user-role advice
|
|
183
|
+
// before the worker is dispatched. Registration seam: the core path sees only
|
|
184
|
+
// `parsed._advisorGuard`; only an activated factory can load the optional implementation.
|
|
185
|
+
const advisorPlan = options.advisorInternal === true
|
|
186
|
+
? null
|
|
187
|
+
: await createRegisteredAdvisorPlan({
|
|
188
|
+
config,
|
|
189
|
+
workerIdentity: `${route.modelId} (provider ${route.providerName})`,
|
|
190
|
+
workerModelId: route.modelId,
|
|
191
|
+
abortSignal: options.abortSignal,
|
|
192
|
+
});
|
|
193
|
+
// Preflight only on real worker turns: a routed-compaction turn is a summarization request,
|
|
194
|
+
// and injecting advice into it would pollute the summary Codex replaces its history with.
|
|
195
|
+
if (advisorPlan && !routedCompaction) {
|
|
196
|
+
await advisorPlan.preflightInject(parsed);
|
|
197
|
+
}
|
|
198
|
+
|
|
172
199
|
// Image / web-search sidecars: plan once, then dispatch with runTurn-aware priority.
|
|
173
200
|
// Routed-compaction turns must NOT hit the image bridge: compaction clears tools/_webSearch but
|
|
174
201
|
// leaves _imageGeneration, so planImageBridge would activate and return a normal Responses
|
|
@@ -198,6 +225,20 @@ export async function executeResponsesSidecars(
|
|
|
198
225
|
// A single request may rotate once for a verification refusal. Keep this separate from the
|
|
199
226
|
// broader OAuth failover count: a sidecar turn can also spend that count on rate-limit recovery.
|
|
200
227
|
let antigravityValidationRotationAttempted = false;
|
|
228
|
+
// A replacement credential whose Copilot Auto negotiation leaves the key's scope ends the turn.
|
|
229
|
+
// The loops only see "no rotation" and fail with the original, replayable 429, so the refusal
|
|
230
|
+
// is held here and answers in place of that failure.
|
|
231
|
+
let sidecarScopeDenial: AdmissionModelDeniedError | undefined;
|
|
232
|
+
const noteSidecarScopeDenial = (error: unknown): void => {
|
|
233
|
+
const refusal = unwrapUpstreamRetryEvidenceError(error);
|
|
234
|
+
if (refusal instanceof AdmissionModelDeniedError) sidecarScopeDenial ??= refusal;
|
|
235
|
+
};
|
|
236
|
+
const sidecarScopeRefusal = (response: Response): Response | undefined => {
|
|
237
|
+
if (!sidecarScopeDenial || response.ok) return undefined;
|
|
238
|
+
void response.body?.cancel().catch(() => {});
|
|
239
|
+
releaseSearchProbeLease();
|
|
240
|
+
return admissionModelDeniedResponse(sidecarScopeDenial);
|
|
241
|
+
};
|
|
201
242
|
/** Admit a replacement credential, or leave the loop's original refusal authoritative. */
|
|
202
243
|
const rotateSidecarProviderOn429 = async (
|
|
203
244
|
retryAfter: string | null,
|
|
@@ -264,7 +305,8 @@ export async function executeResponsesSidecars(
|
|
|
264
305
|
return null;
|
|
265
306
|
}
|
|
266
307
|
transportState.genericFailovers += 1;
|
|
267
|
-
} catch {
|
|
308
|
+
} catch (error) {
|
|
309
|
+
noteSidecarScopeDenial(error);
|
|
268
310
|
hop.permit?.release();
|
|
269
311
|
return null;
|
|
270
312
|
}
|
|
@@ -284,7 +326,10 @@ export async function executeResponsesSidecars(
|
|
|
284
326
|
} else if (rotated) {
|
|
285
327
|
route.provider = rotated;
|
|
286
328
|
try { await resolveCopilotSelection(retryParsed ?? parsed); }
|
|
287
|
-
catch {
|
|
329
|
+
catch (error) { // Keep the sidecar's original refusal when replacement negotiation fails.
|
|
330
|
+
noteSidecarScopeDenial(error);
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
288
333
|
} else if (
|
|
289
334
|
// A POSITIVE gate, not an early return. An early `return null` here made every later arm
|
|
290
335
|
// unreachable: Anthropic never has a genericFailoverAccountId (isGenericFailoverProvider
|
|
@@ -326,7 +371,8 @@ export async function executeResponsesSidecars(
|
|
|
326
371
|
return null;
|
|
327
372
|
}
|
|
328
373
|
if (route.providerName === "kiro") transportState.genericFailovers += 1;
|
|
329
|
-
} catch {
|
|
374
|
+
} catch (error) {
|
|
375
|
+
noteSidecarScopeDenial(error);
|
|
330
376
|
hop.permit?.release();
|
|
331
377
|
return null;
|
|
332
378
|
}
|
|
@@ -373,7 +419,8 @@ export async function executeResponsesSidecars(
|
|
|
373
419
|
hop.permit?.release();
|
|
374
420
|
return null;
|
|
375
421
|
}
|
|
376
|
-
recoveryKind = originalResponse?.status === 401 ? "oauth-401"
|
|
422
|
+
recoveryKind = originalResponse?.status === 401 ? "oauth-401"
|
|
423
|
+
: originalResponse?.status === 403 ? "oauth-account-403" : "anthropic-oauth-429";
|
|
377
424
|
sidecarBudget.ownCredentialHop(hop.permit);
|
|
378
425
|
} else {
|
|
379
426
|
// No key pool, no generic OAuth roster, no Anthropic pool could produce a replacement
|
|
@@ -407,6 +454,15 @@ export async function executeResponsesSidecars(
|
|
|
407
454
|
});
|
|
408
455
|
return { adapter: rotatedAdapter, recoveryKind };
|
|
409
456
|
};
|
|
457
|
+
// A loop's hidden rounds before a rotation were spent by the account it left. Bill them to that
|
|
458
|
+
// account's attempt while it is still open; the next send seals it. Declined when that send
|
|
459
|
+
// keeps the account (no new attempt), so the loop's final total still carries them.
|
|
460
|
+
const billRotatedUsage = (usage: OcxUsage): boolean => {
|
|
461
|
+
const attempt = logCtx.activeAttempt;
|
|
462
|
+
if (!attempt || !attemptAccountChanged(attempt.accountLogLabel, logCtx.accountLogLabel, attempt.provider, logCtx.provider)) return false;
|
|
463
|
+
transportState.bindKeyUsageFromBridge(usage);
|
|
464
|
+
return true;
|
|
465
|
+
};
|
|
410
466
|
if ((imgPlan || vidPlan) && wsPlan) {
|
|
411
467
|
// Web search takes priority when both are active — the media bridge cannot run
|
|
412
468
|
// alongside runWithWebSearch. Surface a runtime signal so the user knows their
|
|
@@ -464,6 +520,7 @@ export async function executeResponsesSidecars(
|
|
|
464
520
|
);
|
|
465
521
|
const imgResponse = await runWithImageBridge({
|
|
466
522
|
parsed, adapter: transportState.adapter,
|
|
523
|
+
clientToolCatalogRestriction: route.provider.authMode === "forward" ? undefined : requestState.clientToolCatalogRestriction,
|
|
467
524
|
incomingMeta: { headers: requestState.selectedForwardHeaders, providerName: route.providerName, abortSignal: options.abortSignal, translatorBudget, ...(options.comboInitialSend ? { providerFetch: imageProviderFetch } : {}), ...sidecarBudget.incomingMeta },
|
|
468
525
|
onProducerStart: sidecarBudget.takeProducerOwnership,
|
|
469
526
|
onProducerEnd: sidecarBudget.release,
|
|
@@ -505,10 +562,12 @@ export async function executeResponsesSidecars(
|
|
|
505
562
|
transportState.bindKeyUsageFromBridge(usage);
|
|
506
563
|
},
|
|
507
564
|
on429: rotateSidecarProviderOn429,
|
|
565
|
+
onRotatedUsage: billRotatedUsage,
|
|
508
566
|
retryOn429Policy: route.providerName === "kiro" && isGenericOAuthFailoverEnabled(config, "kiro")
|
|
509
567
|
? null : rateLimitRetryPolicyFor(route.provider),
|
|
510
568
|
onFirstOutput: noteSidecarOutput,
|
|
511
569
|
...(options.forceEmptyResponseId ? { forceEmptyResponseId: true } : {}),
|
|
570
|
+
terminalRefusalAsFailed: inboundWire !== "chat" && inboundWire !== "anthropic",
|
|
512
571
|
onCompletedResponse: (response, providerState) => {
|
|
513
572
|
const served = transportState.replayOAuthCredentialSnapshot;
|
|
514
573
|
if (route.providerName === "kiro" && response.status === "completed" && served
|
|
@@ -527,6 +586,8 @@ export async function executeResponsesSidecars(
|
|
|
527
586
|
notifyResponseComplete(response);
|
|
528
587
|
},
|
|
529
588
|
});
|
|
589
|
+
const imgScopeRefusal = sidecarScopeRefusal(imgResponse);
|
|
590
|
+
if (imgScopeRefusal) return imgScopeRefusal;
|
|
530
591
|
if (imgResponse.body) {
|
|
531
592
|
const imgTurnAc = new AbortController();
|
|
532
593
|
imgTurnAc.signal.addEventListener("abort", cancelResponseCompletion, { once: true });
|
|
@@ -557,6 +618,7 @@ export async function executeResponsesSidecars(
|
|
|
557
618
|
})(input, init)) as typeof globalThis.fetch;
|
|
558
619
|
const loopResponse = await runWithWebSearch({
|
|
559
620
|
parsed, adapter: transportState.adapter,
|
|
621
|
+
clientToolCatalogRestriction: route.provider.authMode === "forward" ? undefined : requestState.clientToolCatalogRestriction,
|
|
560
622
|
onIterationEnd: sidecarBudget.releaseUnsentHop,
|
|
561
623
|
fetchForRequest: (request, iterParsed, adapter) => providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
562
624
|
dispatchOverride: oauthDispatch(request, iterParsed),
|
|
@@ -587,6 +649,7 @@ export async function executeResponsesSidecars(
|
|
|
587
649
|
settings: wsPlan.settings,
|
|
588
650
|
maxSearches: wsPlan.maxSearches,
|
|
589
651
|
forceEmptyResponseId: true,
|
|
652
|
+
terminalRefusalAsFailed: inboundWire !== "chat" && inboundWire !== "anthropic",
|
|
590
653
|
abortSignal: options.abortSignal,
|
|
591
654
|
onFirstOutput: noteSidecarOutput,
|
|
592
655
|
onRequestBuilt: request => {
|
|
@@ -605,6 +668,7 @@ export async function executeResponsesSidecars(
|
|
|
605
668
|
stallTimeoutSec: wsPlan.stallTimeoutSec,
|
|
606
669
|
streamRoutedModelOutput: wsPlan.streamRoutedModelOutput,
|
|
607
670
|
on429: rotateSidecarProviderOn429,
|
|
671
|
+
onRotatedUsage: billRotatedUsage,
|
|
608
672
|
...(!options.comboInitialSend && route.providerName === "google-antigravity" ? { onIterationEnd: () => {
|
|
609
673
|
// The next Antigravity adapter dispatch claims this permit. If request construction,
|
|
610
674
|
// admission or cancellation stopped it first, return the unused send allowance.
|
|
@@ -622,6 +686,8 @@ export async function executeResponsesSidecars(
|
|
|
622
686
|
notifyResponseComplete(response);
|
|
623
687
|
},
|
|
624
688
|
});
|
|
689
|
+
const wsScopeRefusal = sidecarScopeRefusal(loopResponse);
|
|
690
|
+
if (wsScopeRefusal) return wsScopeRefusal;
|
|
625
691
|
// A routed Anthropic model whose last pool account refused on spent quota fails the loop's
|
|
626
692
|
// first iteration with a generic 429 before any search runs. Answer the usage limit instead,
|
|
627
693
|
// as the main path does (#6991), so Codex stops rather than retrying the whole turn.
|
|
@@ -645,5 +711,32 @@ export async function executeResponsesSidecars(
|
|
|
645
711
|
return wsResponse;
|
|
646
712
|
}
|
|
647
713
|
|
|
714
|
+
// Advisor synthetic tool + stream guard: attach ONLY when this function is about to hand the
|
|
715
|
+
// request back to the normal translated exchange (no sidecar loop claimed the turn, not a
|
|
716
|
+
// run-turn adapter, not compaction). The web-search/image loops own their own event handling;
|
|
717
|
+
// run-turn adapters execute their own loop; both would leak the synthetic tool call they
|
|
718
|
+
// cannot intercept. Preflight (above) still applies to those paths — only the tool does not.
|
|
719
|
+
if (advisorPlan && !routedCompaction && !transportState.adapter.runTurn && !wsPlan && !imgPlan && !vidPlan) {
|
|
720
|
+
// A client-declared tool named "advisor" would collide with the synthetic injection (one
|
|
721
|
+
// wire name, two schemas); the synthetic runtime owns the name for this turn.
|
|
722
|
+
parsed.context.tools = [
|
|
723
|
+
...(parsed.context.tools ?? []).filter(t => !t.advisor && t.name !== ADVISOR_TOOL_NAME),
|
|
724
|
+
advisorPlan.tool,
|
|
725
|
+
];
|
|
726
|
+
// The advisor tool joined AFTER prepare computed the bridge maps; recompute so the tool is
|
|
727
|
+
// declared (undeclared-tool guard, tool_choice mapping, schema repair) on this turn.
|
|
728
|
+
//
|
|
729
|
+
// Rebuild WITHOUT the budget: prepare already charged every tool in the catalog, and
|
|
730
|
+
// buildToolBridgeMaps charges each name it registers, so re-passing the budget would bill the
|
|
731
|
+
// whole catalog a second time and can trip `translation_buffer_limit` for a large MCP catalog.
|
|
732
|
+
// Charge only the one entry that is genuinely new — the synthetic advisor tool is a bare
|
|
733
|
+
// function tool, so it retains its wire name and its bare name (collaboration.ts:187, :233).
|
|
734
|
+
requestState.toolBridgeMaps = buildToolBridgeMaps(parsed);
|
|
735
|
+
translatorBudget.chargeRetained(new TextEncoder().encode(ADVISOR_TOOL_NAME).byteLength * 2, {
|
|
736
|
+
kind: "retained_collectors",
|
|
737
|
+
});
|
|
738
|
+
advisorPlan.attachGuard(parsed);
|
|
739
|
+
}
|
|
740
|
+
|
|
648
741
|
return undefined;
|
|
649
742
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { assistantReplayContent } from "../../responses/assistant-replay";
|
|
1
2
|
import type {
|
|
2
3
|
AdapterEvent,
|
|
3
|
-
OcxAssistantContentPart,
|
|
4
4
|
OcxAssistantMessage,
|
|
5
5
|
OcxParsedRequest,
|
|
6
6
|
OcxUsage,
|
|
@@ -121,21 +121,7 @@ export function analyzeTerminalTurn(parsed: OcxParsedRequest, events: readonly A
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
function assistantMessageFromEvents(events: readonly AdapterEvent[], timestamp: number): OcxAssistantMessage | undefined {
|
|
124
|
-
|
|
125
|
-
let thinking = "";
|
|
126
|
-
let signature: string | undefined;
|
|
127
|
-
const redacted: string[] = [];
|
|
128
|
-
for (const event of events) {
|
|
129
|
-
if (event.type === "text_delta") text += event.text;
|
|
130
|
-
else if (event.type === "thinking_delta") thinking += event.thinking;
|
|
131
|
-
else if (event.type === "thinking_signature") signature = event.signature;
|
|
132
|
-
else if (event.type === "redacted_thinking") redacted.push(event.data);
|
|
133
|
-
}
|
|
134
|
-
const content: OcxAssistantContentPart[] = [];
|
|
135
|
-
if (thinking || signature || redacted.length > 0) {
|
|
136
|
-
content.push({ type: "thinking", thinking, ...(signature ? { signature } : {}), ...(redacted.length > 0 ? { redacted } : {}) });
|
|
137
|
-
}
|
|
138
|
-
if (text) content.push({ type: "text", text });
|
|
124
|
+
const content = assistantReplayContent(events);
|
|
139
125
|
if (content.length === 0) return undefined;
|
|
140
126
|
return { role: "assistant", content, timestamp };
|
|
141
127
|
}
|
|
@@ -170,7 +156,8 @@ export function isTerminalGuardPassthroughOnly(event: AdapterEvent): boolean {
|
|
|
170
156
|
return event.type === "heartbeat" || event.type === "tool_call_delta";
|
|
171
157
|
}
|
|
172
158
|
|
|
173
|
-
|
|
159
|
+
/** Merge two reported usages (canonical Responses convention). Shared with the advisor guard. */
|
|
160
|
+
export function mergeUsage(first: OcxUsage | undefined, second: OcxUsage | undefined): OcxUsage | undefined {
|
|
174
161
|
if (!first) return second;
|
|
175
162
|
if (!second) return first;
|
|
176
163
|
const sumOptional = (key: keyof OcxUsage): number | undefined => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { UNDECLARED_TOOL_CALL_ERROR_CODE } from "../lib/errors";
|
|
1
2
|
import {
|
|
2
3
|
collectAmbiguousDottedAliases,
|
|
3
4
|
dottedAliasIsUnambiguous,
|
|
@@ -78,7 +79,7 @@ const NAMELESS_TOOL_SPEC_TYPES = new Set([
|
|
|
78
79
|
/** An upstream-supplied name reaches the error message; keep it bounded. */
|
|
79
80
|
const MAX_REPORTED_NAME_CHARS = 100;
|
|
80
81
|
|
|
81
|
-
export
|
|
82
|
+
export { UNDECLARED_TOOL_CALL_ERROR_CODE };
|
|
82
83
|
|
|
83
84
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
84
85
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
@@ -370,6 +371,24 @@ export function hasExplicitWireToolCatalog(body: unknown): boolean {
|
|
|
370
371
|
);
|
|
371
372
|
}
|
|
372
373
|
|
|
374
|
+
/**
|
|
375
|
+
* Whether the caller declared an explicitly EMPTY catalog — `tools: []` or an
|
|
376
|
+
* `additional_tools` item with `tools: []`. Unlike an absent or partial catalog this is an
|
|
377
|
+
* unambiguous deny-all: no deferred registry can exist under it, so even deferred wires
|
|
378
|
+
* enforce it. A hosted-only catalog is NOT empty — it still defers like an absent one.
|
|
379
|
+
*/
|
|
380
|
+
export function hasExplicitEmptyToolCatalog(body: unknown): boolean {
|
|
381
|
+
if (!isPlainObject(body)) return false;
|
|
382
|
+
if (Array.isArray(body.tools) && body.tools.length === 0) return true;
|
|
383
|
+
if (!Array.isArray(body.input)) return false;
|
|
384
|
+
return body.input.some(item =>
|
|
385
|
+
isPlainObject(item)
|
|
386
|
+
&& item.type === "additional_tools"
|
|
387
|
+
&& Array.isArray(item.tools)
|
|
388
|
+
&& item.tools.length === 0
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
373
392
|
/**
|
|
374
393
|
* Evaluates whether an individual output item represents an undeclared tool call.
|
|
375
394
|
*
|
|
@@ -703,7 +722,6 @@ function failedBlocks(name: string, newline: string): readonly string[] {
|
|
|
703
722
|
* @param providerExecutedCallTypes - Call types executed by the provider.
|
|
704
723
|
* @param declaredBare - Explicitly declared bare tool names without namespace provenance.
|
|
705
724
|
* @param declaredCustom - Current bare custom declarations eligible for code-mode recovery.
|
|
706
|
-
* @param enforceDeclaredToolNames - Refuse undeclared calls; normalization remains active when false.
|
|
707
725
|
* @returns An SSE block rewrite function.
|
|
708
726
|
*/
|
|
709
727
|
export function createUndeclaredToolCallGuardBlockRewrite(
|
|
@@ -712,8 +730,11 @@ export function createUndeclaredToolCallGuardBlockRewrite(
|
|
|
712
730
|
providerExecutedCallTypes: ProviderExecutedCallTypes = EMPTY_PROVIDER_EXECUTED_CALL_TYPES,
|
|
713
731
|
declaredBare?: ReadonlySet<string>,
|
|
714
732
|
declaredCustom?: ReadonlySet<string>,
|
|
715
|
-
|
|
733
|
+
options?: { refuseUndeclared?: boolean },
|
|
716
734
|
): SseBlockRewrite {
|
|
735
|
+
// Deferred-catalog wires (Chat/Anthropic) keep the default-namespace normalization — the
|
|
736
|
+
// client can only resolve the bare name it declared — while the refusal itself stays off.
|
|
737
|
+
const refuse = options?.refuseUndeclared !== false;
|
|
717
738
|
let tripped = false;
|
|
718
739
|
return (block: string) => {
|
|
719
740
|
if (tripped) return [];
|
|
@@ -725,12 +746,12 @@ export function createUndeclaredToolCallGuardBlockRewrite(
|
|
|
725
746
|
} catch {
|
|
726
747
|
return [block];
|
|
727
748
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
749
|
+
if (refuse) {
|
|
750
|
+
const name = undeclaredToolCallName(parsed, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes, declaredBare, declaredCustom);
|
|
751
|
+
if (name !== undefined) {
|
|
752
|
+
tripped = true;
|
|
753
|
+
return failedBlocks(name, block.includes("\r\n") ? "\r\n" : "\n");
|
|
754
|
+
}
|
|
734
755
|
}
|
|
735
756
|
const normalized = normalizeDefaultNamespaceInPayload(parsed, declared, declaredBare);
|
|
736
757
|
if (normalized.changed) {
|
|
@@ -3,6 +3,8 @@ import { execFile } from "node:child_process";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { durableBunPath } from "../lib/bun-runtime";
|
|
5
5
|
import { selfLaunchArgv } from "../lib/self-launch-argv";
|
|
6
|
+
import { isDesktopSupervised } from "../lib/system-restart-contract";
|
|
7
|
+
import { withLoginShellPath } from "./login-shell-path";
|
|
6
8
|
import {
|
|
7
9
|
WINDOWS_SCHTASKS_CREATE_ACCESS_DENIED_MARKER,
|
|
8
10
|
isWindowsSchtasksCreateAccessDenied,
|
|
@@ -66,6 +68,8 @@ interface StartupInstallLaunch {
|
|
|
66
68
|
execFile?: typeof execFile;
|
|
67
69
|
/** Overrides Bun's own standalone detection, which a source-mode test cannot flip. */
|
|
68
70
|
isStandaloneExecutable?: boolean;
|
|
71
|
+
isDesktopSupervised?: () => boolean;
|
|
72
|
+
withLoginShellPath?: (env: NodeJS.ProcessEnv) => Promise<NodeJS.ProcessEnv>;
|
|
69
73
|
}
|
|
70
74
|
let installLaunch: StartupInstallLaunch = {};
|
|
71
75
|
|
|
@@ -169,7 +173,16 @@ export function installFailureDetail(stdout: string, stderr: string, error: Erro
|
|
|
169
173
|
return classifyCliInstallFailure(stdout, stderr, error).detail;
|
|
170
174
|
}
|
|
171
175
|
|
|
172
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Under OpenCodex Desktop the proxy has the GUI launch PATH, where no user-installed Codex is
|
|
178
|
+
* visible. The shim installer gets the user's login-shell PATH instead (#6971).
|
|
179
|
+
*/
|
|
180
|
+
async function installChildEnv(action: StartupInstallAction): Promise<NodeJS.ProcessEnv> {
|
|
181
|
+
if (action !== "install-shim" || !(installLaunch.isDesktopSupervised ?? isDesktopSupervised)()) return process.env;
|
|
182
|
+
return (installLaunch.withLoginShellPath ?? withLoginShellPath)(process.env);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function runCliInstall(
|
|
173
186
|
action: StartupInstallAction,
|
|
174
187
|
options?: { repair?: boolean },
|
|
175
188
|
): Promise<{ stdout: string; stderr: string }> {
|
|
@@ -183,13 +196,14 @@ function runCliInstall(
|
|
|
183
196
|
isStandaloneExecutable: installLaunch.isStandaloneExecutable,
|
|
184
197
|
});
|
|
185
198
|
const run = installLaunch.execFile ?? execFile;
|
|
199
|
+
const env = await installChildEnv(action);
|
|
186
200
|
return new Promise((resolve, reject) => {
|
|
187
201
|
const timeout = process.platform === "win32" && action === "install-service"
|
|
188
202
|
? 0
|
|
189
203
|
: 60_000;
|
|
190
204
|
run(bun, argv, {
|
|
191
205
|
encoding: "utf8",
|
|
192
|
-
env
|
|
206
|
+
env,
|
|
193
207
|
// A fresh Windows scheduler install now owns its UAC prompt inside this CLI
|
|
194
208
|
// transaction. Killing only the CLI at 60s can orphan its elevated schtasks child,
|
|
195
209
|
// which may register the task after the Dashboard has reported failure. Keep the
|
|
@@ -117,6 +117,7 @@ function conservativeFallback(config: Pick<OcxConfig, "codexAutoStart">): Startu
|
|
|
117
117
|
serviceSupported: process.platform === "win32" || process.platform === "darwin" || process.platform === "linux",
|
|
118
118
|
shimInstalled: shim.installed,
|
|
119
119
|
shimHealthy: shim.healthy,
|
|
120
|
+
...(shim.activationPending ? { shimActivationPending: true } : {}),
|
|
120
121
|
platform: process.platform,
|
|
121
122
|
diagnosticStale: true,
|
|
122
123
|
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The install/repair preflight over the data-plane credential, plus the retry command it
|
|
3
|
+
* names. Split out of `guards.ts` so the guard module stops importing `diagnostics`.
|
|
4
|
+
*
|
|
5
|
+
* Direction of the remaining edge is deliberate: this module asks `guards` for
|
|
6
|
+
* `assertNotAdminToken` and `diagnostics` for the install verdict, and neither imports it back.
|
|
7
|
+
*/
|
|
8
|
+
import { loadConfig } from "../config";
|
|
9
|
+
import { isLoopbackHostname } from "../codex/loopback-target";
|
|
10
|
+
import { readServiceApiTokenState, serviceApiTokenFilePath } from "../lib/service-secrets";
|
|
11
|
+
import { diagnoseService } from "./diagnostics";
|
|
12
|
+
import type { ServiceDiagnostic } from "./diagnostics";
|
|
13
|
+
import { assertNotAdminToken } from "./guards";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The `ocx` command a user should rerun for the service state they actually have.
|
|
17
|
+
*
|
|
18
|
+
* `installed` alone is not enough: `repairService()` refuses a Task-Scheduler-plus-WinSW
|
|
19
|
+
* conflict outright, so recommending repair there names a command guaranteed to fail.
|
|
20
|
+
* Install IS the valid conflict recovery, because `installWindows` removes the native
|
|
21
|
+
* backend first. Exported so the guard tests the real selector rather than a copy of it.
|
|
22
|
+
*/
|
|
23
|
+
export function serviceRetryCommand(
|
|
24
|
+
diag: Pick<ServiceDiagnostic, "installed" | "conflict"> = diagnoseService(),
|
|
25
|
+
): string {
|
|
26
|
+
return diag.installed && !diag.conflict ? "ocx service repair" : "ocx service install";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Preflight for `service install` / `service repair` on the data-plane credential.
|
|
31
|
+
*
|
|
32
|
+
* It used to DEMAND `OPENCODEX_API_AUTH_TOKEN` for a non-loopback hostname, and it threw
|
|
33
|
+
* even when `~/.opencodex/service-api-token` already held a perfectly good token. That is
|
|
34
|
+
* the defect behind the incident this unit exists to close (#4236): an operator exported the
|
|
35
|
+
* ADMIN token as OPENCODEX_API_AUTH_TOKEN because `install` asked for a token, the hub then
|
|
36
|
+
* crash-looped on `assertNotAdminToken`, and `service repair` asked for the same env var
|
|
37
|
+
* again — so the only remembered way to make the command proceed was the thing that broke it.
|
|
38
|
+
*
|
|
39
|
+
* Nobody should have to export a token by hand to run a hub. {@link writeServiceApiTokenFile}
|
|
40
|
+
* provisions one, so the only conditions left that install cannot fix are an admin-token
|
|
41
|
+
* collision in the environment and a token file that exists but cannot be used.
|
|
42
|
+
*/
|
|
43
|
+
export function assertServiceAuthEnvironment(): void {
|
|
44
|
+
const config = loadConfig();
|
|
45
|
+
// Both collision checks come BEFORE the loopback short-circuit, because the launch wrapper
|
|
46
|
+
// exports the token file unconditionally (`buildServiceShellCommand` cats it whenever it
|
|
47
|
+
// exists, whatever the hostname): a management token in either source fences the whole
|
|
48
|
+
// management plane closed at boot, even on a loopback install that needs no admission
|
|
49
|
+
// secret. Returning early is what let that broken state through.
|
|
50
|
+
const present = process.env.OPENCODEX_API_AUTH_TOKEN?.trim();
|
|
51
|
+
if (present) assertNotAdminToken(present);
|
|
52
|
+
const state = readServiceApiTokenState();
|
|
53
|
+
// An existing FILE holding the admin token is the incident shape itself, and the first round
|
|
54
|
+
// only checked the env var — so install/repair reused it and the hub crash-looped at boot.
|
|
55
|
+
// On a machine connected to a hub this same file holds that hub's issued client key, which
|
|
56
|
+
// is never a management token, so the check is a no-op there.
|
|
57
|
+
if (state.kind === "present") assertNotAdminToken(state.token, process.env, "file");
|
|
58
|
+
if (isLoopbackHostname(config.hostname)) return;
|
|
59
|
+
if (present) return;
|
|
60
|
+
// Absent is fine — install/repair generates one below. `unsafe` is not: the writer refuses
|
|
61
|
+
// to replace a path it cannot vouch for, so say so here, where the operator can still act,
|
|
62
|
+
// instead of failing mid-install. Reached from `service repair` as well as `install`, so
|
|
63
|
+
// name a command that can actually succeed (see serviceRetryCommand).
|
|
64
|
+
if (state.kind !== "unsafe") return;
|
|
65
|
+
const diag = diagnoseService();
|
|
66
|
+
throw new Error(
|
|
67
|
+
`The data-plane token file cannot be used (${state.reason}): ${serviceApiTokenFilePath()}. `
|
|
68
|
+
+ `Move it aside, then rerun \`${serviceRetryCommand(diag)}\`; the service provisions a `
|
|
69
|
+
+ "fresh owner-only token and needs nothing from the environment.",
|
|
70
|
+
);
|
|
71
|
+
}
|
package/src/service/cli.ts
CHANGED
|
@@ -11,8 +11,10 @@ import { statusWinswRaw, type WinswStatus } from "../lib/winsw";
|
|
|
11
11
|
import { withWindowsServiceMutationLock } from "../lib/windows-service-mutation-lock";
|
|
12
12
|
import { maybeShowStarPrompt } from "../cli/star-prompt";
|
|
13
13
|
import { serviceStatusReport } from "./diagnostics";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
14
|
+
import { assertServiceAuthEnvironment } from "./auth-preflight";
|
|
15
|
+
import { assertServiceEnvironmentMatchesInstall } from "./guards";
|
|
16
|
+
import { resolveServiceListenPort } from "./definition";
|
|
17
|
+
import { reportServiceServing } from "./health";
|
|
16
18
|
import { platformOps, proxyStillLiveAfterStop, stopTrackedProxyForServiceCommand, installServiceSafely, installFreshWindowsSchedulerSafely, removeServiceInstallState, isServiceInstalled } from "./orchestration";
|
|
17
19
|
import { repairService } from "./repair";
|
|
18
20
|
import type { ServiceRepairVerb } from "./repair";
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape of a service definition: the shell command both Unix backends bake into
|
|
3
|
+
* their unit/plist, the port they pin into it, and the proxy environment it carries.
|
|
4
|
+
*
|
|
5
|
+
* Leaf module. It reads config and the token-file path, and asks nothing of the
|
|
6
|
+
* backends that consume it — importing it from `launchd.ts`/`systemd.ts` cannot
|
|
7
|
+
* route back through them (see `installedServiceListenPort` in `health.ts`, which
|
|
8
|
+
* intentionally stays behind because it asks every backend).
|
|
9
|
+
*/
|
|
10
|
+
import { loadConfig } from "../config";
|
|
11
|
+
import { serviceApiTokenFilePath } from "../lib/service-secrets";
|
|
12
|
+
import { PROXY_ENV_KEYS } from "../lib/proxy-env";
|
|
13
|
+
|
|
14
|
+
export function shellQuote(value: string): string {
|
|
15
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Listen port baked into service wrappers / WinSW XML.
|
|
20
|
+
* Priority: explicit override → OCX_BAKE_PORT (update restart) → config.port → 10100.
|
|
21
|
+
* `config.port === 0` means ephemeral for interactive start; services need a stable pin,
|
|
22
|
+
* so treat 0 / invalid like unset (default 10100) instead of baking `--port 0`.
|
|
23
|
+
*/
|
|
24
|
+
export function resolveServiceListenPort(override?: number): number {
|
|
25
|
+
if (typeof override === "number" && Number.isFinite(override) && override > 0 && override <= 65535) {
|
|
26
|
+
return Math.trunc(override);
|
|
27
|
+
}
|
|
28
|
+
const baked = process.env.OCX_BAKE_PORT?.trim();
|
|
29
|
+
if (baked && /^\d+$/.test(baked)) {
|
|
30
|
+
const n = Number(baked);
|
|
31
|
+
if (n > 0 && n <= 65535) return n;
|
|
32
|
+
}
|
|
33
|
+
const configured = loadConfig().port;
|
|
34
|
+
if (typeof configured === "number" && configured > 0 && configured <= 65535) return configured;
|
|
35
|
+
return 10100;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function buildServiceShellCommand(bun: string, cli: string | null, port = resolveServiceListenPort()): string {
|
|
39
|
+
const tokenFile = serviceApiTokenFilePath();
|
|
40
|
+
const args = cli ? `${shellQuote(cli)} start` : "start";
|
|
41
|
+
return `if [ -f ${shellQuote(tokenFile)} ]; then OPENCODEX_API_AUTH_TOKEN="$(cat ${shellQuote(tokenFile)})"; export OPENCODEX_API_AUTH_TOKEN; fi; exec ${shellQuote(bun)} ${args} --port ${port}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The same command shape, launched through a stable `ocx` executable instead of an
|
|
46
|
+
* explicit Bun + CLI pair. The token-file preamble is identical and deliberately shared
|
|
47
|
+
* in form: the service still reads the token from disk at start and never carries it in
|
|
48
|
+
* the unit.
|
|
49
|
+
*/
|
|
50
|
+
export function buildServiceLauncherShellCommand(launcher: string, port = resolveServiceListenPort()): string {
|
|
51
|
+
const tokenFile = serviceApiTokenFilePath();
|
|
52
|
+
return `if [ -f ${shellQuote(tokenFile)} ]; then OPENCODEX_API_AUTH_TOKEN="$(cat ${shellQuote(tokenFile)})"; export OPENCODEX_API_AUTH_TOKEN; fi; exec ${shellQuote(launcher)} start --port ${port}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Shared tail parser for the baked `--port <n>`.
|
|
57
|
+
*
|
|
58
|
+
* Terminators cover all three artifact shapes: whitespace (batch wrapper, systemd
|
|
59
|
+
* unit), `"` (systemd's quoted ExecStart), `<` (WinSW's `</arguments>`), and `&` (an
|
|
60
|
+
* XML-escaped quote). Matched LAST because every artifact carries the Bun and CLI
|
|
61
|
+
* paths ahead of the argument, and a path containing the literal must not shadow it.
|
|
62
|
+
*/
|
|
63
|
+
export function parseBakedListenPort(read: () => string): number | null {
|
|
64
|
+
try {
|
|
65
|
+
const last = [...read().matchAll(/start --port (\d{1,5})(?:\s|"|&|<|$)/gm)].at(-1);
|
|
66
|
+
if (!last) return null;
|
|
67
|
+
const n = Number(last[1]);
|
|
68
|
+
return n > 0 && n <= 65535 ? n : null;
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Outbound proxy settings the installing shell had, resolved for baking into a service
|
|
76
|
+
* definition.
|
|
77
|
+
*
|
|
78
|
+
* A service manager does not inherit the environment of the shell that installed it, and
|
|
79
|
+
* `ExecStart=/bin/sh -lc` is dash on Ubuntu/WSL — login dash reads `.profile`, not
|
|
80
|
+
* `.bashrc`, which is where proxy exports usually live. So a user who needs a proxy to
|
|
81
|
+
* reach the upstream got a service that dialed direct: the socket was reset, the retry
|
|
82
|
+
* budget drained, and the request surfaced as `502 Provider unreachable` (#2107). The
|
|
83
|
+
* same install driven through `ocx codex-shim` worked, because that path spawns with
|
|
84
|
+
* `{ ...process.env }`.
|
|
85
|
+
*
|
|
86
|
+
* Lower-case variants are honored because curl-style tooling sets them and the runtime's
|
|
87
|
+
* own `applyProxyEnv` already treats both cases as equivalent. Only the canonical
|
|
88
|
+
* upper-case name is baked, so a definition never carries two spellings of one setting.
|
|
89
|
+
*/
|
|
90
|
+
export function resolvedProxyEnv(env: NodeJS.ProcessEnv = process.env): { name: string; value: string }[] {
|
|
91
|
+
const resolved: { name: string; value: string }[] = [];
|
|
92
|
+
for (const key of PROXY_ENV_KEYS) {
|
|
93
|
+
const value = env[key]?.trim() || env[key.toLowerCase()]?.trim();
|
|
94
|
+
if (value) resolved.push({ name: key, value });
|
|
95
|
+
}
|
|
96
|
+
return resolved;
|
|
97
|
+
}
|
|
@@ -122,11 +122,25 @@ function macLoginRegistration(deps: DesktopStartupDeps) {
|
|
|
122
122
|
}
|
|
123
123
|
const program = /^\s*program = (.+)$/m.exec(loaded)?.[1];
|
|
124
124
|
const loadedPath = /^\s*path = (.+)$/m.exec(loaded)?.[1];
|
|
125
|
+
// macOS 13+ Background Task Management can take over the user LaunchAgent and resubmit it
|
|
126
|
+
// through smd; launchctl then names the submitter instead of the plist (#7019). The loaded
|
|
127
|
+
// job no longer points at the registration, so credit it like an unloaded job: same program,
|
|
128
|
+
// and the entire registration re-read unchanged.
|
|
129
|
+
if (program !== undefined && loadedPath !== undefined && SMD_SUBMITTED.test(loadedPath)
|
|
130
|
+
&& /^\s*managed_by = (.+)$/m.exec(loaded)?.[1] === SERVICE_MANAGEMENT && realpathSync(program) === app) {
|
|
131
|
+
const final = capture();
|
|
132
|
+
if (!final || JSON.stringify(first) !== JSON.stringify(final)) return { app, proxy, loginEnabled: false };
|
|
133
|
+
return { app, proxy, loginEnabled: true, loginRegistration: "loaded" as const };
|
|
134
|
+
}
|
|
125
135
|
const loginEnabled = program !== undefined && realpathSync(program) === app
|
|
126
136
|
&& loadedPath !== undefined && realpathSync(loadedPath) === first.registrationPath;
|
|
127
137
|
return { app, proxy, loginEnabled, ...(loginEnabled ? { loginRegistration: "loaded" as const } : {}) };
|
|
128
138
|
}
|
|
129
139
|
|
|
140
|
+
/** launchctl's `path` for a job Background Task Management resubmitted through smd. */
|
|
141
|
+
const SMD_SUBMITTED = /^\(submitted by smd\.\d+\)$/;
|
|
142
|
+
const SERVICE_MANAGEMENT = "com.apple.xpc.ServiceManagement";
|
|
143
|
+
|
|
130
144
|
type DesktopLoginFacts = Pick<DesktopStartupDiagnostic, "loginEnabled" | "loginRegistration">;
|
|
131
145
|
|
|
132
146
|
/**
|