@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
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { advisorResultIsAdvice } from "./context";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Conversation- and task-scoped advisor state.
|
|
6
|
+
*
|
|
7
|
+
* Three scopes, deliberately separate:
|
|
8
|
+
*
|
|
9
|
+
* 1. REQUEST-scoped state lives in the per-request plan closure (see runtime.ts) — consultation
|
|
10
|
+
* count, dedup fingerprints, the preflight flag. Born and dies with one request.
|
|
11
|
+
*
|
|
12
|
+
* 2. TASK-scoped preflight ledger (this file): a bounded, process-local claim table that makes
|
|
13
|
+
* "at most one automatic consultation per task" atomic across concurrent requests. A claim
|
|
14
|
+
* requires a STABLE conversation identity plus the current task boundary; a client that sends
|
|
15
|
+
* no identity never enters this ledger (see `advisorLedgerKey`) and therefore fails open —
|
|
16
|
+
* it may be consulted once per request rather than risk two independent tasks suppressing
|
|
17
|
+
* each other through a shared guess.
|
|
18
|
+
*
|
|
19
|
+
* 3. PROVENANCE, split by authority:
|
|
20
|
+
* - MANUAL advice is verifiable history: a `toolResult` whose `toolName` is the synthetic
|
|
21
|
+
* advisor tool and whose content parses as a runtime-written advice object. Ordinary tool
|
|
22
|
+
* output, developer text, user text, and failure notices can never match it. Bytes inside
|
|
23
|
+
* the quoted advice field cannot change the runtime-owned status.
|
|
24
|
+
* - AUTOMATIC preflight dedup is NOT decided from history at all. The developer transport
|
|
25
|
+
* envelope labels the payload for the worker; any client could echo or forge such a message,
|
|
26
|
+
* so the ledger below is the authoritative source for "this task was already consulted" —
|
|
27
|
+
* `success`, `inflight` and `cooldown` states. A conversation with no stable identity gets
|
|
28
|
+
* no ledger and therefore fails open (at most one extra attempt). A forged marker or a
|
|
29
|
+
* forged developer message cannot suppress the policy.
|
|
30
|
+
*
|
|
31
|
+
* Ledger entries are plain state records — no message bodies, no credentials. Every state is
|
|
32
|
+
* bounded by entry count and its own TTL.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/** Long-lived success suppression: the task lifetime approximation (also the ledger TTL cap). */
|
|
36
|
+
export const ADVISOR_SUCCESS_TTL_MS = 24 * 60 * 60 * 1000;
|
|
37
|
+
/**
|
|
38
|
+
* Failure cooldown. One minute is the repository's standing minute-scale unit (tray polling,
|
|
39
|
+
* subagent availability polling); it turns a transient 503 into a short pause instead of
|
|
40
|
+
* silencing the policy for the rest of the coding session.
|
|
41
|
+
*/
|
|
42
|
+
export const ADVISOR_FAILURE_COOLDOWN_MS = 60 * 1000;
|
|
43
|
+
/**
|
|
44
|
+
* In-flight claim expiry. Longer than the largest configurable consultation timeout (600s
|
|
45
|
+
* upper bound on `advisor.timeoutMs`, default 120s) so a slow-but-alive consultation is never
|
|
46
|
+
* mistaken for a wedged one, while a crashed claim cannot block a task forever.
|
|
47
|
+
*/
|
|
48
|
+
export const ADVISOR_INFLIGHT_TTL_MS = 10 * 60 * 1000;
|
|
49
|
+
|
|
50
|
+
const MAX_ENTRIES = 512;
|
|
51
|
+
|
|
52
|
+
export type AdvisorClaimState =
|
|
53
|
+
/** The caller now owns the consultation for this task. */
|
|
54
|
+
| "claimed"
|
|
55
|
+
/** Another request for the same task is consulting right now. */
|
|
56
|
+
| "inflight"
|
|
57
|
+
/** This task already received advice; suppression holds until the success TTL expires. */
|
|
58
|
+
| "complete"
|
|
59
|
+
/** A recent consultation failed; suppression holds for the short failure cooldown. */
|
|
60
|
+
| "cooldown"
|
|
61
|
+
/**
|
|
62
|
+
* The ledger is full of live claims and granted nothing. Fail-open: the worker continues
|
|
63
|
+
* without a new automatic consultation rather than evicting a claim that is still running.
|
|
64
|
+
*/
|
|
65
|
+
| "saturated";
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The result of a claim attempt. `token` identifies THIS claim and is present only on
|
|
69
|
+
* `"claimed"`; settlement must present it, so a slow consultation whose in-flight entry expired
|
|
70
|
+
* cannot settle the successor claim that took its place.
|
|
71
|
+
*/
|
|
72
|
+
export interface AdvisorClaim {
|
|
73
|
+
state: AdvisorClaimState;
|
|
74
|
+
token?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface LedgerEntry {
|
|
78
|
+
state: "inflight" | "success" | "failed";
|
|
79
|
+
at: number;
|
|
80
|
+
/** Present on in-flight entries: which claim owns this entry. */
|
|
81
|
+
token?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface AdvisorPreflightLedger {
|
|
85
|
+
/**
|
|
86
|
+
* Atomically try to own the consultation for a task key. Returns `claimed` exactly once per
|
|
87
|
+
* task (with the ownership token) until a matching settlement releases it, so two concurrent
|
|
88
|
+
* requests cannot both consult.
|
|
89
|
+
*/
|
|
90
|
+
claim(key: string, now?: number): AdvisorClaim;
|
|
91
|
+
/**
|
|
92
|
+
* Settle a claim the caller owns. A settlement whose token does not match the current entry
|
|
93
|
+
* is a no-op: the entry now belongs to a successor claim (the caller's in-flight window
|
|
94
|
+
* expired), and it must not be able to erase or overwrite that successor's state.
|
|
95
|
+
*/
|
|
96
|
+
complete(key: string, token: string, now?: number): void;
|
|
97
|
+
fail(key: string, token: string, now?: number): void;
|
|
98
|
+
release(key: string, token: string, now?: number): void;
|
|
99
|
+
/**
|
|
100
|
+
* Fact, not settlement: this task received advice (used by a successful MANUAL consultation,
|
|
101
|
+
* which owns no preflight claim). Records success regardless of the current entry, because
|
|
102
|
+
* "the task was advised" is true whichever consultation produced it.
|
|
103
|
+
*/
|
|
104
|
+
markAdvised(key: string, now?: number): void;
|
|
105
|
+
/** Test/observability seam: current entry count. */
|
|
106
|
+
size(): number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function createAdvisorPreflightLedger(): AdvisorPreflightLedger {
|
|
110
|
+
const entries = new Map<string, LedgerEntry>();
|
|
111
|
+
let claimSequence = 0;
|
|
112
|
+
|
|
113
|
+
const isExpired = (entry: LedgerEntry, now: number): boolean => {
|
|
114
|
+
const ttl = entry.state === "success"
|
|
115
|
+
? ADVISOR_SUCCESS_TTL_MS
|
|
116
|
+
: entry.state === "failed"
|
|
117
|
+
? ADVISOR_FAILURE_COOLDOWN_MS
|
|
118
|
+
: ADVISOR_INFLIGHT_TTL_MS;
|
|
119
|
+
return now - entry.at > ttl;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Make room for ONE new claim without ever evicting a live in-flight entry, because that entry
|
|
124
|
+
* is the only thing preventing a second automatic consultation for its task. Order: expired
|
|
125
|
+
* entries first, then settled ones (success before failure), oldest first. Returns false when
|
|
126
|
+
* every entry is a live claim — the caller then reports `saturated` instead of breaking the
|
|
127
|
+
* "at most one in-flight consultation per task" guarantee.
|
|
128
|
+
*/
|
|
129
|
+
const makeRoom = (now: number): boolean => {
|
|
130
|
+
if (entries.size < MAX_ENTRIES) return true;
|
|
131
|
+
for (const [key, entry] of entries) {
|
|
132
|
+
if (isExpired(entry, now)) entries.delete(key);
|
|
133
|
+
}
|
|
134
|
+
if (entries.size < MAX_ENTRIES) return true;
|
|
135
|
+
for (const state of ["success", "failed"] as const) {
|
|
136
|
+
for (const [key, entry] of entries) {
|
|
137
|
+
if (entry.state === state) {
|
|
138
|
+
entries.delete(key);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const liveEntry = (key: string, now: number): LedgerEntry | undefined => {
|
|
147
|
+
const entry = entries.get(key);
|
|
148
|
+
if (!entry) return undefined;
|
|
149
|
+
if (isExpired(entry, now)) {
|
|
150
|
+
entries.delete(key);
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
return entry;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// Replacing an existing key never grows the map, and every NEW key is admitted only through
|
|
157
|
+
// claim()'s makeRoom gate, so the table cannot exceed MAX_ENTRIES.
|
|
158
|
+
const set = (key: string, state: LedgerEntry["state"], now: number, token?: string): void => {
|
|
159
|
+
if (entries.has(key)) entries.delete(key);
|
|
160
|
+
entries.set(key, { state, at: now, ...(token !== undefined ? { token } : {}) });
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/** True when the caller's token still owns the current in-flight entry for this key. */
|
|
164
|
+
const owns = (key: string, token: string, now: number): boolean => {
|
|
165
|
+
const entry = liveEntry(key, now);
|
|
166
|
+
return entry?.state === "inflight" && entry.token === token;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
claim(key, now = Date.now()) {
|
|
171
|
+
const entry = liveEntry(key, now);
|
|
172
|
+
if (entry?.state === "success") return { state: "complete" };
|
|
173
|
+
if (entry?.state === "failed") return { state: "cooldown" };
|
|
174
|
+
if (entry?.state === "inflight") return { state: "inflight" };
|
|
175
|
+
if (!makeRoom(now)) return { state: "saturated" };
|
|
176
|
+
claimSequence += 1;
|
|
177
|
+
const token = `claim-${claimSequence.toString(36)}`;
|
|
178
|
+
set(key, "inflight", now, token);
|
|
179
|
+
return { state: "claimed", token };
|
|
180
|
+
},
|
|
181
|
+
complete(key, token, now = Date.now()) {
|
|
182
|
+
// A settlement that no longer owns the entry is a no-op: the claim it belonged to expired
|
|
183
|
+
// and a successor now owns the state.
|
|
184
|
+
if (owns(key, token, now)) set(key, "success", now);
|
|
185
|
+
},
|
|
186
|
+
fail(key, token, now = Date.now()) {
|
|
187
|
+
if (owns(key, token, now)) set(key, "failed", now);
|
|
188
|
+
},
|
|
189
|
+
release(key, token, now = Date.now()) {
|
|
190
|
+
if (owns(key, token, now)) entries.delete(key);
|
|
191
|
+
},
|
|
192
|
+
markAdvised(key, now = Date.now()) {
|
|
193
|
+
// Recording the FACT must respect the same cap as claiming: a key that is not present and a
|
|
194
|
+
// table holding nothing but live claims means no room, so the record is skipped rather than
|
|
195
|
+
// evicting a claim that is still running. Fail-open: at worst one extra automatic attempt.
|
|
196
|
+
const present = liveEntry(key, now) !== undefined;
|
|
197
|
+
if (!present && !makeRoom(now)) return;
|
|
198
|
+
set(key, "success", now);
|
|
199
|
+
},
|
|
200
|
+
size() {
|
|
201
|
+
return entries.size;
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Stable conversation identity for the ledger, reusing the repository's existing request
|
|
208
|
+
* identities in specificity order: the client's own thread, the shared parent thread, the
|
|
209
|
+
* Cursor conversation, the Cursor client thread, then the reasoning-replay scope's thread.
|
|
210
|
+
* Returns undefined for a client that sends no identity at all — such a caller stays out of the
|
|
211
|
+
* process-global ledger on purpose (fail-open, see the module header).
|
|
212
|
+
*/
|
|
213
|
+
export function advisorConversationIdentity(parsed: {
|
|
214
|
+
_codexOwnThreadId?: string;
|
|
215
|
+
_clientThreadId?: string;
|
|
216
|
+
_cursorConversationId?: string;
|
|
217
|
+
_cursorClientThreadId?: string;
|
|
218
|
+
_reasoningReplayScope?: { clientThreadId?: string };
|
|
219
|
+
}): string | undefined {
|
|
220
|
+
return parsed._codexOwnThreadId
|
|
221
|
+
?? parsed._clientThreadId
|
|
222
|
+
?? parsed._cursorConversationId
|
|
223
|
+
?? parsed._cursorClientThreadId
|
|
224
|
+
?? parsed._reasoningReplayScope?.clientThreadId
|
|
225
|
+
?? undefined;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Domain-separated SHA-256 digest. Task identity and suppression are CORRECTNESS boundaries, so a
|
|
230
|
+
* 32-bit non-cryptographic hash is not an acceptable primary digest: distinct tasks must not
|
|
231
|
+
* collide because of a short fold. The ledger stores only this digest, never the raw text, so a
|
|
232
|
+
* captured key reveals nothing about the conversation. The domain prefix keeps digests from
|
|
233
|
+
* different purposes apart even if their inputs coincide.
|
|
234
|
+
*/
|
|
235
|
+
function sha256Hex(domain: string, value: string, hexChars: number): string {
|
|
236
|
+
return createHash("sha256").update(`${domain}\0${value}`, "utf8").digest("hex").slice(0, hexChars);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* The current task boundary inside a conversation: how many user turns the history carries and a
|
|
241
|
+
* digest of the FULL latest user text. A new user message moves the boundary (a new task gets its
|
|
242
|
+
* own claim); the same turn re-sent by a stateless full-history client keeps the same boundary,
|
|
243
|
+
* and a `previous_response_id` expansion replays the same user turns, so continuations dedup.
|
|
244
|
+
* The whole text participates — no truncation — so two tasks that share an opening prefix still
|
|
245
|
+
* get different boundaries.
|
|
246
|
+
*/
|
|
247
|
+
export function advisorTaskBoundary(parsed: {
|
|
248
|
+
context: { messages: readonly { role: string; content: unknown }[] };
|
|
249
|
+
}): string {
|
|
250
|
+
let userTurns = 0;
|
|
251
|
+
let lastUserText = "";
|
|
252
|
+
for (const message of parsed.context.messages) {
|
|
253
|
+
if (message.role !== "user") continue;
|
|
254
|
+
userTurns += 1;
|
|
255
|
+
lastUserText = contentText(message.content);
|
|
256
|
+
}
|
|
257
|
+
return `t${userTurns}:${sha256Hex("advisor-task-boundary", lastUserText, 32)}`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* The ledger key: one domain-separated SHA-256 digest over conversation identity + task boundary
|
|
262
|
+
* + worker model. Returns undefined when the caller has no stable conversation identity — the
|
|
263
|
+
* caller then relies on request-scoped dedup and genuine in-history provenance instead of a
|
|
264
|
+
* shared guess (documented fail-open).
|
|
265
|
+
*/
|
|
266
|
+
export function advisorLedgerKey(
|
|
267
|
+
parsed: {
|
|
268
|
+
context: { messages: readonly { role: string; content: unknown }[] };
|
|
269
|
+
_codexOwnThreadId?: string;
|
|
270
|
+
_clientThreadId?: string;
|
|
271
|
+
_cursorConversationId?: string;
|
|
272
|
+
_cursorClientThreadId?: string;
|
|
273
|
+
_reasoningReplayScope?: { clientThreadId?: string };
|
|
274
|
+
},
|
|
275
|
+
workerModelId: string,
|
|
276
|
+
): string | undefined {
|
|
277
|
+
const identity = advisorConversationIdentity(parsed);
|
|
278
|
+
if (!identity) return undefined;
|
|
279
|
+
const material = `${identity}\0${advisorTaskBoundary(parsed)}\0${workerModelId}`;
|
|
280
|
+
return `ak-${sha256Hex("advisor-task-key", material, 40)}`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** Text projection for string-or-parts content; used only for hashing, never transmitted. */
|
|
284
|
+
export function contentText(content: unknown): string {
|
|
285
|
+
if (typeof content === "string") return content;
|
|
286
|
+
if (!Array.isArray(content)) return "";
|
|
287
|
+
return content
|
|
288
|
+
.filter((part): part is { type: "text"; text: string } =>
|
|
289
|
+
!!part && typeof part === "object" && (part as { type?: unknown }).type === "text"
|
|
290
|
+
&& typeof (part as { text?: unknown }).text === "string")
|
|
291
|
+
.map(part => part.text)
|
|
292
|
+
.join("");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Legacy marker spellings. Failure text still neutralizes them so an upstream error cannot
|
|
297
|
+
* look like an old wrapper. They are not suppression authority and are not emitted.
|
|
298
|
+
*/
|
|
299
|
+
export const ADVISOR_PREFLIGHT_MARKER = "<opencodex_advisor_preflight>";
|
|
300
|
+
|
|
301
|
+
/** The synthetic advisor tool's wire name; kept in sync with the tool definition by test. */
|
|
302
|
+
export const ADVISOR_RESULT_TOOL_NAME = "advisor";
|
|
303
|
+
|
|
304
|
+
/** Legacy manual wrapper spelling. Detection does not search for this substring. */
|
|
305
|
+
export const ADVISOR_ADVICE_MARKER = "<opencodex_advisor>";
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Detect an ALREADY-PRESENT MANUAL advisor result — by provenance, never by a bare string.
|
|
309
|
+
* The message must be a `toolResult` whose `toolName` is the synthetic advisor tool, and its
|
|
310
|
+
* content must parse as a runtime-written advice object (`status === "advice"` on the sibling
|
|
311
|
+
* field the runtime sets). Text inside `advice` cannot flip that field.
|
|
312
|
+
*
|
|
313
|
+
* Only messages after the latest user turn count. Manual advice from an earlier task in the
|
|
314
|
+
* same thread does not suppress preflight for a later task; the ledger keys those separately.
|
|
315
|
+
* Developer messages are deliberately NOT inspected. Automatic preflight dedup lives in the
|
|
316
|
+
* ledger. A client-echoed developer envelope, a shell result, or a failure notice matches nothing.
|
|
317
|
+
*/
|
|
318
|
+
export function historyHasManualAdvisorResult(parsed: {
|
|
319
|
+
context: { messages: readonly { role: string; content?: unknown; toolName?: string }[] };
|
|
320
|
+
}): boolean {
|
|
321
|
+
for (let i = parsed.context.messages.length - 1; i >= 0; i -= 1) {
|
|
322
|
+
const message = parsed.context.messages[i]!;
|
|
323
|
+
if (message.role === "user") break;
|
|
324
|
+
if (message.role !== "toolResult") continue;
|
|
325
|
+
if (message.toolName !== ADVISOR_RESULT_TOOL_NAME) continue;
|
|
326
|
+
if (advisorResultIsAdvice(contentText(message.content))) return true;
|
|
327
|
+
}
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** Kept for callers that only need the first user text (payload building, tests). */
|
|
332
|
+
export function firstUserText(parsed: { context: { messages: readonly { role: string; content: unknown }[] } }): string {
|
|
333
|
+
for (const message of parsed.context.messages) {
|
|
334
|
+
if (message.role !== "user") continue;
|
|
335
|
+
const text = contentText(message.content);
|
|
336
|
+
if (text.trim() !== "") return text;
|
|
337
|
+
}
|
|
338
|
+
return "";
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Deterministic preflight trigger: has this conversation already produced orientation evidence
|
|
343
|
+
* since the latest user message? The documented approximation for "before the first substantive
|
|
344
|
+
* implementation" — the protocol layer offers no safe pre-mutation checkpoint, so OpenCodex fires
|
|
345
|
+
* the automatic attempt on the first worker reasoning turn that arrives with that evidence.
|
|
346
|
+
* Evidence is an assistant tool call OR a tool result after the latest user message (both forms
|
|
347
|
+
* are genuinely accepted; the docs say so). Only text/toolResult content is inspected — never
|
|
348
|
+
* reasoning, never encrypted items.
|
|
349
|
+
*/
|
|
350
|
+
export function hasOrientationEvidence(parsed: { context: { messages: readonly { role: string; content: unknown }[] } }): boolean {
|
|
351
|
+
let latestUserIndex = -1;
|
|
352
|
+
for (let i = parsed.context.messages.length - 1; i >= 0; i -= 1) {
|
|
353
|
+
if (parsed.context.messages[i]?.role === "user") {
|
|
354
|
+
latestUserIndex = i;
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (latestUserIndex < 0) return false;
|
|
359
|
+
for (let i = latestUserIndex + 1; i < parsed.context.messages.length; i += 1) {
|
|
360
|
+
const message = parsed.context.messages[i];
|
|
361
|
+
if (message.role === "toolResult") return true;
|
|
362
|
+
if (message.role === "assistant" && Array.isArray(message.content)
|
|
363
|
+
&& message.content.some(part =>
|
|
364
|
+
!!part && typeof part === "object" && (part as { type?: unknown }).type === "toolCall")) {
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The synthetic `advisor` tool injected into routed worker turns.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors src/web-search/synthetic-tool.ts: the proxy OWNS this tool — the worker only expresses
|
|
5
|
+
* "I want to consult the expert" (optionally with a focus), and OpenCodex builds everything else
|
|
6
|
+
* (task context, conversation, tool catalog, identities) itself. The worker never passes a
|
|
7
|
+
* transcript, provider, model, or files.
|
|
8
|
+
*/
|
|
9
|
+
import type { OcxTool } from "../types";
|
|
10
|
+
|
|
11
|
+
export const ADVISOR_TOOL_NAME = "advisor";
|
|
12
|
+
|
|
13
|
+
export function buildAdvisorTool(): OcxTool {
|
|
14
|
+
return {
|
|
15
|
+
name: ADVISOR_TOOL_NAME,
|
|
16
|
+
description:
|
|
17
|
+
"Consult an independent expert advisor model about the current task. The advisor receives a "
|
|
18
|
+
+ "summary of what you have done so far (task, conversation, tool results) and returns "
|
|
19
|
+
+ "strategic advice, critique, root-cause reasoning, or alternative approaches. Use it when "
|
|
20
|
+
+ "you are stuck, before starting substantive implementation on a hard task, or when you want "
|
|
21
|
+
+ "a second opinion on a plan or diagnosis. The advisor cannot execute tools or edit files; "
|
|
22
|
+
+ "you remain responsible for all execution. Optionally pass a short `question` to focus the "
|
|
23
|
+
+ "consultation.",
|
|
24
|
+
parameters: {
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: {
|
|
27
|
+
question: { type: "string", description: "Optional short focus question for the advisor." },
|
|
28
|
+
},
|
|
29
|
+
required: [],
|
|
30
|
+
},
|
|
31
|
+
advisor: true,
|
|
32
|
+
};
|
|
33
|
+
}
|
package/src/bridge/errors.ts
CHANGED
|
@@ -13,16 +13,35 @@ import {
|
|
|
13
13
|
CYBER_POLICY_ERROR_CODE,
|
|
14
14
|
isCyberPolicyCode,
|
|
15
15
|
SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE,
|
|
16
|
+
UNDECLARED_TOOL_CALL_ERROR_CODE,
|
|
16
17
|
type OcxErrorPayload,
|
|
17
18
|
} from "../lib/errors";
|
|
18
19
|
|
|
20
|
+
const PINNED_VERDICT_CODES = new Set([CYBER_POLICY_ERROR_CODE, "client_closed_request"]);
|
|
21
|
+
|
|
19
22
|
export function formatErrorResponse(
|
|
20
23
|
status: number,
|
|
21
24
|
type: string,
|
|
22
25
|
message: string,
|
|
23
|
-
options?: { code?: string | null; retryAfter?: string | null },
|
|
26
|
+
options?: { code?: string | null; retryAfter?: string | null; classified?: boolean },
|
|
24
27
|
): Response {
|
|
25
28
|
const error = classifyError(status, type, message);
|
|
29
|
+
// A combo child holding an adapterFailureFromEvent verdict has already classified this
|
|
30
|
+
// failure. Re-deriving it from the status folded insufficient_quota into rate_limit_exceeded
|
|
31
|
+
// and a 400 modality refusal into invalid_request_error, so the combo loop cooled and hopped
|
|
32
|
+
// on the wrong evidence. The child body reaches only that loop, whose consumer re-applies
|
|
33
|
+
// this formatter's allowlist before anything reaches a client. The overrides below still win.
|
|
34
|
+
// The caller's code fills in only where its type and message add nothing to what the status
|
|
35
|
+
// alone says. A verdict read from the full message (quota, credentials, cyber policy, context
|
|
36
|
+
// overflow, catalog size) outranks a generic adapter code: the consumer sees only the first
|
|
37
|
+
// 500 characters, so replacing it turned a late cyber refusal into a hop and spent quota into a
|
|
38
|
+
// target cooldown. The baseline uses a neutral type because an untyped adapter event arrives
|
|
39
|
+
// with a type adapterFailureFromEvent already inferred from that same message.
|
|
40
|
+
const messageVerdict = error.code !== classifyError(status, "upstream_error", "").code;
|
|
41
|
+
if (options?.classified && options.code && !messageVerdict && !PINNED_VERDICT_CODES.has(error.code ?? "")) {
|
|
42
|
+
error.type = type;
|
|
43
|
+
error.code = options.code;
|
|
44
|
+
}
|
|
26
45
|
if (options?.code === SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE) {
|
|
27
46
|
error.type = "server_error";
|
|
28
47
|
error.code = SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE;
|
|
@@ -31,6 +50,10 @@ export function formatErrorResponse(
|
|
|
31
50
|
error.code = CYBER_POLICY_ERROR_CODE;
|
|
32
51
|
error.type = cyberPolicyErrorType(type);
|
|
33
52
|
}
|
|
53
|
+
// The undeclared-tool refusal is deterministic, not a transient upstream failure: its code
|
|
54
|
+
// survives so the Anthropic translator can classify it instead of folding it into a
|
|
55
|
+
// retryable overload (and so clients see the SSE path's rejection identity).
|
|
56
|
+
if (options?.code === UNDECLARED_TOOL_CALL_ERROR_CODE) error.code = options.code;
|
|
34
57
|
// Only the allowlisted transport verdicts survive this formatter. Do not forward
|
|
35
58
|
// arbitrary provider codes, and preserve the existing cyber-policy precedence.
|
|
36
59
|
const replayBlocked = error.code !== CYBER_POLICY_ERROR_CODE
|
package/src/bridge/internal.ts
CHANGED
|
@@ -186,7 +186,9 @@ export function adapterFailureFromEvent(event: Extract<AdapterEvent, { type: "er
|
|
|
186
186
|
* This fixes items created from here on. History recorded before it is repaired at the
|
|
187
187
|
* replay boundary by `backfillWebSearchQueries()` in the Responses adapter.
|
|
188
188
|
*/
|
|
189
|
-
export function webSearchAction(queries: string[]): Record<string, unknown> {
|
|
189
|
+
export function webSearchAction(queries: string[], fetch?: { url: string }): Record<string, unknown> {
|
|
190
|
+
// Anthropic's server-side web_fetch opens one page instead of searching.
|
|
191
|
+
if (fetch) return { type: "open_page", url: fetch.url };
|
|
190
192
|
const first = queries[0] ?? "";
|
|
191
193
|
return { type: "search", query: first, queries: queries.length > 0 ? queries : [first] };
|
|
192
194
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { anthropicProgressItems } from "../responses/anthropic-progress";
|
|
1
2
|
import type {
|
|
2
3
|
AdapterEvent,
|
|
3
4
|
OcxMessagePhase,
|
|
@@ -67,6 +68,8 @@ export function buildResponseJSON(
|
|
|
67
68
|
if (options?.translatorBudget && options.recordBufferedDelivery !== false) {
|
|
68
69
|
attemptDeliveryRecorder(budget)?.noteBufferedDelivery(body);
|
|
69
70
|
}
|
|
71
|
+
if (body.status === "completed" && options?.compaction
|
|
72
|
+
&& !events.some(event => event.type === "done" && event.compactionEncryptedContent)) options.compactionRetention?.commit();
|
|
70
73
|
return body;
|
|
71
74
|
} finally {
|
|
72
75
|
for (const event of events) {
|
|
@@ -98,6 +101,7 @@ function buildResponseJSONWithBudget(
|
|
|
98
101
|
toolSearchToolNames?: Set<string>;
|
|
99
102
|
/** Remote compaction v2 turn — append one synthetic compaction output item (see bridgeToResponsesSSE). */
|
|
100
103
|
compaction?: boolean;
|
|
104
|
+
compactionRetention?: { encode(summary: string): string; commit(): void };
|
|
101
105
|
onProviderState?: (state: OcxProviderContinuationState) => void;
|
|
102
106
|
/** Raw adapter-reported usage before wire normalization (see bridgeToResponsesSSE onUsage). */
|
|
103
107
|
onUsage?: (usage: OcxUsage | undefined) => void;
|
|
@@ -386,6 +390,16 @@ function buildResponseJSONWithBudget(
|
|
|
386
390
|
flushToolCall();
|
|
387
391
|
break;
|
|
388
392
|
case "text_delta":
|
|
393
|
+
if (options?.compaction && e.anthropicThinkingSignature) break;
|
|
394
|
+
if (e.anthropicThinkingSignature) {
|
|
395
|
+
rawReasoningForNextToolCall = "";
|
|
396
|
+
flushText("commentary");
|
|
397
|
+
flushSummaryReasoning();
|
|
398
|
+
flushRawReasoning();
|
|
399
|
+
flushToolCall();
|
|
400
|
+
for (const item of anthropicProgressItems(e.text, e.anthropicThinkingSignature, budget)) pushOutput(item as OutputItem);
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
389
403
|
// Only flush on an explicit phase change. A later delta that omits `phase` must keep
|
|
390
404
|
// appending under the previously established phase.
|
|
391
405
|
if (currentText.bytes > 0 && e.phase !== undefined && currentTextPhase !== e.phase) flushText("commentary");
|
|
@@ -533,8 +547,9 @@ function buildResponseJSONWithBudget(
|
|
|
533
547
|
const safeSources = safeWebSearchSources(e.sources);
|
|
534
548
|
pushOutput({
|
|
535
549
|
type: "web_search_call", id: `ws_${uuid()}`, status: e.status ?? "completed",
|
|
536
|
-
action: webSearchAction(e.queries),
|
|
550
|
+
action: webSearchAction(e.queries, e.fetch),
|
|
537
551
|
...(safeSources.length > 0 ? { sources: safeSources } : {}),
|
|
552
|
+
...(e.fetch ? { fetch_result: e.fetch.result } : {}),
|
|
538
553
|
});
|
|
539
554
|
if (safeSources.length > 0) {
|
|
540
555
|
for (const source of safeSources) {
|
|
@@ -616,7 +631,7 @@ function buildResponseJSONWithBudget(
|
|
|
616
631
|
) {
|
|
617
632
|
const item = {
|
|
618
633
|
type: "compaction", id: `cmp_${uuid()}`,
|
|
619
|
-
encrypted_content: compactionEncryptedContent ?? encodeCompactionSummary(joinChunks(batchCompaction)),
|
|
634
|
+
encrypted_content: compactionEncryptedContent ?? (options?.compactionRetention?.encode(joinChunks(batchCompaction)) ?? encodeCompactionSummary(joinChunks(batchCompaction))),
|
|
620
635
|
};
|
|
621
636
|
pushOutput(item, compactionEncryptedContent ? 0 : batchCompaction.bytes);
|
|
622
637
|
}
|
|
@@ -646,7 +661,7 @@ function buildResponseJSONWithBudget(
|
|
|
646
661
|
: errorEvent?.retryable !== undefined ? { retryable: errorEvent.retryable } : {}),
|
|
647
662
|
...(incompleteEvent ? {
|
|
648
663
|
incomplete_details: {
|
|
649
|
-
reason: incompleteEvent.reason,
|
|
664
|
+
reason: truncationReasonFor(incompleteEvent.reason) ?? incompleteEvent.reason,
|
|
650
665
|
...(incompleteEvent.message ? { message: incompleteEvent.message } : {}),
|
|
651
666
|
...(incompleteEvent.retryable !== undefined ? { retryable: incompleteEvent.retryable } : {}),
|
|
652
667
|
},
|