@bitkyc08/opencodex 2.83.0 → 2.86.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-BjlttVLy.js +58 -0
- package/gui/dist/assets/Tray-DyR9yBee.js +1 -0
- package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
- package/gui/dist/assets/index-BMssP4k3.js +94 -0
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +2 -3
- package/src/adapters/anthropic/beta-allowlist.ts +87 -8
- package/src/adapters/anthropic/client-identity.ts +1 -1
- package/src/adapters/anthropic/passthrough.ts +33 -4
- package/src/adapters/azure.ts +19 -1
- package/src/adapters/client-fingerprint.ts +24 -0
- package/src/adapters/coding-agent/protocol.ts +15 -8
- package/src/adapters/coding-agent/turn.ts +22 -9
- 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/google-antigravity-wire.ts +22 -1
- package/src/adapters/google.ts +2 -2
- package/src/adapters/ollama-native.ts +29 -1
- package/src/adapters/openai-chat/messages.ts +8 -4
- package/src/adapters/openai-chat/response-events.ts +59 -1
- package/src/adapters/openai-chat.ts +8 -5
- package/src/adapters/openai-responses/empty-assistant.ts +32 -0
- package/src/adapters/openai-responses/passthrough.ts +3 -0
- package/src/adapters/openai-responses/request-strips.ts +10 -2
- package/src/adapters/qoder/adapter.ts +56 -15
- package/src/advisor/consult.ts +220 -0
- package/src/advisor/context.ts +226 -0
- package/src/advisor/disclosure.ts +20 -0
- package/src/advisor/runtime.ts +305 -0
- package/src/advisor/settings.ts +135 -0
- package/src/advisor/state.ts +369 -0
- package/src/advisor/synthetic-tool.ts +33 -0
- package/src/bridge/errors.ts +24 -0
- package/src/bridge.ts +1 -1
- package/src/chat/inbound.ts +30 -6
- package/src/chat/outbound.ts +5 -0
- package/src/claude/context-windows.ts +54 -1
- package/src/claude/desktop-3p-library.ts +43 -3
- package/src/claude/desktop-3p.ts +22 -8
- package/src/claude/desktop-picker-profile.ts +11 -4
- package/src/claude/desktop-profile.ts +10 -1
- package/src/claude/desktop-remote-store-io.ts +5 -3
- package/src/claude/inbound.ts +121 -50
- package/src/claude/intercept/connect-proxy.ts +10 -3
- package/src/claude/intercept/local-ca.ts +8 -1
- package/src/claude/intercept/picker-ca-persistence.ts +3 -1
- package/src/claude/intercept/picker-listener.ts +49 -1
- package/src/claude/intercept/settings-no-proxy.ts +146 -0
- package/src/claude/intercept/settings.ts +101 -9
- package/src/claude/model-default-settings.ts +154 -0
- package/src/claude/model-info.ts +7 -7
- package/src/claude/outbound.ts +13 -6
- package/src/cli/account-api.ts +65 -0
- package/src/cli/account-auth.ts +26 -2
- package/src/cli/account.ts +41 -5
- package/src/cli/advisor.ts +151 -0
- package/src/cli/auxiliary-listener-preflight.ts +37 -0
- package/src/cli/capabilities-access-remote.ts +8 -0
- package/src/cli/capabilities-agents-routing.ts +18 -0
- package/src/cli/capabilities-base.ts +14 -2
- package/src/cli/catalog-command-result.ts +2 -1
- package/src/cli/chatgpt-command.ts +35 -14
- package/src/cli/claude.ts +60 -11
- package/src/cli/config-command.ts +8 -2
- package/src/cli/debug.ts +8 -0
- package/src/cli/dispatch.ts +21 -4
- package/src/cli/doctor.ts +9 -0
- package/src/cli/ensure-desktop-cold-start.ts +329 -0
- package/src/cli/export-command.ts +15 -2
- package/src/cli/help.ts +2 -1
- package/src/cli/hub-disable.ts +99 -0
- package/src/cli/hub.ts +4 -2
- package/src/cli/index.ts +26 -8
- package/src/cli/integration-plan-dto.ts +2 -0
- package/src/cli/link-child.ts +104 -0
- package/src/cli/link-enrollment.ts +96 -0
- package/src/cli/link.ts +39 -24
- package/src/cli/observe-stream.ts +6 -1
- package/src/cli/observe.ts +10 -2
- package/src/cli/provider.ts +12 -0
- package/src/cli/registry.ts +20 -7
- package/src/cli/restart-scope.ts +3 -1
- package/src/cli/runtime-api.ts +27 -2
- package/src/cli/status.ts +104 -20
- package/src/client/connect.ts +39 -4
- package/src/client/hub-client.ts +14 -7
- package/src/client/hub-state.ts +25 -14
- package/src/client/link-home.ts +248 -0
- package/src/client/link-management-relay.ts +61 -0
- package/src/client/link-relay.ts +6 -1
- package/src/client/link-status.ts +14 -0
- package/src/client/machine-api.ts +14 -7
- package/src/client/machine-listener.ts +29 -4
- package/src/clients/config-export/contracts.ts +1 -0
- 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 +59 -16
- package/src/clients/omo-role-models.ts +6 -3
- package/src/codex/agent-role-models.ts +26 -8
- package/src/codex/auth-api/account-list.ts +17 -2
- package/src/codex/auth-api/account-selection.ts +17 -7
- package/src/codex/auth-api/pool-mode-gate.ts +5 -2
- package/src/codex/auth-api/routes.ts +5 -1
- package/src/codex/auth-context.ts +79 -5
- package/src/codex/autostart-health.ts +7 -1
- package/src/codex/catalog/build-entries.ts +11 -2
- package/src/codex/catalog/bundled.ts +5 -1
- package/src/codex/catalog/configured-tiers.ts +50 -0
- package/src/codex/catalog/effort.ts +6 -3
- package/src/codex/catalog/metadata.ts +4 -2
- package/src/codex/catalog/model-hints.ts +21 -3
- package/src/codex/catalog/native-models.ts +16 -2
- package/src/codex/catalog/native-ultrafast.ts +106 -0
- package/src/codex/catalog/parsing.ts +5 -0
- package/src/codex/catalog/provider-models.ts +4 -0
- package/src/codex/catalog/retained-sync.ts +2 -0
- package/src/codex/catalog/routed-gather.ts +22 -4
- package/src/codex/catalog/vendor-context.ts +82 -0
- package/src/codex/config-write-lock.ts +40 -2
- package/src/codex/convergence.ts +2 -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 +4 -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 +55 -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 +173 -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 +59 -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/history-provider.ts +1 -1
- package/src/codex/inject/config-write-section.ts +4 -1
- package/src/codex/inject/remove.ts +18 -6
- package/src/codex/journal.ts +16 -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 +4 -1
- package/src/codex/prompt-journal.ts +20 -2
- package/src/codex/prompt-layers/encoding.ts +49 -0
- package/src/codex/prompt-layers/import-source.ts +30 -0
- package/src/codex/prompt-layers/toml-edit.ts +269 -37
- package/src/codex/prompt-layers/toml-read.ts +52 -4
- package/src/codex/prompt-layers.ts +383 -20
- package/src/codex/prompt-lock-claim.ts +13 -3
- package/src/codex/prompt-lock-evidence.ts +3 -3
- package/src/codex/prompt-lock-owner.ts +14 -6
- package/src/codex/prompt-text-probe.ts +34 -5
- package/src/codex/quota.ts +13 -6
- package/src/codex/role-auto-assign.ts +2 -5
- package/src/codex/routing/active-account.ts +21 -0
- package/src/codex/routing/health-store.ts +1 -1
- package/src/codex/routing/pool-recovery.ts +79 -0
- package/src/codex/routing.ts +1 -1
- package/src/codex/runtime.ts +196 -8
- package/src/codex/shim-diagnostics.ts +6 -0
- package/src/codex/shim-overlay.ts +15 -6
- package/src/codex/shim.ts +1 -1
- package/src/combos/failover.ts +3 -0
- package/src/config/api-key-reconcile.ts +34 -0
- package/src/config/diagnostics.ts +18 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/live-reconcile.ts +56 -29
- package/src/config/live-topology.ts +29 -0
- package/src/config/load-degrade.ts +8 -2
- package/src/config/paths.ts +13 -3
- package/src/config/salvage.ts +3 -1
- package/src/config/schema/config-schema.ts +19 -0
- package/src/config/schema/desktop-compatibility.ts +9 -0
- package/src/config/schema/leaf-validators.ts +1 -0
- package/src/generated/compatibility-version.json +713 -285
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/grpc-web.ts +23 -1
- package/src/grok/reset-coupon-ledger.ts +75 -5
- package/src/grok/reset-coupons.ts +4 -2
- 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 +14 -7
- package/src/integrations/registry.ts +52 -11
- package/src/integrations/state.ts +19 -7
- package/src/integrations/store.ts +3 -0
- package/src/integrations/target.ts +42 -5
- package/src/integrations/writer.ts +24 -7
- package/src/lib/advisor-activation.ts +16 -0
- package/src/lib/app-owned-memory-stores.ts +4 -0
- package/src/lib/app-owned-memory.ts +11 -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/desktop-proxy-route.ts +30 -0
- package/src/lib/desktop-upstream-tunnel.ts +107 -0
- package/src/lib/errors.ts +7 -0
- package/src/lib/json-byte-size.ts +31 -0
- package/src/lib/local-internal-call-capability.ts +55 -0
- package/src/lib/optional-shutdown-hooks.ts +35 -0
- package/src/lib/outbound-credential-registry.ts +210 -0
- package/src/lib/package-tree-integrity.ts +2 -2
- package/src/lib/passive-read.ts +35 -0
- package/src/lib/shadow-call.ts +44 -5
- 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/link/codex-settings.ts +155 -0
- package/src/link/management-routes.ts +89 -0
- package/src/link/remote-probe.ts +38 -0
- package/src/link/ssh-argv.ts +26 -5
- package/src/link/ssh-runner.ts +58 -27
- package/src/link/status-projection.ts +22 -4
- package/src/link/supervisor.ts +144 -49
- package/src/link/tunnel-state.ts +11 -4
- package/src/oauth/anthropic-continuity.ts +4 -3
- package/src/oauth/anthropic-identity.ts +13 -2
- package/src/oauth/anthropic-model-quota.ts +13 -1
- package/src/oauth/anthropic-routing.ts +230 -23
- package/src/oauth/anthropic.ts +18 -4
- package/src/oauth/generic-account-failover.ts +29 -5
- package/src/oauth/google-antigravity.ts +24 -7
- package/src/oauth/index.ts +28 -17
- package/src/oauth/kiro-account-identity.ts +106 -0
- package/src/oauth/kiro-device-login.ts +2 -1
- package/src/oauth/kiro.ts +55 -6
- package/src/oauth/plan.ts +29 -0
- package/src/oauth/store.ts +40 -3
- package/src/oauth/types.ts +2 -0
- package/src/plugins/loader.ts +11 -0
- 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/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/settings.ts +8 -0
- package/src/providers/anthropic-billing-ownership.ts +74 -0
- package/src/providers/anthropic-billing-recovery.ts +31 -0
- package/src/providers/anthropic-billing-refusal.ts +55 -0
- package/src/providers/anthropic-reset-admission.ts +62 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +28 -6
- package/src/providers/antigravity-account-roster.ts +102 -0
- package/src/providers/antigravity-models.ts +85 -7
- package/src/providers/api-key-resolve.ts +17 -2
- package/src/providers/key-failover.ts +52 -5
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/model-rename-migration.ts +5 -3
- package/src/providers/model-rename-startup.ts +5 -3
- package/src/providers/quota/account-cache.ts +11 -0
- package/src/providers/quota/anthropic-account-quota.ts +4 -2
- package/src/providers/quota/report-cache.ts +14 -0
- package/src/providers/quota/vendor-probes-oauth.ts +3 -3
- package/src/providers/quota.ts +4 -1
- package/src/providers/registry/entries-core.ts +21 -20
- package/src/providers/registry/entries-extended.ts +19 -1
- package/src/providers/registry/model-seeds.ts +17 -0
- package/src/providers/request-pacing.ts +9 -0
- package/src/providers/stale-no-vision-seed-migration.ts +72 -0
- package/src/responses/compaction.ts +14 -0
- package/src/responses/parser.ts +37 -20
- package/src/responses/state.ts +32 -29
- package/src/routing/history/build-file.ts +92 -0
- package/src/routing/history/indexer.ts +248 -216
- package/src/routing/history/ingest.ts +238 -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/server/admission-model-scope.ts +4 -1
- package/src/server/antigravity-direct-eligibility.ts +208 -0
- package/src/server/auth-cors.ts +46 -0
- package/src/server/chat-completions.ts +75 -9
- package/src/server/claude-messages.ts +110 -20
- package/src/server/configured-credentials.ts +194 -0
- package/src/server/index/desktop-compatibility-startup.ts +56 -0
- package/src/server/index/link-management.ts +58 -0
- package/src/server/index/optional-listeners.ts +5 -1
- package/src/server/index/serve-options.ts +15 -2
- package/src/server/index/startup-warnings.ts +9 -0
- package/src/server/index/websocket-handler.ts +9 -0
- package/src/server/index.ts +13 -10
- package/src/server/inference/client-encoder-delivery.ts +6 -5
- package/src/server/lifecycle.ts +2 -1
- package/src/server/login-shell-path.ts +146 -0
- package/src/server/management/advisor-routes.ts +191 -0
- package/src/server/management/agent-settings-routes.ts +50 -14
- package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
- package/src/server/management/codex-agent-role-routes.ts +19 -6
- package/src/server/management/codex-prompt-routes.ts +136 -5
- package/src/server/management/codex-role-auto-assign.ts +1 -1
- package/src/server/management/companion-routes.ts +4 -0
- package/src/server/management/config-routes.ts +2 -2
- package/src/server/management/context.ts +4 -0
- 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/grok-coupon-routes.ts +78 -27
- package/src/server/management/link-enrollment.ts +192 -0
- package/src/server/management/link-route-state.ts +185 -0
- package/src/server/management/link-routes.ts +82 -402
- package/src/server/management/model-routes.ts +2 -2
- package/src/server/management/model-rows.ts +3 -1
- package/src/server/management/protocol-settings-patch.ts +1 -0
- package/src/server/management/provider-overwrite-carry.ts +10 -0
- package/src/server/management/provider-routes.ts +1 -0
- package/src/server/management/route-registry.ts +16 -3
- package/src/server/management/shared.ts +3 -0
- 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.ts +73 -43
- package/src/server/relay-eager.ts +2 -0
- package/src/server/request-log-filter.ts +6 -1
- package/src/server/request-log-terminal-status.ts +63 -6
- package/src/server/request-log.ts +7 -0
- package/src/server/responses/adapter-continuation.ts +35 -18
- package/src/server/responses/adapter-delivery.ts +24 -2
- package/src/server/responses/adapter-dispatch.ts +261 -81
- package/src/server/responses/advisor-plan-slot.ts +27 -0
- package/src/server/responses/advisor-slot.ts +336 -0
- package/src/server/responses/code-mode-collab-surface.ts +313 -0
- package/src/server/responses/codex-ws-exchange.ts +147 -6
- package/src/server/responses/codex-ws-wire.ts +203 -0
- package/src/server/responses/collaboration.ts +36 -7
- package/src/server/responses/compact.ts +126 -48
- package/src/server/responses/context-overflow.ts +13 -1
- package/src/server/responses/core-codex-account.ts +105 -1
- package/src/server/responses/core-combo-failure.ts +24 -1
- package/src/server/responses/core-combo.ts +28 -2
- package/src/server/responses/core-errors.ts +29 -1
- package/src/server/responses/core-lifetime.ts +2 -0
- package/src/server/responses/core-normalize.ts +11 -0
- package/src/server/responses/core-options.ts +48 -0
- package/src/server/responses/core-replay.ts +7 -4
- package/src/server/responses/core.ts +1 -1
- package/src/server/responses/deferred-collab-surface.ts +158 -0
- package/src/server/responses/deferred-send-reporter.ts +25 -0
- package/src/server/responses/fetch-helpers.ts +12 -1
- package/src/server/responses/passthrough-delivery.ts +20 -11
- package/src/server/responses/passthrough-dispatch.ts +29 -7
- package/src/server/responses/request-prepare.ts +74 -9
- package/src/server/responses/request-transport.ts +22 -8
- package/src/server/responses/run-turn-execution.ts +70 -8
- package/src/server/responses/sidecar-execution.ts +89 -5
- package/src/server/responses/terminal-guard.ts +2 -1
- package/src/server/responses/ws-upstream.ts +9 -1
- package/src/server/responses-undeclared-tool-guard.ts +30 -9
- package/src/server/startup-action-control.ts +38 -4
- package/src/server/startup-health-cache.ts +5 -0
- package/src/server/system-env-shell.ts +7 -2
- package/src/server/system-env.ts +11 -1
- package/src/service/cli.ts +5 -1
- package/src/service/desktop-startup.ts +161 -34
- package/src/service/desktop-supervision.d.mts +27 -1
- package/src/service/desktop-supervision.mjs +111 -5
- package/src/service/health-sentinel-launch.ts +176 -0
- package/src/service/health-sentinel-main.ts +466 -0
- package/src/service/health-sentinel-policy.ts +241 -0
- package/src/service/health-sentinel-probe.ts +71 -0
- package/src/service/health-sentinel-windows.ts +87 -0
- package/src/service/linux-linger.ts +20 -0
- package/src/service/logging.ts +72 -0
- package/src/tray/windows-tray.ps1 +43 -24
- package/src/types/config.ts +68 -6
- package/src/types/provider.ts +7 -0
- package/src/types/request.ts +15 -0
- package/src/types/tools.ts +2 -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/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/expected-prices.ts +16 -0
- package/src/usage/log.ts +19 -1
- package/src/web-search/gemini-executor.ts +2 -2
- package/assets/sponsors/packycode.png +0 -0
- package/gui/dist/assets/App-Ba8uG696.js +0 -54
- package/gui/dist/assets/Tray-IImbNPRy.js +0 -1
- package/gui/dist/assets/index-BHRZdJuv.js +0 -94
- package/gui/dist/assets/quota-summary-DiJO6sYY.js +0 -1
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credentials this installation is configured with, read when an upstream diagnostic is checked.
|
|
3
|
+
*
|
|
4
|
+
* A gateway can echo a credential it learned before this process ever sent it: an inactive pool
|
|
5
|
+
* key, another provider's key, an OAuth token from an earlier session. Learning only from sends
|
|
6
|
+
* would let such an echo be warned and finalized before the value is first dispatched, and a warning
|
|
7
|
+
* cannot be retracted. So the request log also refuses any diagnostic that repeats a configured
|
|
8
|
+
* value: every provider's apiKey and apiKeyPool keys (keychain and env references resolved), every
|
|
9
|
+
* configured header value, every credential-named provider field, and every token in the OAuth
|
|
10
|
+
* store. The set is rebuilt when the bytes of config.json or auth.json change, a stored keychain
|
|
11
|
+
* secret is replaced in this process, or a referenced environment variable changes.
|
|
12
|
+
*
|
|
13
|
+
* Freshness is decided by content, never by file metadata. Every check reads both stores and
|
|
14
|
+
* compares a digest of the bytes it read; the rebuild parses those same bytes. An inode/size/mtime
|
|
15
|
+
* key missed a same-length rewrite inside one timestamp tick (coarse on Windows) and any writer that
|
|
16
|
+
* preserves or backdates mtime, and then kept serving the previous, complete-looking view (#6923).
|
|
17
|
+
* The cost is two small reads per check; checks run only for retained upstream diagnostics.
|
|
18
|
+
*
|
|
19
|
+
* Reads are observe-only: no hardening, backup, repair or warning. A file that exists but cannot be
|
|
20
|
+
* read, parsed or recognized as the expected shape, or a keychain reference that cannot be resolved,
|
|
21
|
+
* makes coverage incomplete and callers fail closed; an incomplete set is retried after a short delay
|
|
22
|
+
* so a keychain that becomes available restores diagnostics. Values are held in memory only and
|
|
23
|
+
* never serialized or exposed.
|
|
24
|
+
*
|
|
25
|
+
* Not covered: keychain secrets are cached per process (`readKeychain` in
|
|
26
|
+
* src/providers/api-key-resolve.ts), so a secret replaced by another process is seen only after
|
|
27
|
+
* this process re-resolves it.
|
|
28
|
+
*/
|
|
29
|
+
import { createHash } from "node:crypto";
|
|
30
|
+
import { readFileSync, statSync } from "node:fs";
|
|
31
|
+
import { join } from "node:path";
|
|
32
|
+
import { getConfigDir } from "../config/paths";
|
|
33
|
+
import { resolveEnvValue } from "../config/proxy-env";
|
|
34
|
+
import { isKeychainReference, providerKeyResolutionGeneration, resolveProviderApiKeyQuietly } from "../providers/api-key-resolve";
|
|
35
|
+
import { credentialComponents, credentialSetMatches } from "../lib/outbound-credential-registry";
|
|
36
|
+
import { SENSITIVE_KEY_PATTERN } from "../lib/redact";
|
|
37
|
+
|
|
38
|
+
const CREDENTIAL_FIELD = /(?:key|token|secret|password|passwd|credential|cookie|authorization)$/i;
|
|
39
|
+
/** How long an incomplete set is trusted before its sources are read again. */
|
|
40
|
+
export const INCOMPLETE_RETRY_MS = 30_000;
|
|
41
|
+
|
|
42
|
+
export interface ConfiguredCredentials {
|
|
43
|
+
readonly complete: boolean;
|
|
44
|
+
matches(value: string): boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface Snapshot {
|
|
48
|
+
/** The only object handed out: cache metadata (including env values) stays private here. */
|
|
49
|
+
readonly view: ConfiguredCredentials;
|
|
50
|
+
readonly signature: string;
|
|
51
|
+
readonly envNames: readonly string[];
|
|
52
|
+
readonly envFingerprint: string;
|
|
53
|
+
readonly builtAt: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let cached: Snapshot | undefined;
|
|
57
|
+
|
|
58
|
+
/** One read of a store: its state for the signature and, for a readable file, the bytes read. */
|
|
59
|
+
interface StoreRead {
|
|
60
|
+
/** "absent", "not-a-file", "unreadable", or "sha256:<hex>" of exactly the bytes in `text`. */
|
|
61
|
+
readonly state: string;
|
|
62
|
+
readonly text?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function readStore(path: string): StoreRead {
|
|
66
|
+
try {
|
|
67
|
+
if (!statSync(path).isFile()) return { state: "not-a-file" };
|
|
68
|
+
} catch (error) {
|
|
69
|
+
// Only a store that is not there at all counts as intentionally absent.
|
|
70
|
+
return { state: (error as NodeJS.ErrnoException)?.code === "ENOENT" ? "absent" : "unreadable" };
|
|
71
|
+
}
|
|
72
|
+
let bytes: Buffer;
|
|
73
|
+
try {
|
|
74
|
+
bytes = readFileSync(path);
|
|
75
|
+
} catch {
|
|
76
|
+
// Present a moment ago, gone or unreadable now (a replacement in progress): fail closed.
|
|
77
|
+
return { state: "unreadable" };
|
|
78
|
+
}
|
|
79
|
+
return { state: `sha256:${createHash("sha256").update(bytes).digest("hex")}`, text: bytes.toString("utf8") };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function parseStore(read: StoreRead): { ok: boolean; value?: unknown } {
|
|
83
|
+
if (read.state === "absent") return { ok: true };
|
|
84
|
+
if (read.text === undefined) return { ok: false };
|
|
85
|
+
try {
|
|
86
|
+
return { ok: true, value: JSON.parse(read.text.replace(/^\uFEFF/, "")) };
|
|
87
|
+
} catch {
|
|
88
|
+
return { ok: false };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
93
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function envFingerprint(names: readonly string[]): string {
|
|
97
|
+
return names.map(name => process.env[name] ?? "\u0000unset").join("\u0000");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Mirrors resolveEnvValue: a braced or bare dollar reference reads the environment. */
|
|
101
|
+
function envReference(raw: string): string | undefined {
|
|
102
|
+
if (!raw.startsWith("$")) return undefined;
|
|
103
|
+
return /^\$\{(\w+)\}$/.exec(raw)?.[1] ?? raw.slice(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function build(configRead: StoreRead, authRead: StoreRead, signature: string, now: number): Snapshot {
|
|
107
|
+
const values = new Set<string>();
|
|
108
|
+
const envNames = new Set<string>();
|
|
109
|
+
let complete = true;
|
|
110
|
+
const add = (raw: string | undefined) => {
|
|
111
|
+
if (raw) for (const part of credentialComponents(raw)) values.add(part);
|
|
112
|
+
};
|
|
113
|
+
const noteEnv = (raw: string) => {
|
|
114
|
+
const name = envReference(raw);
|
|
115
|
+
if (name) envNames.add(name);
|
|
116
|
+
};
|
|
117
|
+
const addReference = (raw: unknown) => {
|
|
118
|
+
if (typeof raw !== "string" || !raw) return;
|
|
119
|
+
noteEnv(raw);
|
|
120
|
+
const resolved = resolveProviderApiKeyQuietly(raw);
|
|
121
|
+
if (resolved === undefined && isKeychainReference(raw)) complete = false;
|
|
122
|
+
add(resolved);
|
|
123
|
+
};
|
|
124
|
+
const isCredentialField = (field: string) => CREDENTIAL_FIELD.test(field) || SENSITIVE_KEY_PATTERN.test(field);
|
|
125
|
+
const walkProvider = (node: unknown, depth: number) => {
|
|
126
|
+
if (depth > 8) return;
|
|
127
|
+
if (Array.isArray(node)) { for (const item of node) walkProvider(item, depth + 1); return; }
|
|
128
|
+
if (!isRecord(node)) return;
|
|
129
|
+
for (const [field, value] of Object.entries(node)) {
|
|
130
|
+
if (field === "headers" && isRecord(value)) {
|
|
131
|
+
for (const header of Object.values(value)) {
|
|
132
|
+
if (typeof header !== "string") continue;
|
|
133
|
+
noteEnv(header);
|
|
134
|
+
add(resolveEnvValue(header) ?? header);
|
|
135
|
+
}
|
|
136
|
+
} else if (typeof value === "string") {
|
|
137
|
+
if (isCredentialField(field)) addReference(value);
|
|
138
|
+
} else {
|
|
139
|
+
walkProvider(value, depth + 1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
const walkAuth = (node: unknown, depth: number) => {
|
|
144
|
+
if (depth > 8) return;
|
|
145
|
+
if (Array.isArray(node)) { for (const item of node) walkAuth(item, depth + 1); return; }
|
|
146
|
+
if (!isRecord(node)) return;
|
|
147
|
+
for (const [field, value] of Object.entries(node)) {
|
|
148
|
+
if (typeof value === "string") {
|
|
149
|
+
if (field === "access" || field === "refresh" || isCredentialField(field)) add(value);
|
|
150
|
+
} else {
|
|
151
|
+
walkAuth(value, depth + 1);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const configSig = configRead.state;
|
|
157
|
+
const authSig = authRead.state;
|
|
158
|
+
const config = parseStore(configRead);
|
|
159
|
+
const auth = parseStore(authRead);
|
|
160
|
+
if (!config.ok || !auth.ok) complete = false;
|
|
161
|
+
// An existing file must have the shape its loader expects; anything else hides credentials.
|
|
162
|
+
if (configSig !== "absent" && config.ok && (!isRecord(config.value)
|
|
163
|
+
|| (config.value.providers !== undefined && !isRecord(config.value.providers)))) complete = false;
|
|
164
|
+
if (authSig !== "absent" && auth.ok && !isRecord(auth.value)) complete = false;
|
|
165
|
+
if (isRecord(config.value) && isRecord(config.value.providers)) walkProvider(config.value.providers, 0);
|
|
166
|
+
if (isRecord(auth.value)) walkAuth(auth.value, 0);
|
|
167
|
+
|
|
168
|
+
const names = [...envNames].sort();
|
|
169
|
+
const view: ConfiguredCredentials = Object.freeze({
|
|
170
|
+
complete,
|
|
171
|
+
matches: (value: string) => credentialSetMatches(values, candidate => values.has(candidate), value),
|
|
172
|
+
});
|
|
173
|
+
return {
|
|
174
|
+
view,
|
|
175
|
+
signature,
|
|
176
|
+
envNames: names,
|
|
177
|
+
envFingerprint: envFingerprint(names),
|
|
178
|
+
builtAt: now,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** The configured credential set for the active config home, rebuilt when any input changes. */
|
|
183
|
+
export function configuredCredentials(now = Date.now()): ConfiguredCredentials {
|
|
184
|
+
const dir = getConfigDir();
|
|
185
|
+
const configRead = readStore(join(dir, "config.json"));
|
|
186
|
+
const authRead = readStore(join(dir, "auth.json"));
|
|
187
|
+
const signature = `${configRead.state}|${authRead.state}|${providerKeyResolutionGeneration()}|${dir}`;
|
|
188
|
+
const stale = !cached
|
|
189
|
+
|| cached.signature !== signature
|
|
190
|
+
|| cached.envFingerprint !== envFingerprint(cached.envNames)
|
|
191
|
+
|| (!cached.view.complete && now - cached.builtAt >= INCOMPLETE_RETRY_MS);
|
|
192
|
+
if (stale) cached = build(configRead, authRead, signature, now);
|
|
193
|
+
return cached!.view;
|
|
194
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { OcxConfig } from "../../types";
|
|
2
|
+
import { siblingOfLivePort } from "../../codex/sibling-start";
|
|
3
|
+
import { isTestHomeGuardArmed } from "../../lib/test-home-guard";
|
|
4
|
+
import type { DesktopCompatibilityRuntime } from "../../codex/desktop-compatibility/runtime";
|
|
5
|
+
import { bindNativeCompatibilityOwner } from "../../codex/desktop-compatibility/routing-binding";
|
|
6
|
+
import type { ReadinessGate } from "../readiness";
|
|
7
|
+
|
|
8
|
+
type RuntimeModule = { getDesktopCompatibilityRuntime(): DesktopCompatibilityRuntime; shutdownDesktopCompatibility(): Promise<void> };
|
|
9
|
+
interface StartupIo {
|
|
10
|
+
platform?: string;
|
|
11
|
+
testGuard?: boolean;
|
|
12
|
+
sibling?: boolean;
|
|
13
|
+
load?: () => Promise<RuntimeModule>;
|
|
14
|
+
warn?: (message: string) => void;
|
|
15
|
+
boundPort?: number;
|
|
16
|
+
boundHostname?: string;
|
|
17
|
+
loopbackPort?: number;
|
|
18
|
+
readiness?: Pick<ReadinessGate, "getStatus">;
|
|
19
|
+
readinessTimeoutMs?: number;
|
|
20
|
+
}
|
|
21
|
+
/** Core-safe gate: off installs do not load the optional runtime, read credentials or start timers. */
|
|
22
|
+
export function scheduleDesktopCompatibilityStartup(config: OcxConfig, io: StartupIo = {}): { shutdown(): Promise<void> } {
|
|
23
|
+
let stopped = false, module: RuntimeModule | undefined;
|
|
24
|
+
let cancelReadiness: (() => void) | undefined;
|
|
25
|
+
const waitForReadiness = () => !io.readiness ? Promise.resolve(true) : new Promise<boolean>(resolve => {
|
|
26
|
+
const deadline = Date.now() + (io.readinessTimeoutMs ?? 120000);
|
|
27
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
28
|
+
const finish = (ready: boolean) => { if (timer) clearTimeout(timer); cancelReadiness = undefined; resolve(ready); };
|
|
29
|
+
cancelReadiness = () => finish(false);
|
|
30
|
+
const check = () => {
|
|
31
|
+
const status = io.readiness!.getStatus();
|
|
32
|
+
if (stopped || status !== "pending" || Date.now() >= deadline) { finish(!stopped && status === "ready"); return; }
|
|
33
|
+
timer = setTimeout(check, 100); timer.unref();
|
|
34
|
+
};
|
|
35
|
+
check();
|
|
36
|
+
});
|
|
37
|
+
const unbind = io.boundPort === undefined || io.boundHostname === undefined ? () => {} : bindNativeCompatibilityOwner({ config, hostname: io.boundHostname, port: io.boundPort, loopbackPort: io.loopbackPort });
|
|
38
|
+
const enabled = config.desktopCompatibility?.startOnProxyStart === true && (io.platform ?? process.platform) === "win32"
|
|
39
|
+
&& !(io.testGuard ?? isTestHomeGuardArmed()) && !(io.sibling ?? siblingOfLivePort() !== null)
|
|
40
|
+
&& config.runtimeRole !== "client";
|
|
41
|
+
const pending = enabled ? Promise.resolve().then(async () => {
|
|
42
|
+
if (stopped) return;
|
|
43
|
+
if (io.readiness) {
|
|
44
|
+
const ready = await waitForReadiness();
|
|
45
|
+
if (stopped) return;
|
|
46
|
+
if (!ready) throw new Error("desktop_compatibility_startup_not_ready");
|
|
47
|
+
}
|
|
48
|
+
module = await (io.load?.() ?? import("../../codex/desktop-compatibility/service"));
|
|
49
|
+
if (stopped) return;
|
|
50
|
+
await module.getDesktopCompatibilityRuntime().start();
|
|
51
|
+
}).catch(() => { (io.warn ?? console.warn)("Desktop compatibility observation did not start. Inspect Desktop compatibility status; no automatic trust or correction was applied."); }) : Promise.resolve();
|
|
52
|
+
return { async shutdown() {
|
|
53
|
+
stopped = true; cancelReadiness?.(); unbind(); await pending;
|
|
54
|
+
if (module) await module.shutdownDesktopCompatibility();
|
|
55
|
+
} };
|
|
56
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Home end of the Remote Link management relay (#6957).
|
|
3
|
+
*
|
|
4
|
+
* Reached only on the hub-link listener, after the request has passed its link-key admission
|
|
5
|
+
* (`resolveApiAuth` restricted to recorded link keys) and the shared route table in
|
|
6
|
+
* `src/link/management-routes.ts`. The management handler sees a fresh request built here: no
|
|
7
|
+
* caller header, cookie, Origin or credential crosses from the link, and the principal is
|
|
8
|
+
* absent, so no consent- or capability-gated branch can be reached. Only allowlisted reads run.
|
|
9
|
+
* They run as passive reads (`src/lib/passive-read.ts`): no discovery, config write or diagnostic (#6959).
|
|
10
|
+
*/
|
|
11
|
+
import type { OcxConfig } from "../../types";
|
|
12
|
+
import { handleManagementAPI, type ManagementApiDeps } from "../management-api";
|
|
13
|
+
import type { ManagementSessionControl } from "../management-auth";
|
|
14
|
+
import { runPassiveRead } from "../../lib/passive-read";
|
|
15
|
+
import { LINK_CODEX_SETTINGS_PATH, linkManagementRouteAllowed, linkManagementTarget } from "../../link/management-routes";
|
|
16
|
+
import { projectHomeCodexSettings } from "../../link/codex-settings";
|
|
17
|
+
import { packageVersion } from "../../lib/package-version";
|
|
18
|
+
|
|
19
|
+
const VERSION = packageVersion("0.0.0");
|
|
20
|
+
const KEPT_RESPONSE_HEADERS = ["content-type", "content-language", "etag", "last-modified"];
|
|
21
|
+
|
|
22
|
+
function notFound(): Response {
|
|
23
|
+
return Response.json({ error: "not_found" }, { status: 404, headers: { "cache-control": "no-store" } });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function handleLinkManagementRequest(
|
|
27
|
+
req: Request,
|
|
28
|
+
url: URL,
|
|
29
|
+
config: OcxConfig,
|
|
30
|
+
deps: ManagementApiDeps,
|
|
31
|
+
sessionControl?: ManagementSessionControl,
|
|
32
|
+
): Promise<Response> {
|
|
33
|
+
if (!linkManagementRouteAllowed(url, req)) return notFound();
|
|
34
|
+
if (url.pathname === LINK_CODEX_SETTINGS_PATH) {
|
|
35
|
+
return Response.json(projectHomeCodexSettings(config, VERSION), { headers: { "cache-control": "no-store" } });
|
|
36
|
+
}
|
|
37
|
+
const target = linkManagementTarget(url);
|
|
38
|
+
if (!target) return notFound();
|
|
39
|
+
const local = new URL(`${target.pathname}${target.search}`, "http://127.0.0.1");
|
|
40
|
+
// The handlers branch on GET; an admitted HEAD runs as GET and its body is dropped below.
|
|
41
|
+
const inner = new Request(local.href, {
|
|
42
|
+
method: "GET",
|
|
43
|
+
headers: { accept: "application/json", host: "127.0.0.1" },
|
|
44
|
+
});
|
|
45
|
+
const response = await runPassiveRead(() =>
|
|
46
|
+
handleManagementAPI(inner, local, config, deps, undefined, sessionControl, { trustedLoopback: false }));
|
|
47
|
+
if (!response) return notFound();
|
|
48
|
+
const headers = new Headers({ "cache-control": "no-store" });
|
|
49
|
+
for (const name of KEPT_RESPONSE_HEADERS) {
|
|
50
|
+
const value = response.headers.get(name);
|
|
51
|
+
if (value) headers.set(name, value);
|
|
52
|
+
}
|
|
53
|
+
if (req.method === "HEAD") {
|
|
54
|
+
try { await response.body?.cancel(); } catch { /* best effort */ }
|
|
55
|
+
return new Response(null, { status: response.status, headers });
|
|
56
|
+
}
|
|
57
|
+
return new Response(response.body, { status: response.status, headers });
|
|
58
|
+
}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
type LinkListenerStatus,
|
|
15
15
|
} from "./link-listener";
|
|
16
16
|
import { createLinkSupervisor, type LinkSupervisor } from "../../link/supervisor";
|
|
17
|
+
import { linkManagementRouteAllowed } from "../../link/management-routes";
|
|
17
18
|
import { saveConfigPreservingClaudeCode } from "../../config/live-reconcile";
|
|
18
19
|
import { reconcileLiveStateStores } from "../../lib/state-store-registrations";
|
|
19
20
|
export { LINK_INGRESS_HOSTNAME } from "./link-listener";
|
|
@@ -77,7 +78,10 @@ export function createOptionalListenerSet<T>(linkDeps: LinkListenerDeps = {}): O
|
|
|
77
78
|
if (claudeIntercept.ownsListener(server)) return "claude-intercept";
|
|
78
79
|
return undefined;
|
|
79
80
|
},
|
|
80
|
-
|
|
81
|
+
// The Home's link listener admits the data plane plus the read-only management relay
|
|
82
|
+
// (#6957). The Child's own ingress keeps the data-only table, so a local caller on the Child
|
|
83
|
+
// cannot reach the Home's management reads without the Child's dashboard session.
|
|
84
|
+
linkRouteAllowed: (url, req) => linkRouteAllowed(url, req) || linkManagementRouteAllowed(url, req),
|
|
81
85
|
linkAdmissionKeyIds: () => linkListener.linkAdmissionKeyIds(),
|
|
82
86
|
onAuthenticatedCatalog: listener => linkListener.onAuthenticatedCatalog(listener),
|
|
83
87
|
notifyAuthenticatedCatalog: apiKeyId => linkListener.notifyAuthenticatedCatalog(apiKeyId),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Server, ServerWebSocket } from "bun";
|
|
2
2
|
import type { StartServerDeps } from "./startup-warnings";
|
|
3
|
+
import { isLinkManagementPath } from "../../link/management-routes";
|
|
3
4
|
import {
|
|
4
5
|
GUI_PAIRING_EXCHANGE_BODY_LIMIT,
|
|
5
6
|
REMOTE_WORKSPACE_PAIRING_BODY_LIMIT,
|
|
@@ -96,6 +97,7 @@ import {
|
|
|
96
97
|
isAllowedManagementOrigin,
|
|
97
98
|
isApiAuthRequired,
|
|
98
99
|
isLoopbackHostname,
|
|
100
|
+
markTrustedHubManagementRequest,
|
|
99
101
|
jsonResponse,
|
|
100
102
|
admissionFields,
|
|
101
103
|
resolveApiAuth,
|
|
@@ -313,6 +315,11 @@ export function createServeOptions(ctx: ServeOptionsContext) {
|
|
|
313
315
|
maxRequestBodySize: inboundBodyLimitBytes,
|
|
314
316
|
async fetch(req: Request, requestServer: Server<WsData>): Promise<Response> {
|
|
315
317
|
const ingress = ingressForServer(requestServer);
|
|
318
|
+
// Bind management-origin trust to the actual Bun listener object resolved above.
|
|
319
|
+
// Neither Host nor any X-Forwarded-* header can manufacture this process-local mark.
|
|
320
|
+
if (ingress === "hub-management" && config.runtimeRole === "hub") {
|
|
321
|
+
markTrustedHubManagementRequest(req);
|
|
322
|
+
}
|
|
316
323
|
const requestUrl = codexCompatibleUrl(req.url);
|
|
317
324
|
const linkPolicyView = ingress === "hub-link" ? linkPolicy() : undefined;
|
|
318
325
|
// The unauthenticated loopback listener (#1102) serves a fixed allowlist and nothing
|
|
@@ -367,6 +374,12 @@ export function createServeOptions(ctx: ServeOptionsContext) {
|
|
|
367
374
|
if (requestUrl.pathname === "/v1/catalog" && requestUrl.search === ""
|
|
368
375
|
&& (req.method === "GET" || req.method === "HEAD")
|
|
369
376
|
&& admission.kind === "configured") onAuthenticatedCatalog(admission.keyId);
|
|
377
|
+
// #6957: the Child's read-only view of this Home. Admitted by the link key above and by
|
|
378
|
+
// the shared table in src/link/management-routes.ts; never reachable on another ingress.
|
|
379
|
+
if (isLinkManagementPath(requestUrl.pathname)) {
|
|
380
|
+
const { handleLinkManagementRequest } = await import("./link-management");
|
|
381
|
+
return withCors(await handleLinkManagementRequest(req, requestUrl, config, requestManagementApiDeps, managementSessionControl), req, policy);
|
|
382
|
+
}
|
|
370
383
|
}
|
|
371
384
|
const url = requestUrl;
|
|
372
385
|
const admissionOptions = { linkIngress: policy.linkIngress?.allowedKeyIds };
|
|
@@ -1042,7 +1055,7 @@ export function createServeOptions(ctx: ServeOptionsContext) {
|
|
|
1042
1055
|
);
|
|
1043
1056
|
const { buildAnthropicModelInfos } = await import("../../claude/model-info");
|
|
1044
1057
|
const { resolveAutoContext } = await import("../../claude/context-windows");
|
|
1045
|
-
const {
|
|
1058
|
+
const { resolvableDesktop3pAlias } = await import("../../claude/desktop-3p");
|
|
1046
1059
|
// Per-surface id family (devlog 050): explicit ?ids= wins; otherwise the
|
|
1047
1060
|
// Claude Code CLI discovery UA (`claude-code/<version>`, binary n_()) gets
|
|
1048
1061
|
// readable claude-ocx ids and every other client (Desktop 3P) keeps the
|
|
@@ -1058,7 +1071,7 @@ export function createServeOptions(ctx: ServeOptionsContext) {
|
|
|
1058
1071
|
goOrdered,
|
|
1059
1072
|
resolveAutoContext(config.claudeCode),
|
|
1060
1073
|
idStyle,
|
|
1061
|
-
|
|
1074
|
+
resolvableDesktop3pAlias,
|
|
1062
1075
|
desktopInputs.nativeContextCap,
|
|
1063
1076
|
config.fastMode,
|
|
1064
1077
|
// Explicit opt-out omits the Fast predicate.
|
|
@@ -277,3 +277,12 @@ export function warnPlaintextV2AgentMessagesStartup(config: { plaintextV2AgentMe
|
|
|
277
277
|
console.warn(" Eligible ChatGPT collaboration calls may carry plaintext message arguments. HTTPS remains encrypted, but task text may be retained in Codex history, selected providers, and local response/debug state.");
|
|
278
278
|
console.warn(" This depends on undocumented ChatGPT and Codex behavior; it does not decrypt existing tasks.");
|
|
279
279
|
}
|
|
280
|
+
|
|
281
|
+
export function logProxyEndpoints(actualPort: number): void {
|
|
282
|
+
console.log(`🚀 opencodex proxy running on http://localhost:${actualPort}`);
|
|
283
|
+
console.log(` POST /v1/responses → provider translation`);
|
|
284
|
+
console.log(` POST /v1/chat/completions → OpenAI-compatible clients`);
|
|
285
|
+
console.log(` GET /healthz → health check`);
|
|
286
|
+
console.log(` GET /api/* → management API`);
|
|
287
|
+
console.log(` GET / → GUI dashboard`);
|
|
288
|
+
}
|
|
@@ -30,7 +30,9 @@ import {
|
|
|
30
30
|
} from "../ws-bridge";
|
|
31
31
|
import {
|
|
32
32
|
CodexAccountCooldownError,
|
|
33
|
+
CodexMainAccountHardLockError,
|
|
33
34
|
cooldownErrorMessage,
|
|
35
|
+
cooldownErrorResponse,
|
|
34
36
|
} from "../../codex/auth-context";
|
|
35
37
|
import { codexAccountNamespaceForModel } from "../../codex/account-namespace-match";
|
|
36
38
|
import {
|
|
@@ -368,6 +370,13 @@ export function createWebsocketHandler(
|
|
|
368
370
|
const accountSelector = typeof payload.model === "string"
|
|
369
371
|
? codexAccountNamespaceForModel(config.codexAccountNamespaces, payload.model)
|
|
370
372
|
: undefined;
|
|
373
|
+
if (err instanceof CodexMainAccountHardLockError) {
|
|
374
|
+
// Same terminal usage-limit body and Retry-After as HTTP (#7046).
|
|
375
|
+
const refusal = cooldownErrorResponse(err, Date.now(), accountSelector);
|
|
376
|
+
const body = await refusal.json() as { error: Record<string, unknown> };
|
|
377
|
+
sendJsonFrame(ws, buildWsErrorFrame(429, body.error, refusal.headers));
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
371
380
|
sendJsonFrame(ws, buildWsErrorFrame(429, {
|
|
372
381
|
type: "rate_limit_error",
|
|
373
382
|
message: cooldownErrorMessage(err, accountSelector),
|
package/src/server/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { remoteWorkspaceEnabled } from "../remote-control/workspace-activation";
|
|
2
|
+
import { scheduleDesktopCompatibilityStartup } from "./index/desktop-compatibility-startup";
|
|
2
3
|
import { AuxiliaryListenerBindError } from "./ports";
|
|
3
4
|
import { runAdmittedBodyWork } from "./inbound-body-admission";
|
|
4
5
|
import {
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
websocketsEnabled,
|
|
25
26
|
} from "../config";
|
|
26
27
|
import { flushConfigDirHardeningAndReaps } from "../config/paths";
|
|
28
|
+
import { trackBackgroundWork } from "../lib/background-work";
|
|
27
29
|
import { migrateStartupSubagentModels } from "./subagent-models-startup";
|
|
28
30
|
import { migrateStartupXaiResponses } from "./xai-responses-startup";
|
|
29
31
|
import { migrateStartupZaiResponses } from "./zai-responses-startup";
|
|
@@ -62,6 +64,7 @@ import {
|
|
|
62
64
|
} from "../lib/app-owned-memory-stores";
|
|
63
65
|
import { acquireServerBackgroundLifecycle } from "./background-lifecycle";
|
|
64
66
|
import { startPackageRefresh, stopPackageRefresh } from "../update/refresh-scheduler";
|
|
67
|
+
import { activateAdvisor } from "../lib/advisor-activation";
|
|
65
68
|
import { activateLab, labActivationRequired } from "../lib/lab-activation";
|
|
66
69
|
import { runOpenAiTierStartupMigration } from "../providers/openai-tier-startup";
|
|
67
70
|
import { runAlibabaRegionStartupMigration } from "../providers/alibaba-region-startup";
|
|
@@ -198,7 +201,7 @@ import { createReadinessGate, type ReadinessGate } from "./readiness";
|
|
|
198
201
|
import { createServeOptions, type ServerIngress } from "./index/serve-options";
|
|
199
202
|
import { createOptionalListenerSet, LINK_INGRESS_HOSTNAME } from "./index/optional-listeners";
|
|
200
203
|
import { createPackageTreeIntegrityGuardForServer } from "./index/package-tree-guard";
|
|
201
|
-
import { inspectStartupOwnership, resolveInboundBodyLimitWithWarning, setStartupCacheInvalidationWrite, warnAgentTaskRecoveryStartup, warnPlaintextV2AgentMessagesStartup, type StartServerDeps } from "./index/startup-warnings";
|
|
204
|
+
import { inspectStartupOwnership, logProxyEndpoints, resolveInboundBodyLimitWithWarning, setStartupCacheInvalidationWrite, warnAgentTaskRecoveryStartup, warnPlaintextV2AgentMessagesStartup, type StartServerDeps } from "./index/startup-warnings";
|
|
202
205
|
import { acquireSpendLedgerServerLifecycle, recordFailedStartRollback, type SpendLedgerServerLifecycle } from "./index/spend-ledger-lifecycle";
|
|
203
206
|
export { waitForFailedStartRollback } from "./index/spend-ledger-lifecycle";
|
|
204
207
|
|
|
@@ -638,11 +641,14 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
|
|
|
638
641
|
let unregisterQuotaAutoRefresh: (() => void) | null = null;
|
|
639
642
|
let remoteWorkspaceStopping = false;
|
|
640
643
|
let remoteWorkspaceShutdown: (() => Promise<void>) | undefined;
|
|
644
|
+
let desktopCompatibilityStartup: ReturnType<typeof scheduleDesktopCompatibilityStartup> | undefined;
|
|
645
|
+
let desktopCompatibilityShutdown: (() => Promise<void>) | undefined;
|
|
641
646
|
const managementApiDeps: ManagementApiDeps = {
|
|
642
647
|
...deps.managementApi,
|
|
643
648
|
listLowQuotaEvents: limit => backgroundLifecycle?.listLowQuotaEvents(limit) ?? [],
|
|
644
649
|
remoteWorkspaceStopping: () => remoteWorkspaceStopping,
|
|
645
650
|
onRemoteWorkspaceShutdown: shutdown => { remoteWorkspaceShutdown = shutdown; }, linkSupervisor: () => optionalListeners.linkSupervisor(), linkListener: () => optionalListeners,
|
|
651
|
+
onDesktopCompatibilityShutdown: shutdown => { desktopCompatibilityShutdown = shutdown; },
|
|
646
652
|
};
|
|
647
653
|
let workspaceRuntimeFlight: Promise<typeof import("../remote-control/workspace-runtime")> | undefined;
|
|
648
654
|
const loadRemoteWorkspaceRuntime = () => {
|
|
@@ -772,6 +778,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
|
|
|
772
778
|
: []),
|
|
773
779
|
() => optionalListeners.stop(),
|
|
774
780
|
async () => { await remoteWorkspaceShutdown?.(); },
|
|
781
|
+
async () => { await desktopCompatibilityStartup?.shutdown(); await desktopCompatibilityShutdown?.(); },
|
|
775
782
|
async () => {
|
|
776
783
|
try {
|
|
777
784
|
userCostOverlayReconciler?.stop();
|
|
@@ -801,13 +808,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
|
|
|
801
808
|
boundPort = actualPort;
|
|
802
809
|
setCorsOrigin(actualPort);
|
|
803
810
|
|
|
804
|
-
|
|
805
|
-
console.log(` POST /v1/responses → provider translation`);
|
|
806
|
-
console.log(` POST /v1/chat/completions → OpenAI-compatible clients`);
|
|
807
|
-
console.log(` GET /healthz → health check`);
|
|
808
|
-
console.log(` GET /api/* → management API`);
|
|
809
|
-
console.log(` GET / → GUI dashboard`);
|
|
810
|
-
|
|
811
|
+
logProxyEndpoints(actualPort);
|
|
811
812
|
if (loopbackServer) {
|
|
812
813
|
// Loud on every start, not once at enable time. An operator who inherits a config, or
|
|
813
814
|
// who forgot, has to be able to see that an unauthenticated surface is live without
|
|
@@ -843,7 +844,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
|
|
|
843
844
|
&& isCanonicalOpenAiForwardProvider(openAiProvider)
|
|
844
845
|
&& providerCodexAccountMode("openai", openAiProvider) === "pool"
|
|
845
846
|
) {
|
|
846
|
-
import("../codex/plan-from-token")
|
|
847
|
+
void trackBackgroundWork("codex-pool-quota-prime-startup", import("../codex/plan-from-token")
|
|
847
848
|
.then(({ reconcileCodexPlansFromTokens }) => {
|
|
848
849
|
try {
|
|
849
850
|
reconcileCodexPlansFromTokens(config);
|
|
@@ -853,7 +854,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
|
|
|
853
854
|
return import("../codex/auth-api");
|
|
854
855
|
})
|
|
855
856
|
.then(({ primeCodexPoolQuotas }) => primeCodexPoolQuotas(config, "startup"))
|
|
856
|
-
.catch(() => {});
|
|
857
|
+
.catch(() => {}));
|
|
857
858
|
}
|
|
858
859
|
|
|
859
860
|
// Opt-in storage policy (default OFF). Never blocks listen; cancellable on shutdown.
|
|
@@ -864,6 +865,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
|
|
|
864
865
|
// startServer returns, in the same turn as Bun.serve, so a policy route can never be
|
|
865
866
|
// evaluated before its evidence provider is registered. That ordering is load-bearing:
|
|
866
867
|
// the subagent-fallback chain routes synchronously and has nowhere to await.
|
|
868
|
+
activateAdvisor(config);
|
|
867
869
|
const labConfigDir = getConfigDir();
|
|
868
870
|
if (labActivationRequired(config, labConfigDir)) {
|
|
869
871
|
activateLab(config, labConfigDir);
|
|
@@ -879,6 +881,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
|
|
|
879
881
|
}
|
|
880
882
|
|
|
881
883
|
startPackageRefresh();
|
|
884
|
+
desktopCompatibilityStartup = scheduleDesktopCompatibilityStartup(config, { boundHostname: bindHost, boundPort: actualPort, loopbackPort: loopbackServer?.port ?? undefined, readiness: deps.readinessGate });
|
|
882
885
|
return server;
|
|
883
886
|
}
|
|
884
887
|
|
|
@@ -111,17 +111,18 @@ export interface ClientEncodedDelivery {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
|
-
* Record the attempt's observed path
|
|
115
|
-
*
|
|
116
|
-
*
|
|
114
|
+
* Record the attempt's observed path. The response reaches the client from the IR without the
|
|
115
|
+
* internal Responses hop. The request side is the bridge's unless the ingress also decoded the
|
|
116
|
+
* conversation straight into the IR (the Antigravity direct lane, `requestLane: "direct"`).
|
|
117
117
|
*/
|
|
118
118
|
function markDirectEncoderPath(logCtx: RequestLogContext, encoder: ClientEncoderOption, adapter: string): void {
|
|
119
119
|
const attempt = logCtx.activeAttempt;
|
|
120
120
|
if (!attempt) return;
|
|
121
121
|
const upstream = upstreamWireForAdapter(attempt.adapter || adapter);
|
|
122
|
+
const lane = encoder.requestLane ?? "bridge";
|
|
122
123
|
markAttemptProtocolPath(attempt, {
|
|
123
|
-
mode: deliveryModeForLane(encoder.protocol,
|
|
124
|
-
requestPath: requestPathForLane(encoder.protocol,
|
|
124
|
+
mode: deliveryModeForLane(encoder.protocol, lane, upstream),
|
|
125
|
+
requestPath: requestPathForLane(encoder.protocol, lane, upstream),
|
|
125
126
|
responsePath: [upstream, "ir", encoder.protocol],
|
|
126
127
|
});
|
|
127
128
|
}
|
package/src/server/lifecycle.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "../storage/policy-job";
|
|
9
9
|
import { abortRestoreTrashJobAsync } from "../storage/restore-job";
|
|
10
10
|
import { stopStorageCleanupScheduler } from "../storage/policy-scheduler";
|
|
11
|
-
import { runOptionalShutdownHooks } from "../lib/optional-shutdown-hooks";
|
|
11
|
+
import { runDrainStartHooks, runOptionalShutdownHooks } from "../lib/optional-shutdown-hooks";
|
|
12
12
|
import { stopStateStoreSweeper } from "../lib/state-store-sweeper";
|
|
13
13
|
import {
|
|
14
14
|
cancelQueuedStorageWorkerSpawns,
|
|
@@ -499,6 +499,7 @@ export async function drainAndShutdown(
|
|
|
499
499
|
// ordinary in-flight turns. A stuck scoped owner must not pin shutdown forever.
|
|
500
500
|
const deadline = Date.now() + Math.max(0, timeoutMs);
|
|
501
501
|
beginShutdownDrain();
|
|
502
|
+
runDrainStartHooks();
|
|
502
503
|
const temporaryDrainsSettled = await waitForTemporaryDrainsUntil(deadline);
|
|
503
504
|
if (!temporaryDrainsSettled) {
|
|
504
505
|
console.warn("Temporary drain lease did not settle before the shutdown deadline; forcing shutdown");
|