@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,15 +7,16 @@
|
|
|
7
7
|
* `intercept`. Everything else, and claude.ai whenever any condition fails, is a blind tunnel.
|
|
8
8
|
*
|
|
9
9
|
* The decision is cached by `refresh()` and only read by `selectTunnel`, so a CONNECT never
|
|
10
|
-
* waits on the
|
|
10
|
+
* waits on the OS trust store or the catalog. Arming needs all of: a supported platform, the persisted resolved mode is
|
|
11
11
|
* first-party (observation-aware, so a pre-field first-party install counts), Desktop intent on,
|
|
12
12
|
* `claudeCode.intercept.picker !== false`, the disarm latch clear, the picker listener up and the
|
|
13
|
-
* current picker CA trusted in the
|
|
13
|
+
* current picker CA trusted in the OS root store. `disarm()` stops terminating immediately and
|
|
14
14
|
* latches; only the picker controller's owner-only `rearm()` clears the latch.
|
|
15
15
|
*/
|
|
16
16
|
import { join } from "node:path";
|
|
17
17
|
import type { OcxConfig } from "../../types";
|
|
18
18
|
import type { ClaudeDesktopMode } from "../desktop-first-party";
|
|
19
|
+
import { supportsDesktopPicker } from "../desktop-picker-platform";
|
|
19
20
|
import type { TunnelDecision } from "./connect-proxy";
|
|
20
21
|
import type { PemKeyPair } from "./local-ca";
|
|
21
22
|
import { PICKER_HOST, ensurePickerCa, issuePickerLeaf, pickerCaFingerprints, pickerLeafCertPath, pickerStateDir, type PickerCa } from "./picker-ca";
|
|
@@ -108,7 +109,7 @@ export function pickerDesired(
|
|
|
108
109
|
mode: ClaudeDesktopMode,
|
|
109
110
|
platform: NodeJS.Platform = process.platform,
|
|
110
111
|
): boolean {
|
|
111
|
-
return platform
|
|
112
|
+
return supportsDesktopPicker(platform)
|
|
112
113
|
&& config.clientIntegrations?.["claude-desktop"] !== false
|
|
113
114
|
&& mode === "first-party"
|
|
114
115
|
&& config.claudeCode?.intercept?.picker !== false;
|
|
@@ -225,7 +226,7 @@ export function createPickerRuntime(options: CreatePickerRuntimeOptions): Picker
|
|
|
225
226
|
desired = pickerDesired(fresh, mode, platform);
|
|
226
227
|
if (!desired || latched) {
|
|
227
228
|
armed = false;
|
|
228
|
-
reason = platform
|
|
229
|
+
reason = !supportsDesktopPicker(platform) ? "unsupported_platform" : latched ? "disarmed" : "not_desired";
|
|
229
230
|
closeListener();
|
|
230
231
|
return;
|
|
231
232
|
}
|
|
@@ -312,7 +313,7 @@ export function createPickerRuntime(options: CreatePickerRuntimeOptions): Picker
|
|
|
312
313
|
const current = snapshot?.current() ?? null;
|
|
313
314
|
return {
|
|
314
315
|
desired,
|
|
315
|
-
supported: platform
|
|
316
|
+
supported: supportsDesktopPicker(platform),
|
|
316
317
|
trust,
|
|
317
318
|
listenerReady: listener !== null,
|
|
318
319
|
effective: armed && listener !== null && !latched && !stopped,
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { X509Certificate } from "node:crypto";
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { resolveTrustedWindowsPowerShellExe } from "../../lib/windows-elevation";
|
|
6
|
+
import {
|
|
7
|
+
acceptsPickerAuthority,
|
|
8
|
+
PICKER_CA_COMMON_NAME,
|
|
9
|
+
PICKER_HOST,
|
|
10
|
+
pickerCaFingerprints,
|
|
11
|
+
} from "./picker-ca";
|
|
12
|
+
import type { PickerTrustState, SecurityResult } from "./picker-trust";
|
|
13
|
+
|
|
14
|
+
/** powershell.exe argv result; alias so the two trust seams never drift apart. */
|
|
15
|
+
export type WindowsTrustResult = SecurityResult;
|
|
16
|
+
/** Per-script options; fakes may ignore them, so single-arg fakes stay portable. */
|
|
17
|
+
export interface WindowsTrustRunnerOptions { timeoutMs?: number }
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* powershell.exe argv runner. A SecurityRunner satisfies this shape so macOS-style
|
|
21
|
+
* fakes stay portable. The caller in picker-trust.ts only forwards an explicitly
|
|
22
|
+
* injected runner and otherwise leaves this undefined for the Windows default.
|
|
23
|
+
*/
|
|
24
|
+
export type WindowsTrustRunner = (
|
|
25
|
+
args: readonly string[],
|
|
26
|
+
options?: WindowsTrustRunnerOptions,
|
|
27
|
+
) => Promise<WindowsTrustResult>;
|
|
28
|
+
|
|
29
|
+
/** Read-only argv prefix: no profile, no logo, no interaction. Used for inspection. */
|
|
30
|
+
export const WINDOWS_TRUST_POWERSHELL_PREFIX = ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command"] as const;
|
|
31
|
+
/** Mutating argv prefix: the OS confirmation dialog must be able to appear. */
|
|
32
|
+
export const WINDOWS_TRUST_POWERSHELL_INTERACTIVE_PREFIX = ["-NoLogo", "-NoProfile", "-Command"] as const;
|
|
33
|
+
|
|
34
|
+
/** Read-only inspection budget: a hung store lookup must never stall status checks. */
|
|
35
|
+
export const WINDOWS_TRUST_INSPECT_TIMEOUT_MS = 10_000;
|
|
36
|
+
/** Interactive import budget: Root installs raise the OS confirmation dialog. */
|
|
37
|
+
export const WINDOWS_TRUST_IMPORT_TIMEOUT_MS = 120_000;
|
|
38
|
+
/**
|
|
39
|
+
* Removal budget stays bounded: predecessor cleanup also runs in headless migration
|
|
40
|
+
* and startup paths where an OS confirmation dialog cannot be answered. A timed-out or
|
|
41
|
+
* declined removal reports failure and is retried on the next explicit activation.
|
|
42
|
+
*/
|
|
43
|
+
export const WINDOWS_TRUST_UNTRUST_TIMEOUT_MS = 30_000;
|
|
44
|
+
|
|
45
|
+
const OUTPUT_LIMIT = 64 * 1024;
|
|
46
|
+
const TRUST_MARKER = "OCX_PICKER_TRUST=";
|
|
47
|
+
const SHA1_PATTERN = /^[0-9A-F]{40}$/;
|
|
48
|
+
const BASE64_PATTERN = /^[A-Za-z0-9+/=]*$/;
|
|
49
|
+
|
|
50
|
+
/** Uppercase hex without separators, or null. Trust matches on this, never on the CN. */
|
|
51
|
+
export function normalizePickerTrustSha1(value: string): string | null {
|
|
52
|
+
const normalized = value.replace(/[:\s]/g, "").toUpperCase();
|
|
53
|
+
return SHA1_PATTERN.test(normalized) ? normalized : null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Copy the environment minus every PSModulePath entry (case-insensitive).
|
|
58
|
+
* The trust scripts are .NET-only so module autoload is pure cost and shadowing
|
|
59
|
+
* risk: a PSModulePath entry can shadow resolution and stall process start on
|
|
60
|
+
* hosts with a cold module cache. Nothing here needs a module.
|
|
61
|
+
*/
|
|
62
|
+
export function sanitizeWindowsPowerShellEnv(env: NodeJS.ProcessEnv = process.env): Record<string, string | undefined> {
|
|
63
|
+
const out: Record<string, string | undefined> = {};
|
|
64
|
+
for (const [key, value] of Object.entries(env)) {
|
|
65
|
+
if (key.toLowerCase() === "psmodulepath") continue;
|
|
66
|
+
out[key] = value;
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Production runner: trusted System32 powershell.exe, hidden window, sanitized
|
|
73
|
+
* module path, bounded runtime. Timeout and output-read errors kill the child and report code null
|
|
74
|
+
* so callers map it to unknown/declined instead of hanging periodic status.
|
|
75
|
+
*/
|
|
76
|
+
export const defaultWindowsPowerShellRunner: WindowsTrustRunner = async (args, options) => {
|
|
77
|
+
const timeoutMs = options?.timeoutMs ?? WINDOWS_TRUST_INSPECT_TIMEOUT_MS;
|
|
78
|
+
let exe: string;
|
|
79
|
+
try {
|
|
80
|
+
exe = resolveTrustedWindowsPowerShellExe();
|
|
81
|
+
} catch (error) {
|
|
82
|
+
return { code: null, stdout: "", stderr: error instanceof Error ? error.message : String(error) };
|
|
83
|
+
}
|
|
84
|
+
let child: { kill(): void } | undefined;
|
|
85
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
86
|
+
try {
|
|
87
|
+
const proc = Bun.spawn([exe, ...args], {
|
|
88
|
+
stdin: "ignore",
|
|
89
|
+
stdout: "pipe",
|
|
90
|
+
stderr: "pipe",
|
|
91
|
+
windowsHide: true,
|
|
92
|
+
env: sanitizeWindowsPowerShellEnv(),
|
|
93
|
+
});
|
|
94
|
+
child = proc;
|
|
95
|
+
const pending = (async () => {
|
|
96
|
+
const [out, err, code] = await Promise.all([
|
|
97
|
+
new Response(proc.stdout).text(),
|
|
98
|
+
new Response(proc.stderr).text(),
|
|
99
|
+
proc.exited,
|
|
100
|
+
]);
|
|
101
|
+
return { out, err, code };
|
|
102
|
+
})();
|
|
103
|
+
const settled = await new Promise<{ out: string; err: string; code: number } | null>((resolve, reject) => {
|
|
104
|
+
timer = setTimeout(() => {
|
|
105
|
+
try { child?.kill(); } catch { /* already gone */ }
|
|
106
|
+
resolve(null);
|
|
107
|
+
}, timeoutMs);
|
|
108
|
+
pending.then(resolve, reject);
|
|
109
|
+
});
|
|
110
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
111
|
+
if (settled === null) {
|
|
112
|
+
return { code: null, stdout: "", stderr: `powershell trust script timed out after ${timeoutMs}ms` };
|
|
113
|
+
}
|
|
114
|
+
return { code: settled.code, stdout: settled.out.slice(-OUTPUT_LIMIT), stderr: settled.err.slice(-OUTPUT_LIMIT) };
|
|
115
|
+
} catch (error) {
|
|
116
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
117
|
+
try { child?.kill(); } catch { /* already gone */ }
|
|
118
|
+
return { code: null, stdout: "", stderr: error instanceof Error ? error.message : String(error) };
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
function base64Utf8(value: string): string {
|
|
123
|
+
return Buffer.from(value, "utf8").toString("base64");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** A filesystem path carried as data, never as PowerShell syntax. */
|
|
127
|
+
function encodedPathExpression(path: string): string {
|
|
128
|
+
const encoded = base64Utf8(path);
|
|
129
|
+
if (!BASE64_PATTERN.test(encoded)) throw new Error("path encoding failed");
|
|
130
|
+
return `[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${encoded}'))`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Interpolated constants must not carry a quote or wildcard that changes the script. */
|
|
134
|
+
function psLiteral(value: string, label: string): string {
|
|
135
|
+
if (value.includes("'") || /[*?[\]]/.test(value)) throw new Error(`unsafe ${label} for script literal`);
|
|
136
|
+
return `'${value}'`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function markerOf(stdout: string): string | null {
|
|
140
|
+
const line = stdout
|
|
141
|
+
.split(/\r?\n/)
|
|
142
|
+
.map(entry => entry.trim())
|
|
143
|
+
.find(entry => entry.startsWith(TRUST_MARKER));
|
|
144
|
+
return line === undefined ? null : line.slice(TRUST_MARKER.length);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function currentUserRootStore(): string {
|
|
148
|
+
return "New-Object Security.Cryptography.X509Certificates.X509Store('Root','CurrentUser')";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function subjectLikePattern(): string {
|
|
152
|
+
if (PICKER_CA_COMMON_NAME.length === 0 || /['*?[\]]/.test(PICKER_CA_COMMON_NAME)) {
|
|
153
|
+
throw new Error("unsafe picker CA common name for script literal");
|
|
154
|
+
}
|
|
155
|
+
return `'*CN=${PICKER_CA_COMMON_NAME}*'`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Inspect script: exact SHA-1 lookup in CurrentUser Root, persisted-leaf chain build
|
|
160
|
+
* against that root, plus DNS-name and validity checks. Emits exactly one marker
|
|
161
|
+
* line and exits 0 (trusted), 1 (untrusted) or 2 (error). Read-only: trust is never
|
|
162
|
+
* added or removed here; installation happens only on explicit activation via trust.
|
|
163
|
+
*/
|
|
164
|
+
export function buildWindowsTrustInspectScript(leafPath: string, wantSha1: string): string {
|
|
165
|
+
const want = normalizePickerTrustSha1(wantSha1);
|
|
166
|
+
if (want === null) throw new Error("picker trust fingerprint must be 40 hex characters");
|
|
167
|
+
if (leafPath.length === 0) throw new Error("picker trust leaf path must not be empty");
|
|
168
|
+
const host = psLiteral(PICKER_HOST, "picker host");
|
|
169
|
+
return [
|
|
170
|
+
"$ErrorActionPreference='Stop'",
|
|
171
|
+
`$leafFile=${encodedPathExpression(leafPath)}`,
|
|
172
|
+
`$want=${psLiteral(want, "picker CA fingerprint")}`,
|
|
173
|
+
`$store=${currentUserRootStore()}`,
|
|
174
|
+
"$store.Open('ReadOnly')",
|
|
175
|
+
"try {",
|
|
176
|
+
"$found=@($store.Certificates | Where-Object { $_.Thumbprint -eq $want })",
|
|
177
|
+
`if($found.Count -eq 0) { Write-Output '${TRUST_MARKER}UNTRUSTED'; exit 1 }`,
|
|
178
|
+
"$ca=$found[0]",
|
|
179
|
+
`if($ca.Subject -notlike ${subjectLikePattern()}) { Write-Output '${TRUST_MARKER}UNTRUSTED'; exit 1 }`,
|
|
180
|
+
"$leaf=New-Object Security.Cryptography.X509Certificates.X509Certificate2($leafFile)",
|
|
181
|
+
"$dns=$leaf.GetNameInfo([Security.Cryptography.X509Certificates.X509NameType]::DnsName,$false)",
|
|
182
|
+
`if($dns -ne ${host}) { Write-Output '${TRUST_MARKER}UNTRUSTED'; exit 1 }`,
|
|
183
|
+
"$now=Get-Date",
|
|
184
|
+
`if($leaf.NotBefore -gt $now -or $leaf.NotAfter -lt $now) { Write-Output '${TRUST_MARKER}UNTRUSTED'; exit 1 }`,
|
|
185
|
+
`if($leaf.Issuer -ne $ca.Subject) { Write-Output '${TRUST_MARKER}UNTRUSTED'; exit 1 }`,
|
|
186
|
+
"$chain=New-Object Security.Cryptography.X509Certificates.X509Chain",
|
|
187
|
+
"$chain.ChainPolicy.RevocationMode=[Security.Cryptography.X509Certificates.X509RevocationMode]::NoCheck",
|
|
188
|
+
"$chain.ChainPolicy.VerificationFlags=[Security.Cryptography.X509Certificates.X509VerificationFlags]::AllowUnknownCertificateAuthority",
|
|
189
|
+
"$chain.ChainPolicy.ExtraStore.Add($ca) | Out-Null",
|
|
190
|
+
"$built=$chain.Build($leaf)",
|
|
191
|
+
"$root=$null",
|
|
192
|
+
"if($chain.ChainElements.Count -gt 0) { $root=$chain.ChainElements[$chain.ChainElements.Count-1].Certificate }",
|
|
193
|
+
`if($built -and ($null -ne $root) -and ($root.Thumbprint -eq $want)) { Write-Output '${TRUST_MARKER}TRUSTED'; exit 0 }`,
|
|
194
|
+
`Write-Output '${TRUST_MARKER}UNTRUSTED'`,
|
|
195
|
+
"exit 1",
|
|
196
|
+
`} catch { Write-Output '${TRUST_MARKER}ERROR'; exit 2 } finally { $store.Close() }`,
|
|
197
|
+
].join(";");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Install script: imports the snapshot file into CurrentUser Root after re-checking
|
|
202
|
+
* its thumbprint inside the script. Adding to Root raises the OS confirmation
|
|
203
|
+
* dialog and that user approval is the install gate; the powershell.exe process
|
|
204
|
+
* itself stays hidden and only the OS dialog is ever visible.
|
|
205
|
+
*/
|
|
206
|
+
export function buildWindowsTrustInstallScript(caPath: string, wantSha1: string): string {
|
|
207
|
+
const want = normalizePickerTrustSha1(wantSha1);
|
|
208
|
+
if (want === null) throw new Error("picker trust fingerprint must be 40 hex characters");
|
|
209
|
+
if (caPath.length === 0) throw new Error("picker trust CA path must not be empty");
|
|
210
|
+
return [
|
|
211
|
+
"$ErrorActionPreference='Stop'",
|
|
212
|
+
`$caFile=${encodedPathExpression(caPath)}`,
|
|
213
|
+
`$want=${psLiteral(want, "picker CA fingerprint")}`,
|
|
214
|
+
"$cert=New-Object Security.Cryptography.X509Certificates.X509Certificate2($caFile)",
|
|
215
|
+
`if($cert.Thumbprint -ne $want) { Write-Output '${TRUST_MARKER}ERROR'; exit 2 }`,
|
|
216
|
+
`$store=${currentUserRootStore()}`,
|
|
217
|
+
"$store.Open('ReadWrite')",
|
|
218
|
+
"try {",
|
|
219
|
+
"$found=@($store.Certificates | Where-Object { $_.Thumbprint -eq $want })",
|
|
220
|
+
"if($found.Count -eq 0) { $store.Add($cert) }",
|
|
221
|
+
`Write-Output '${TRUST_MARKER}INSTALLED'`,
|
|
222
|
+
"exit 0",
|
|
223
|
+
`} catch { Write-Output '${TRUST_MARKER}ERROR'; exit 2 } finally { $store.Close() }`,
|
|
224
|
+
].join(";");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Remove script: verifies picker identity for every exact-thumbprint Root entry before
|
|
229
|
+
* deleting any of them, then re-checks. Legacy picker roots need not match the new install profile.
|
|
230
|
+
* Removal from Root raises OS confirmation like an install, so this runs on the
|
|
231
|
+
* interactive prefix with a bounded 30s budget: predecessor cleanup also runs in headless
|
|
232
|
+
* removal may prompt, mirroring the macOS keychain prompt on migration cleanup.
|
|
233
|
+
*/
|
|
234
|
+
export function buildWindowsTrustRemoveScript(wantSha1: string): string {
|
|
235
|
+
const want = normalizePickerTrustSha1(wantSha1);
|
|
236
|
+
if (want === null) throw new Error("picker trust fingerprint must be 40 hex characters");
|
|
237
|
+
return [
|
|
238
|
+
"$ErrorActionPreference='Stop'",
|
|
239
|
+
`$want=${psLiteral(want, "picker CA fingerprint")}`,
|
|
240
|
+
`$pickerName=${psLiteral(PICKER_CA_COMMON_NAME, "picker CA common name")}`,
|
|
241
|
+
"try {",
|
|
242
|
+
`$store=${currentUserRootStore()}`,
|
|
243
|
+
"$store.Open('ReadWrite')",
|
|
244
|
+
"try { $found=@($store.Certificates | Where-Object { $_.Thumbprint -eq $want });",
|
|
245
|
+
"foreach($c in $found) {",
|
|
246
|
+
`if($c.GetNameInfo([Security.Cryptography.X509Certificates.X509NameType]::SimpleName,$false) -ne $pickerName) { Write-Output '${TRUST_MARKER}ERROR'; exit 2 }`,
|
|
247
|
+
"}",
|
|
248
|
+
"foreach($c in $found) { $store.Remove($c) } } finally { $store.Close() }",
|
|
249
|
+
`$check=${currentUserRootStore()}`,
|
|
250
|
+
"$check.Open('ReadOnly')",
|
|
251
|
+
"try { $left=@($check.Certificates | Where-Object { $_.Thumbprint -eq $want });",
|
|
252
|
+
`if($left.Count -eq 0) { Write-Output '${TRUST_MARKER}REMOVED'; exit 0 }`,
|
|
253
|
+
`Write-Output '${TRUST_MARKER}ERROR'; exit 1 } finally { $check.Close() }`,
|
|
254
|
+
`} catch { Write-Output '${TRUST_MARKER}ERROR'; exit 2 }`,
|
|
255
|
+
].join(";");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function powershellArgs(script: string, interactive: boolean): string[] {
|
|
259
|
+
const prefix = interactive ? WINDOWS_TRUST_POWERSHELL_INTERACTIVE_PREFIX : WINDOWS_TRUST_POWERSHELL_PREFIX;
|
|
260
|
+
return [...prefix, script];
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export async function inspectPickerTrustWindows(
|
|
264
|
+
leafPath: string,
|
|
265
|
+
caSha1: string,
|
|
266
|
+
run?: WindowsTrustRunner,
|
|
267
|
+
): Promise<PickerTrustState> {
|
|
268
|
+
if (normalizePickerTrustSha1(caSha1) === null || leafPath.length === 0) return "untrusted";
|
|
269
|
+
const exec = run ?? defaultWindowsPowerShellRunner;
|
|
270
|
+
try {
|
|
271
|
+
const result = await exec(powershellArgs(buildWindowsTrustInspectScript(leafPath, caSha1), false), {
|
|
272
|
+
timeoutMs: WINDOWS_TRUST_INSPECT_TIMEOUT_MS,
|
|
273
|
+
});
|
|
274
|
+
if (result.code === 0 && markerOf(result.stdout) === "TRUSTED") return "trusted";
|
|
275
|
+
if (result.code === 1 && markerOf(result.stdout) === "UNTRUSTED") return "untrusted";
|
|
276
|
+
return "unknown";
|
|
277
|
+
} catch {
|
|
278
|
+
return "unknown";
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export async function trustPickerCaWindows(
|
|
283
|
+
caPath: string,
|
|
284
|
+
run?: WindowsTrustRunner,
|
|
285
|
+
cert?: { pem?: string },
|
|
286
|
+
): Promise<{ ok: boolean; reason?: "unsupported" | "declined_or_failed" }> {
|
|
287
|
+
// The caller gates this on explicit activation; status and restart paths never call it.
|
|
288
|
+
// The installed bytes are always a private snapshot of the authority authorized here,
|
|
289
|
+
// never the shared ca.pem a same-user process could swap mid-flight.
|
|
290
|
+
const exec = run ?? defaultWindowsPowerShellRunner;
|
|
291
|
+
let privateDir: string | undefined;
|
|
292
|
+
try {
|
|
293
|
+
let pem = cert?.pem;
|
|
294
|
+
if (pem === undefined) pem = readFileSync(caPath, "utf8");
|
|
295
|
+
if (!acceptsPickerAuthority(pem)) return { ok: false, reason: "declined_or_failed" };
|
|
296
|
+
const { sha1 } = pickerCaFingerprints(pem);
|
|
297
|
+
privateDir = mkdtempSync(join(tmpdir(), "ocx-picker-ca-win-"));
|
|
298
|
+
const snapshot = join(privateDir, "ca.pem");
|
|
299
|
+
writeFileSync(snapshot, pem, { mode: 0o600 });
|
|
300
|
+
const result = await exec(powershellArgs(buildWindowsTrustInstallScript(snapshot, sha1), true), {
|
|
301
|
+
timeoutMs: WINDOWS_TRUST_IMPORT_TIMEOUT_MS,
|
|
302
|
+
});
|
|
303
|
+
return result.code === 0 && markerOf(result.stdout) === "INSTALLED"
|
|
304
|
+
? { ok: true }
|
|
305
|
+
: { ok: false, reason: "declined_or_failed" };
|
|
306
|
+
} catch {
|
|
307
|
+
return { ok: false, reason: "declined_or_failed" };
|
|
308
|
+
} finally {
|
|
309
|
+
if (privateDir !== undefined) rmSync(privateDir, { recursive: true, force: true });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export async function untrustPickerCaWindows(
|
|
314
|
+
fingerprintSha1: string,
|
|
315
|
+
run?: WindowsTrustRunner,
|
|
316
|
+
cert?: { pem: string },
|
|
317
|
+
): Promise<{ ok: boolean }> {
|
|
318
|
+
const exec = run ?? defaultWindowsPowerShellRunner;
|
|
319
|
+
if (normalizePickerTrustSha1(fingerprintSha1) === null) return { ok: false };
|
|
320
|
+
try {
|
|
321
|
+
// Public recovery state is not removal authority. Accept legacy picker roots,
|
|
322
|
+
// but reject foreign certificates before invoking even an injected runner.
|
|
323
|
+
if (cert) {
|
|
324
|
+
const root = new X509Certificate(cert.pem);
|
|
325
|
+
const names = root.subject.split("\n");
|
|
326
|
+
if (!root.ca || !names.includes(`CN=${PICKER_CA_COMMON_NAME}`)
|
|
327
|
+
|| names.filter(name => name.startsWith("CN=")).length !== 1
|
|
328
|
+
|| root.issuer !== root.subject || !root.verify(root.publicKey)
|
|
329
|
+
|| pickerCaFingerprints(cert.pem).sha1 !== normalizePickerTrustSha1(fingerprintSha1)) return { ok: false };
|
|
330
|
+
}
|
|
331
|
+
const result = await exec(powershellArgs(buildWindowsTrustRemoveScript(fingerprintSha1), true), {
|
|
332
|
+
timeoutMs: WINDOWS_TRUST_UNTRUST_TIMEOUT_MS,
|
|
333
|
+
});
|
|
334
|
+
return result.code === 0 && markerOf(result.stdout) === "REMOVED" ? { ok: true } : { ok: false };
|
|
335
|
+
} catch {
|
|
336
|
+
return { ok: false };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
@@ -2,6 +2,11 @@ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { homedir, tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { PICKER_CA_COMMON_NAME, PICKER_HOST } from "./picker-ca";
|
|
5
|
+
import {
|
|
6
|
+
inspectPickerTrustWindows,
|
|
7
|
+
trustPickerCaWindows,
|
|
8
|
+
untrustPickerCaWindows,
|
|
9
|
+
} from "./picker-trust-windows";
|
|
5
10
|
|
|
6
11
|
/** Trust is scoped to the current root fingerprint and a verified persisted leaf. */
|
|
7
12
|
export type PickerTrustState = "trusted" | "untrusted" | "unsupported" | "unknown";
|
|
@@ -61,6 +66,11 @@ export async function inspectPickerTrust(
|
|
|
61
66
|
run: SecurityRunner = defaultSecurityRunner,
|
|
62
67
|
platform: NodeJS.Platform = process.platform,
|
|
63
68
|
): Promise<PickerTrustState> {
|
|
69
|
+
if (platform === "win32") {
|
|
70
|
+
// The macOS default runner is never passed down: only an explicitly injected
|
|
71
|
+
// runner crosses over, otherwise the Windows default powershell.exe runner runs.
|
|
72
|
+
return inspectPickerTrustWindows(leafPath, caSha1, run === defaultSecurityRunner ? undefined : run);
|
|
73
|
+
}
|
|
64
74
|
if (platform !== "darwin") return "unsupported";
|
|
65
75
|
const keychain = loginKeychainPath();
|
|
66
76
|
try {
|
|
@@ -86,6 +96,9 @@ export async function trustPickerCa(
|
|
|
86
96
|
platform: NodeJS.Platform = process.platform,
|
|
87
97
|
cert?: { pem?: string },
|
|
88
98
|
): Promise<{ ok: boolean; reason?: "unsupported" | "declined_or_failed" }> {
|
|
99
|
+
if (platform === "win32") {
|
|
100
|
+
return trustPickerCaWindows(caPath, run === defaultSecurityRunner ? undefined : run, cert);
|
|
101
|
+
}
|
|
89
102
|
if (platform !== "darwin") return { ok: false, reason: "unsupported" };
|
|
90
103
|
// `cert.pem` is the caller-verified certificate bytes: the shared on-disk ca.pem is writable by
|
|
91
104
|
// same-user processes and could be swapped between fingerprint inspection and this install, so
|
|
@@ -117,6 +130,12 @@ export async function untrustPickerCa(
|
|
|
117
130
|
run: SecurityRunner = defaultSecurityRunner,
|
|
118
131
|
platform: NodeJS.Platform = process.platform,
|
|
119
132
|
): Promise<{ ok: boolean }> {
|
|
133
|
+
if (platform === "win32") {
|
|
134
|
+
try {
|
|
135
|
+
return await untrustPickerCaWindows(fingerprintSha1, run === defaultSecurityRunner ? undefined : run,
|
|
136
|
+
{ pem: readFileSync(caPath, "utf8") });
|
|
137
|
+
} catch { return { ok: false }; }
|
|
138
|
+
}
|
|
120
139
|
if (platform !== "darwin") return { ok: false };
|
|
121
140
|
const keychain = loginKeychainPath();
|
|
122
141
|
// Listed means the current picker CA is in the login keychain; unlisted means nothing to remove,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { OcxConfig } from "../../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Port and enablement policy for the Claude intercept pair.
|
|
5
|
+
*
|
|
6
|
+
* These are pure functions of config, with no listener, CA, or picker dependency. They live
|
|
7
|
+
* apart from `./runtime` because the first-party Desktop path reads them too: importing them
|
|
8
|
+
* from `./runtime` made `desktop-first-party` reach the picker runtime, which reaches
|
|
9
|
+
* `desktop-first-party` back. Keeping the policy in a leaf keeps that pair acyclic.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const CLAUDE_INTERCEPT_PORT_OFFSET = 100;
|
|
13
|
+
|
|
14
|
+
export function claudeInterceptEnabled(config: Pick<OcxConfig, "claudeCode" | "runtimeRole">): boolean {
|
|
15
|
+
if (config.runtimeRole === "client") return false;
|
|
16
|
+
if (config.claudeCode?.enabled === false) return false;
|
|
17
|
+
return config.claudeCode?.intercept?.enabled !== false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function claudeInterceptProxyPort(config: Pick<OcxConfig, "claudeCode">, publicPort: number): number {
|
|
21
|
+
const configured = config.claudeCode?.intercept?.port;
|
|
22
|
+
if (typeof configured === "number" && Number.isInteger(configured) && configured >= 1 && configured <= 65535) return configured;
|
|
23
|
+
return publicPort + CLAUDE_INTERCEPT_PORT_OFFSET;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Desktop's egress proxy for picker mode: the port after the intercept proxy (before it at 65535). */
|
|
27
|
+
export function claudePickerProxyPort(config: Pick<OcxConfig, "claudeCode">, publicPort: number): number {
|
|
28
|
+
const interceptPort = claudeInterceptProxyPort(config, publicPort);
|
|
29
|
+
return interceptPort < 65535 ? interceptPort + 1 : interceptPort - 1;
|
|
30
|
+
}
|