@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,117 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import type { OcxParsedRequest } from "../types";
|
|
3
|
+
import { contentText } from "./state";
|
|
4
|
+
import { advisorEvidenceTool } from "./evidence-tool";
|
|
5
|
+
|
|
6
|
+
export interface AdvisorRepairEvidence {
|
|
7
|
+
id: string;
|
|
8
|
+
command: string;
|
|
9
|
+
firstFailure: { message: number; exitCode: number; output: string };
|
|
10
|
+
repair: { message: number; tool: string };
|
|
11
|
+
repeatedFailure: { message: number; exitCode: number; output: string };
|
|
12
|
+
}
|
|
13
|
+
interface Validation { command: string; fingerprint: string; invokedAt: number }
|
|
14
|
+
const digest = (value: string) => createHash("sha256").update(value).digest("hex");
|
|
15
|
+
const excerpt = (text: string) => text.length <= 1600 ? text : `${text.slice(0, 800)}\n[output truncated]\n${text.slice(-800)}`;
|
|
16
|
+
|
|
17
|
+
/** A deliberately small command grammar. No pipelines, substitutions or arbitrary scripts. */
|
|
18
|
+
function validation(args: Record<string, unknown>, invokedAt: number): Validation | undefined {
|
|
19
|
+
const input = args.cmd ?? args.command;
|
|
20
|
+
const raw = Array.isArray(input) && input.every(value => typeof value === "string")
|
|
21
|
+
? input.map(value => JSON.stringify(value)).join(" ") : input;
|
|
22
|
+
if (typeof raw !== "string" || raw.length > 4096 || /[\n\r;|<>`$]/.test(raw)) return;
|
|
23
|
+
let command = raw.trim();
|
|
24
|
+
let cwd = typeof (args.workdir ?? args.cwd) === "string" ? String(args.workdir ?? args.cwd) : "";
|
|
25
|
+
const cd = /^cd\s+("[^"]+"|'[^']+'|[^\s&]+)\s*&&\s*(.+)$/.exec(command);
|
|
26
|
+
if (cd) { cwd += `/${cd[1]!.replace(/^["']|["']$/g, "")}`; command = cd[2]!; }
|
|
27
|
+
if (command.includes("&")) return;
|
|
28
|
+
// Quoted arguments stay intact; metacharacters above are rejected even inside quotes.
|
|
29
|
+
const tokens = command.match(/"[^"]*"|'[^']*'|[^\s"']+/g);
|
|
30
|
+
if (!tokens || tokens.join(" ").replace(/\s+/g, " ") !== command.replace(/\s+/g, " ")) return;
|
|
31
|
+
const words = tokens.map(token => token.replace(/^["']|["']$/g, ""));
|
|
32
|
+
const executable = words[0]!.split(/[\\/]/).at(-1)!;
|
|
33
|
+
const rest = words.slice(1).join(" ");
|
|
34
|
+
const isValidation = /^(?:pytest|tsc|eslint)$/.test(executable)
|
|
35
|
+
|| (/^(?:bun|npm|pnpm|yarn)$/.test(executable) && /^(?:(?:run\s+)?(?:test|typecheck|lint|check|build)(?:[\s:]|$)|(?:x|exec)\s+(?:tsc|eslint)(?:\s|$))/.test(rest))
|
|
36
|
+
|| (/^python(?:3)?$/.test(executable) && /^-m pytest(?:\s|$)/.test(rest))
|
|
37
|
+
|| (executable === "cargo" && /^(?:test|check|clippy|build)(?:\s|$)/.test(rest))
|
|
38
|
+
|| (executable === "go" && /^(?:test|vet)(?:\s|$)/.test(rest));
|
|
39
|
+
if (!isValidation) return;
|
|
40
|
+
return { command: raw, fingerprint: digest(JSON.stringify([cwd, words])), invokedAt };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Shell envelope status only. Test prose such as "FAIL" is not an exit status. */
|
|
44
|
+
function exitCode(text: string): number | undefined {
|
|
45
|
+
try {
|
|
46
|
+
const wrapped = text.indexOf("\nOutput:\n");
|
|
47
|
+
const raw: unknown = JSON.parse(wrapped >= 0 ? text.slice(wrapped + "\nOutput:\n".length) : text);
|
|
48
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
49
|
+
const record = raw as Record<string, unknown>;
|
|
50
|
+
const code = record.exit_code ?? record.exitCode;
|
|
51
|
+
if (typeof code === "number" && Number.isSafeInteger(code)) return code;
|
|
52
|
+
}
|
|
53
|
+
} catch { /* textual shell envelope */ }
|
|
54
|
+
const code = /^(?:Process exited with code|Process exit code:|Exit code:)\s*(-?\d+)\s*$/m.exec(text);
|
|
55
|
+
return code ? Number(code[1]) : undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Reconstructed from paired tool calls/results after the latest user turn; no LLM or timers. */
|
|
59
|
+
export function collectAdvisorRepairEvidence(parsed: OcxParsedRequest): AdvisorRepairEvidence | undefined {
|
|
60
|
+
const calls = new Map<string, { tool: string; args: Record<string, unknown>; invokedAt: number }>();
|
|
61
|
+
const sessions = new Map<string, Validation>();
|
|
62
|
+
const failures = new Map<string, { failure: AdvisorRepairEvidence["firstFailure"]; callId: string; repair?: AdvisorRepairEvidence["repair"] & { callId: string } }>();
|
|
63
|
+
let candidate: AdvisorRepairEvidence | undefined;
|
|
64
|
+
let candidateFingerprint: string | undefined;
|
|
65
|
+
const messages = parsed.context.messages;
|
|
66
|
+
let start = messages.findLastIndex(message => message.role === "user") + 1;
|
|
67
|
+
// Bounded work; a clipped prefix cannot establish a failure/repair sequence.
|
|
68
|
+
start = Math.max(start, messages.length - 512);
|
|
69
|
+
for (let i = start; i < messages.length; i++) {
|
|
70
|
+
const message = messages[i]!;
|
|
71
|
+
if (message.role === "assistant") {
|
|
72
|
+
for (const part of message.content) if (part.type === "toolCall") {
|
|
73
|
+
calls.set(part.id, { ...advisorEvidenceTool(part.name, part.arguments ?? {}), invokedAt: i + 1 });
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (message.role !== "toolResult") continue;
|
|
78
|
+
const call = calls.get(message.toolCallId);
|
|
79
|
+
if (!call) continue;
|
|
80
|
+
calls.delete(message.toolCallId); // A replayed result cannot stand in for another execution.
|
|
81
|
+
const output = contentText(message.content);
|
|
82
|
+
if (["apply_patch", "Write", "Edit", "write_file", "edit_file"].includes(call.tool) && !message.isError
|
|
83
|
+
&& output.trim() && !/^(?:error|failed|failure)\b/i.test(output.trim())) {
|
|
84
|
+
for (const failure of failures.values()) if (call.invokedAt > failure.failure.message) failure.repair = { message: i + 1, tool: call.tool, callId: message.toolCallId };
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
let check: Validation | undefined;
|
|
88
|
+
if (["exec_command", "shell_command", "shell", "Bash", "bash"].includes(call.tool)) check = validation(call.args, call.invokedAt);
|
|
89
|
+
else if (call.tool === "write_stdin") check = sessions.get(String(call.args.session_id));
|
|
90
|
+
if (!check) continue;
|
|
91
|
+
const code = exitCode(output);
|
|
92
|
+
if (code === undefined) {
|
|
93
|
+
const session = /(?:Process running with session ID\s+|"session_id"\s*:\s*)(\d+)/.exec(output);
|
|
94
|
+
if (session) sessions.set(session[1]!, check);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (call.tool === "write_stdin") sessions.delete(String(call.args.session_id));
|
|
98
|
+
if (code === 0) {
|
|
99
|
+
failures.delete(check.fingerprint);
|
|
100
|
+
if (candidateFingerprint === check.fingerprint) { candidate = undefined; candidateFingerprint = undefined; }
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const failure = { message: i + 1, exitCode: code, output: excerpt(output) };
|
|
104
|
+
const earlier = failures.get(check.fingerprint);
|
|
105
|
+
if (earlier?.repair && check.invokedAt > earlier.repair.message) {
|
|
106
|
+
candidate = {
|
|
107
|
+
// Positions are prompt references only: history pruning must not mint a new event.
|
|
108
|
+
id: digest(JSON.stringify([check.fingerprint, earlier.callId, earlier.repair.callId, message.toolCallId])),
|
|
109
|
+
command: check.command,
|
|
110
|
+
firstFailure: earlier.failure, repair: { message: earlier.repair.message, tool: earlier.repair.tool }, repeatedFailure: failure,
|
|
111
|
+
};
|
|
112
|
+
candidateFingerprint = check.fingerprint;
|
|
113
|
+
}
|
|
114
|
+
failures.set(check.fingerprint, { failure, callId: message.toolCallId });
|
|
115
|
+
}
|
|
116
|
+
return candidate;
|
|
117
|
+
}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The advisor request plan: what the optional subsystem registers into the core Responses path.
|
|
3
|
+
*
|
|
4
|
+
* Created PER REQUEST through the activated factory called by the sidecar planner — never
|
|
5
|
+
* at module load and never globally. All mutable state is request-scoped except the bounded
|
|
6
|
+
* task-scoped preflight ledger (src/advisor/state.ts).
|
|
7
|
+
*
|
|
8
|
+
* Responsibilities:
|
|
9
|
+
* - decide whether the advisor applies to this request (settings + capability of the path);
|
|
10
|
+
* - preflight: one automatic consultation ATTEMPT per task when the orientation evidence exists,
|
|
11
|
+
* claimed atomically in the ledger and injected as user-role advisory data;
|
|
12
|
+
* - manual: back the synthetic `advisor` tool guard with real consultations through the routing
|
|
13
|
+
* authority (loopback chat completion);
|
|
14
|
+
* - observability: one structured log line per consultation — proof that the advisor actually
|
|
15
|
+
* ran (worker model, advisor model, trigger, duration, status, usage).
|
|
16
|
+
*
|
|
17
|
+
* Preflight injection transport: the runtime-owned instruction is fixed developer policy;
|
|
18
|
+
* all Advisor-generated bytes are a separate JSON-quoted user-role advisory message. Manual
|
|
19
|
+
* consultations remain paired tool results. No automatic path fabricates a tool call or
|
|
20
|
+
* places Advisor output in developer/system content. User-role advice can still contain
|
|
21
|
+
* hostile recommendations; the fixed policy tells the worker how to interpret that data.
|
|
22
|
+
*/
|
|
23
|
+
import type { OcxConfig, OcxParsedRequest } from "../types";
|
|
24
|
+
import type { AdvisorPlan, AdvisorConsultOutcome } from "../server/responses/advisor-slot";
|
|
25
|
+
import { createAdvisorGuard, MAX_ADVISOR_CONSULTATIONS_PER_REQUEST } from "../server/responses/advisor-slot";
|
|
26
|
+
import { resolveAdvisorSettings, type AdvisorPolicy } from "./settings";
|
|
27
|
+
import { consultAdvisor } from "./consult";
|
|
28
|
+
import { buildAdvisorTool } from "./synthetic-tool";
|
|
29
|
+
import { sanitizeLogMetadataString } from "../lib/redact";
|
|
30
|
+
import {
|
|
31
|
+
advisorLedgerKey,
|
|
32
|
+
createAdvisorPreflightLedger,
|
|
33
|
+
hasOrientationEvidence,
|
|
34
|
+
historyHasManualAdvisorResult,
|
|
35
|
+
type AdvisorPreflightLedger,
|
|
36
|
+
} from "./state";
|
|
37
|
+
import { formatAdvisorAdvice, advisorPreflightMessages, formatAdvisorUnavailable } from "./context";
|
|
38
|
+
import { advisorActivityStore, type AdvisorActivityStore, type AdvisorTrigger } from "./activity";
|
|
39
|
+
import { createAdvisorTaskBudget, type AdvisorTaskBudget } from "./budget";
|
|
40
|
+
import { collectAdvisorRepairEvidence, type AdvisorRepairEvidence } from "./evidence";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Process-local task ledger. Bounded (entries + per-state TTL) in src/advisor/state.ts; one
|
|
44
|
+
* instance per process so the claim is atomic across concurrent requests. Not durable by design
|
|
45
|
+
* — see the documented restart limitation.
|
|
46
|
+
*/
|
|
47
|
+
const sharedPreflightLedger = createAdvisorPreflightLedger();
|
|
48
|
+
|
|
49
|
+
export const ADVISOR_SHARED_LEDGER = sharedPreflightLedger;
|
|
50
|
+
|
|
51
|
+
export interface AdvisorRuntimeDeps {
|
|
52
|
+
config: Pick<OcxConfig, "advisor" | "port" | "hostname" | "apiKeys" | "unauthenticatedLoopbackListener">;
|
|
53
|
+
/** Routed worker identity for logs and the advisor payload, e.g. "deepseek-chat (provider deepseek)". */
|
|
54
|
+
workerIdentity: string;
|
|
55
|
+
workerModelId: string;
|
|
56
|
+
abortSignal?: AbortSignal;
|
|
57
|
+
/** Test seam; production always self-fetches the resolved local destination. */
|
|
58
|
+
baseUrlOverride?: string;
|
|
59
|
+
/** Test seam; production uses the process-wide ledger. */
|
|
60
|
+
ledger?: AdvisorPreflightLedger;
|
|
61
|
+
budget?: AdvisorTaskBudget;
|
|
62
|
+
activities?: AdvisorActivityStore;
|
|
63
|
+
/** Deterministic clock seam for the ledger's TTL/cooldown arithmetic (tests only). */
|
|
64
|
+
now?: () => number;
|
|
65
|
+
/**
|
|
66
|
+
* Test seam: runs after a preflight claim is taken and before the consultation
|
|
67
|
+
* dispatch, so a test can revoke consent in the claim-to-outbound window.
|
|
68
|
+
*/
|
|
69
|
+
afterPreflightClaim?: () => void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface AdvisorRuntimePlan extends AdvisorPlan {
|
|
73
|
+
readonly policy: AdvisorPolicy;
|
|
74
|
+
readonly toolEnabled: boolean;
|
|
75
|
+
readonly tool: import("../types").OcxTool;
|
|
76
|
+
/**
|
|
77
|
+
* The automatic preflight pass. Returns true when advice was injected. A cancelled
|
|
78
|
+
* consultation injects nothing and leaves the task eligible for a later attempt.
|
|
79
|
+
*/
|
|
80
|
+
preflightInject(parsed: OcxParsedRequest): Promise<boolean>;
|
|
81
|
+
/** Attach the stream guard for the synthetic tool to the parsed request. */
|
|
82
|
+
attachGuard(parsed: OcxParsedRequest): void;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function createAdvisorRuntimePlan(deps: AdvisorRuntimeDeps): AdvisorRuntimePlan | null {
|
|
86
|
+
const initial = resolveAdvisorSettings(deps.config);
|
|
87
|
+
// A model is required before the synthetic tool exists. Consent is checked at every
|
|
88
|
+
// consultation against the live config, so an enabled advisor without current consent can
|
|
89
|
+
// still refuse a manual call in-band without sending task context. Disabled, or enabled
|
|
90
|
+
// with no model, stays off the path.
|
|
91
|
+
if (!initial.enabled || initial.model.trim() === "") return null;
|
|
92
|
+
const ledger = deps.ledger ?? sharedPreflightLedger;
|
|
93
|
+
const now = deps.now ?? (() => Date.now());
|
|
94
|
+
const budget = deps.budget ?? createAdvisorTaskBudget();
|
|
95
|
+
const activities = deps.activities ?? advisorActivityStore(deps.config);
|
|
96
|
+
// Management PUT mutates this same config object (`config.advisor = ...`). Re-resolving
|
|
97
|
+
// at dispatch is what makes a mid-request consent revocation stop outbound transfer.
|
|
98
|
+
const liveSettings = () => resolveAdvisorSettings(deps.config);
|
|
99
|
+
|
|
100
|
+
// Request-scoped state: born here, dies with the request. Never global.
|
|
101
|
+
const fingerprints = new Set<string>();
|
|
102
|
+
let preflightUsed = false;
|
|
103
|
+
let consultations = 0;
|
|
104
|
+
// Capture before automatic user-role advisory injection. Continuations belong to this
|
|
105
|
+
// original task; an injected advisory must never mint another spending allowance.
|
|
106
|
+
let identity: { preflight?: string; budget?: string } | undefined;
|
|
107
|
+
const identify = (parsed: OcxParsedRequest) => identity ??= {
|
|
108
|
+
preflight: advisorLedgerKey(parsed, deps.workerModelId),
|
|
109
|
+
budget: advisorLedgerKey(parsed, ""),
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const taskKey = (parsed: OcxParsedRequest): string | undefined =>
|
|
113
|
+
identify(parsed).preflight;
|
|
114
|
+
|
|
115
|
+
const logConsultation = (
|
|
116
|
+
trigger: AdvisorTrigger,
|
|
117
|
+
outcome: { ok: boolean; cancelled?: boolean; durationMs: number; error?: string; usage?: { inputTokens?: number; outputTokens?: number; totalTokens?: number } },
|
|
118
|
+
advisorModel: string,
|
|
119
|
+
): void => {
|
|
120
|
+
const usage = outcome.usage
|
|
121
|
+
? ` usage=in=${outcome.usage.inputTokens ?? "?"} out=${outcome.usage.outputTokens ?? "?"}`
|
|
122
|
+
: "";
|
|
123
|
+
const status = outcome.ok ? "ok" : outcome.cancelled ? "cancelled" : "failed";
|
|
124
|
+
const errorNote = outcome.ok || outcome.cancelled
|
|
125
|
+
? ""
|
|
126
|
+
: ` error=${sanitizeLogMetadataString(outcome.error ?? "unknown", 160) ?? "unknown"}`;
|
|
127
|
+
// One structured line per consultation: model ids, timing, and a bounded status. No prompt,
|
|
128
|
+
// no tool output, no capability.
|
|
129
|
+
console.warn(
|
|
130
|
+
`[advisor] consultation ${status} trigger=${trigger} worker=${deps.workerModelId}`
|
|
131
|
+
+ ` advisor=${advisorModel} durationMs=${outcome.durationMs}${usage}${errorNote}`,
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const runConsultation = async (
|
|
136
|
+
parsed: OcxParsedRequest,
|
|
137
|
+
reason: AdvisorTrigger,
|
|
138
|
+
question: string | undefined,
|
|
139
|
+
evidence?: AdvisorRepairEvidence,
|
|
140
|
+
): Promise<AdvisorConsultOutcome> => {
|
|
141
|
+
const current = liveSettings();
|
|
142
|
+
if (!current.enabled || current.model.trim() === "") {
|
|
143
|
+
return {
|
|
144
|
+
ok: false,
|
|
145
|
+
isError: true,
|
|
146
|
+
blocked: "settings",
|
|
147
|
+
content: formatAdvisorUnavailable(
|
|
148
|
+
reason === "manual" ? "manual" : "preflight",
|
|
149
|
+
"advisor is not configured",
|
|
150
|
+
),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
// Consent is operator config. Task text, the worker, and the advisor cannot grant it.
|
|
154
|
+
// The live object is re-read here so a revocation after plan creation still blocks
|
|
155
|
+
// outbound transfer. No fingerprint, no ledger write, no fetch.
|
|
156
|
+
if (current.contextSharingConsent === null) {
|
|
157
|
+
console.warn(
|
|
158
|
+
`[advisor] consultation blocked trigger=${reason} reason=advisor_context_sharing_consent_required`,
|
|
159
|
+
);
|
|
160
|
+
return {
|
|
161
|
+
ok: false,
|
|
162
|
+
isError: true,
|
|
163
|
+
blocked: "consent",
|
|
164
|
+
content: formatAdvisorUnavailable(
|
|
165
|
+
"consent",
|
|
166
|
+
"advisor_context_sharing_consent_required",
|
|
167
|
+
),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
// Same-consultation dedup within this request: identical trigger + focus returns a
|
|
171
|
+
// non-advice outcome instead of a second expert call.
|
|
172
|
+
const fingerprint = `${reason}|${question ?? ""}|${evidence?.id ?? ""}`;
|
|
173
|
+
let result;
|
|
174
|
+
if (fingerprints.has(fingerprint)) {
|
|
175
|
+
result = {
|
|
176
|
+
ok: false,
|
|
177
|
+
advice: "",
|
|
178
|
+
advisorModel: current.model,
|
|
179
|
+
error: "duplicate consultation request (already consulted with this focus in this request)",
|
|
180
|
+
durationMs: 0,
|
|
181
|
+
};
|
|
182
|
+
return {
|
|
183
|
+
ok: false,
|
|
184
|
+
isError: true,
|
|
185
|
+
content: formatAdvisorUnavailable(reason === "manual" ? "manual" : "preflight", result.error),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (deps.abortSignal?.aborted) return { ok: false, isError: true, cancelled: true, content: "" };
|
|
189
|
+
const budgetKey = identify(parsed).budget;
|
|
190
|
+
const requestCapped = consultations >= MAX_ADVISOR_CONSULTATIONS_PER_REQUEST;
|
|
191
|
+
if (requestCapped
|
|
192
|
+
|| (current.policy === "adaptive" && budgetKey && !budget.reserve(budgetKey, current.maxConsultationsPerTask, now()).ok)) {
|
|
193
|
+
const id = activities.start({ trigger: reason, workerModel: deps.workerModelId, advisorModel: current.model, requestedEffort: current.effort }, now());
|
|
194
|
+
const blocked = requestCapped ? "limit" : "budget";
|
|
195
|
+
activities.finish(id, { status: "blocked", durationMs: 0, blockReason: blocked });
|
|
196
|
+
return { ok: false, isError: true, blocked, content: formatAdvisorUnavailable(blocked, requestCapped
|
|
197
|
+
? "per-request consultation limit reached; no consultation sent"
|
|
198
|
+
: "task budget exhausted or unavailable; no consultation sent") };
|
|
199
|
+
}
|
|
200
|
+
consultations += 1;
|
|
201
|
+
fingerprints.add(fingerprint);
|
|
202
|
+
const startedAt = now();
|
|
203
|
+
const activityId = activities.start({ trigger: reason, workerModel: deps.workerModelId, advisorModel: current.model, requestedEffort: current.effort }, startedAt);
|
|
204
|
+
try {
|
|
205
|
+
result = await consultAdvisor(
|
|
206
|
+
{
|
|
207
|
+
parsed,
|
|
208
|
+
workerIdentity: deps.workerIdentity,
|
|
209
|
+
advisorModel: current.model,
|
|
210
|
+
reason,
|
|
211
|
+
...(evidence ? { evidence } : {}),
|
|
212
|
+
...(question !== undefined ? { question } : {}),
|
|
213
|
+
},
|
|
214
|
+
deps.config,
|
|
215
|
+
current.effort,
|
|
216
|
+
current.timeoutMs,
|
|
217
|
+
deps.abortSignal,
|
|
218
|
+
deps.baseUrlOverride,
|
|
219
|
+
);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
activities.finish(activityId, { status: "failed", durationMs: Math.max(0, now() - startedAt) });
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
activities.finish(activityId, {
|
|
225
|
+
status: result.ok ? "ok" : result.cancelled ? "cancelled" : "failed",
|
|
226
|
+
durationMs: result.durationMs, effectiveEffort: result.effectiveEffort ?? null,
|
|
227
|
+
...(result.usage ? { usage: result.usage } : {}),
|
|
228
|
+
});
|
|
229
|
+
logConsultation(reason, result, current.model);
|
|
230
|
+
|
|
231
|
+
if (result.ok) {
|
|
232
|
+
// A genuine result suppresses further automatic consultation for the task. Manual success
|
|
233
|
+
// settles it too: a task the worker already had advised does not need a preflight attempt.
|
|
234
|
+
if (reason === "manual") {
|
|
235
|
+
const key = taskKey(parsed);
|
|
236
|
+
// A manual consultation owns no preflight claim; this records the FACT that the task was
|
|
237
|
+
// advised, which is true whichever consultation produced the advice.
|
|
238
|
+
if (key) ledger.markAdvised(key, now());
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
ok: true,
|
|
242
|
+
isError: false,
|
|
243
|
+
content: formatAdvisorAdvice({
|
|
244
|
+
advisorModel: result.advisorModel,
|
|
245
|
+
reason,
|
|
246
|
+
advice: result.advice,
|
|
247
|
+
channel: reason === "manual" ? "manual" : "preflight",
|
|
248
|
+
}),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
ok: false,
|
|
253
|
+
isError: true,
|
|
254
|
+
...(result.cancelled ? { cancelled: true } : {}),
|
|
255
|
+
content: formatAdvisorUnavailable(reason === "manual" ? "manual" : "preflight", result.error ?? "unavailable"),
|
|
256
|
+
};
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const preflightInject = async (parsed: OcxParsedRequest): Promise<boolean> => {
|
|
260
|
+
const task = identify(parsed);
|
|
261
|
+
const current = liveSettings();
|
|
262
|
+
if (current.policy === "manual" || preflightUsed || deps.abortSignal?.aborted) return false;
|
|
263
|
+
// No consent, disabled, or no model: do not claim, do not inject, do not send task context.
|
|
264
|
+
if (!current.enabled || current.model.trim() === "" || current.contextSharingConsent === null) return false;
|
|
265
|
+
// A genuine MANUAL consultation already advised this task (verifiable tool-result
|
|
266
|
+
// provenance), or the task has no orientation evidence yet: skip.
|
|
267
|
+
// Adaptive automation needs a stable identity for its durable task budget. Legacy
|
|
268
|
+
// preflight keeps its documented identity-less, request-scoped behavior.
|
|
269
|
+
if (current.policy === "adaptive" && !task.budget) return false;
|
|
270
|
+
const evidence = current.policy === "adaptive" ? collectAdvisorRepairEvidence(parsed) : undefined;
|
|
271
|
+
const reason = evidence ? "repair_failure" : "preflight";
|
|
272
|
+
if (!evidence && (historyHasManualAdvisorResult(parsed) || !hasOrientationEvidence(parsed))) return false;
|
|
273
|
+
|
|
274
|
+
// Atomic claim. A client with a stable conversation identity participates in the
|
|
275
|
+
// process-global ledger, so concurrent requests for one task consult at most once and a
|
|
276
|
+
// settled task is not re-consulted. A client with NO stable identity stays out of the
|
|
277
|
+
// ledger on purpose: request-scoped dedup plus genuine in-history provenance are the only
|
|
278
|
+
// suppression it gets — fail-open, so two independent identity-less conversations can never
|
|
279
|
+
// suppress each other through a shared guess.
|
|
280
|
+
const baseKey = taskKey(parsed);
|
|
281
|
+
const key = evidence && baseKey ? `${baseKey}:repair:${evidence.id}` : baseKey;
|
|
282
|
+
let claimToken: string | undefined;
|
|
283
|
+
if (key) {
|
|
284
|
+
const claim = ledger.claim(key, now());
|
|
285
|
+
if (claim.state !== "claimed") return false;
|
|
286
|
+
claimToken = claim.token;
|
|
287
|
+
}
|
|
288
|
+
preflightUsed = true;
|
|
289
|
+
deps.afterPreflightClaim?.();
|
|
290
|
+
|
|
291
|
+
let outcome: AdvisorConsultOutcome;
|
|
292
|
+
try {
|
|
293
|
+
outcome = await runConsultation(parsed, reason, undefined, evidence);
|
|
294
|
+
} catch (error) {
|
|
295
|
+
if (key && claimToken) ledger.fail(key, claimToken, now());
|
|
296
|
+
console.warn(`[advisor] consultation failed trigger=${reason} worker=${deps.workerModelId} error=plan_threw`);
|
|
297
|
+
parsed.context.messages = [
|
|
298
|
+
...parsed.context.messages,
|
|
299
|
+
{
|
|
300
|
+
role: "user",
|
|
301
|
+
content: "An automatic advisor consultation could not be completed. Continue with your own judgment.",
|
|
302
|
+
timestamp: Date.now(),
|
|
303
|
+
},
|
|
304
|
+
];
|
|
305
|
+
void error;
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (outcome.blocked) {
|
|
310
|
+
// Operator revoked consent (or disabled the sidecar) after the claim: not a provider
|
|
311
|
+
// failure. Release so the task is not put on cooldown and nothing is injected.
|
|
312
|
+
if (key && claimToken) ledger.release(key, claimToken, now());
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
if (outcome.ok) {
|
|
316
|
+
if (key && claimToken) ledger.complete(key, claimToken, now());
|
|
317
|
+
} else if (outcome.cancelled) {
|
|
318
|
+
// Client cancellation is not a provider failure: no cooldown, the task may retry later.
|
|
319
|
+
if (key && claimToken) ledger.release(key, claimToken, now());
|
|
320
|
+
// Nothing to inject — the caller is gone or aborting; do not add noise to a live turn.
|
|
321
|
+
return false;
|
|
322
|
+
} else {
|
|
323
|
+
if (key && claimToken) ledger.fail(key, claimToken, now());
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
parsed.context.messages = [
|
|
327
|
+
...parsed.context.messages,
|
|
328
|
+
...advisorPreflightMessages(outcome.content),
|
|
329
|
+
];
|
|
330
|
+
return outcome.ok;
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const plan: AdvisorPlan = {
|
|
334
|
+
consult: (parsed, reason, question) => runConsultation(parsed, reason, question),
|
|
335
|
+
// The guard's own failure/limit text goes through the same runtime-owned, marker-neutralized
|
|
336
|
+
// formatter so no guard path can emit text that looks like a genuine advice wrapper.
|
|
337
|
+
formatUnavailable: (kind, error) => formatAdvisorUnavailable(kind, error),
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
policy: initial.policy,
|
|
342
|
+
// The synthetic tool is only safe where the guard can intercept: run-turn adapters own their
|
|
343
|
+
// own loops, so they get preflight support but never the tool (documented limitation).
|
|
344
|
+
toolEnabled: initial.enabled,
|
|
345
|
+
tool: buildAdvisorTool(),
|
|
346
|
+
consult: plan.consult,
|
|
347
|
+
formatUnavailable: plan.formatUnavailable,
|
|
348
|
+
preflightInject,
|
|
349
|
+
attachGuard: parsed => {
|
|
350
|
+
parsed._advisorGuard = createAdvisorGuard(plan);
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single reader of `advisor` config.
|
|
3
|
+
*
|
|
4
|
+
* Nothing else reads the `advisor` key directly: the sidecar planner, the management API and the
|
|
5
|
+
* CLI all resolve through here, so they cannot disagree about defaults or validity. Type-only
|
|
6
|
+
* config import keeps this module free of runtime edges.
|
|
7
|
+
*/
|
|
8
|
+
import type { OcxConfig } from "../types";
|
|
9
|
+
import { ADVISOR_CONTEXT_SHARING_CONSENT_VERSION } from "./disclosure";
|
|
10
|
+
|
|
11
|
+
export { ADVISOR_CONTEXT_SHARING_CONSENT_VERSION };
|
|
12
|
+
|
|
13
|
+
export type AdvisorPolicy = "manual" | "preflight" | "adaptive";
|
|
14
|
+
|
|
15
|
+
export const ADVISOR_EFFORTS = ["low", "medium", "high", "xhigh", "max", "ultra"] as const;
|
|
16
|
+
export type AdvisorEffort = (typeof ADVISOR_EFFORTS)[number];
|
|
17
|
+
|
|
18
|
+
export const ADVISOR_POLICIES = ["manual", "preflight", "adaptive"] as const;
|
|
19
|
+
|
|
20
|
+
export interface AdvisorSettings {
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
model: string;
|
|
23
|
+
effort: AdvisorEffort;
|
|
24
|
+
policy: AdvisorPolicy;
|
|
25
|
+
timeoutMs: number;
|
|
26
|
+
/** Adaptive task budget, including manual calls. Stable task identity is required. */
|
|
27
|
+
maxConsultationsPerTask: number;
|
|
28
|
+
/**
|
|
29
|
+
* Current context-sharing consent, or null when absent, stale, or malformed.
|
|
30
|
+
* `enabled` does not imply this. Only the current version allows data transfer.
|
|
31
|
+
*/
|
|
32
|
+
contextSharingConsent: typeof ADVISOR_CONTEXT_SHARING_CONSENT_VERSION | null;
|
|
33
|
+
/** Where each resolved value came from, so the GUI/CLI can show real runtime state. */
|
|
34
|
+
sources: {
|
|
35
|
+
enabled: "default" | "configured";
|
|
36
|
+
model: "default" | "configured";
|
|
37
|
+
effort: "default" | "configured";
|
|
38
|
+
policy: "default" | "configured";
|
|
39
|
+
contextSharingConsent: "default" | "configured";
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const DEFAULT_ADVISOR_SETTINGS: Readonly<AdvisorSettings> = Object.freeze({
|
|
44
|
+
enabled: false,
|
|
45
|
+
model: "",
|
|
46
|
+
effort: "max",
|
|
47
|
+
policy: "manual",
|
|
48
|
+
timeoutMs: 120_000,
|
|
49
|
+
maxConsultationsPerTask: 3,
|
|
50
|
+
contextSharingConsent: null,
|
|
51
|
+
sources: Object.freeze({
|
|
52
|
+
enabled: "default",
|
|
53
|
+
model: "default",
|
|
54
|
+
effort: "default",
|
|
55
|
+
policy: "default",
|
|
56
|
+
contextSharingConsent: "default",
|
|
57
|
+
}),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
type Rec = Record<string, unknown>;
|
|
61
|
+
function isRec(value: unknown): value is Rec {
|
|
62
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function isValidAdvisorEffort(value: unknown): value is AdvisorEffort {
|
|
66
|
+
return typeof value === "string" && (ADVISOR_EFFORTS as readonly string[]).includes(value);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function isValidAdvisorPolicy(value: unknown): value is AdvisorPolicy {
|
|
70
|
+
return typeof value === "string" && (ADVISOR_POLICIES as readonly string[]).includes(value);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function isValidAdvisorTaskLimit(value: unknown): value is number {
|
|
74
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 3;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** True only for the current context-sharing consent version. Anything else is not a grant. */
|
|
78
|
+
export function isCurrentAdvisorContextSharingConsent(
|
|
79
|
+
value: unknown,
|
|
80
|
+
): value is typeof ADVISOR_CONTEXT_SHARING_CONSENT_VERSION {
|
|
81
|
+
return value === ADVISOR_CONTEXT_SHARING_CONSENT_VERSION;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Resolve advisor settings with conservative defaults for every absent or malformed field.
|
|
86
|
+
* A malformed block resolves to fully disabled defaults rather than throwing: the advisor is
|
|
87
|
+
* optional and must never take the request path down with it.
|
|
88
|
+
*/
|
|
89
|
+
export function resolveAdvisorSettings(config: Pick<OcxConfig, "advisor">): AdvisorSettings {
|
|
90
|
+
const raw: unknown = config.advisor;
|
|
91
|
+
if (!isRec(raw)) return { ...DEFAULT_ADVISOR_SETTINGS, sources: { ...DEFAULT_ADVISOR_SETTINGS.sources } };
|
|
92
|
+
const enabled = typeof raw.enabled === "boolean" ? raw.enabled : DEFAULT_ADVISOR_SETTINGS.enabled;
|
|
93
|
+
const model = typeof raw.model === "string" ? raw.model.trim() : DEFAULT_ADVISOR_SETTINGS.model;
|
|
94
|
+
const effort = isValidAdvisorEffort(raw.effort) ? raw.effort : DEFAULT_ADVISOR_SETTINGS.effort;
|
|
95
|
+
const policy = isValidAdvisorPolicy(raw.policy) ? raw.policy : DEFAULT_ADVISOR_SETTINGS.policy;
|
|
96
|
+
const timeoutRaw = raw.timeoutMs;
|
|
97
|
+
const timeoutMs = typeof timeoutRaw === "number" && Number.isFinite(timeoutRaw) && timeoutRaw >= 1_000
|
|
98
|
+
? Math.min(Math.floor(timeoutRaw), 600_000)
|
|
99
|
+
: DEFAULT_ADVISOR_SETTINGS.timeoutMs;
|
|
100
|
+
// A present but non-current value (stale version, wrong type) resolves as no consent.
|
|
101
|
+
// The stored bytes are left untouched; this reader never writes an upgrade.
|
|
102
|
+
const consentConfigured = Object.prototype.hasOwnProperty.call(raw, "contextSharingConsent");
|
|
103
|
+
const contextSharingConsent = isCurrentAdvisorContextSharingConsent(raw.contextSharingConsent)
|
|
104
|
+
? raw.contextSharingConsent
|
|
105
|
+
: null;
|
|
106
|
+
return {
|
|
107
|
+
enabled,
|
|
108
|
+
model,
|
|
109
|
+
effort,
|
|
110
|
+
policy,
|
|
111
|
+
timeoutMs,
|
|
112
|
+
maxConsultationsPerTask: isValidAdvisorTaskLimit(raw.maxConsultationsPerTask)
|
|
113
|
+
? raw.maxConsultationsPerTask : DEFAULT_ADVISOR_SETTINGS.maxConsultationsPerTask,
|
|
114
|
+
contextSharingConsent,
|
|
115
|
+
sources: {
|
|
116
|
+
enabled: typeof raw.enabled === "boolean" ? "configured" : "default",
|
|
117
|
+
model: typeof raw.model === "string" && raw.model.trim() !== "" ? "configured" : "default",
|
|
118
|
+
effort: isValidAdvisorEffort(raw.effort) ? "configured" : "default",
|
|
119
|
+
policy: isValidAdvisorPolicy(raw.policy) ? "configured" : "default",
|
|
120
|
+
contextSharingConsent: consentConfigured ? "configured" : "default",
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Whether a consultation may send task context. Requires the switch, a model, and the
|
|
127
|
+
* current context-sharing consent. `enabled` is not consent. A miss fails closed for the
|
|
128
|
+
* data transfer and leaves the worker request itself running.
|
|
129
|
+
*/
|
|
130
|
+
export function advisorRunnable(settings: AdvisorSettings): boolean {
|
|
131
|
+
return settings.enabled
|
|
132
|
+
&& settings.model.trim() !== ""
|
|
133
|
+
&& settings.contextSharingConsent === ADVISOR_CONTEXT_SHARING_CONSENT_VERSION;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Enabled, with a model, but without current consent. The management surface reports
|
|
138
|
+
* `advisor_context_sharing_consent_required` and the runtime sends no task context.
|
|
139
|
+
*/
|
|
140
|
+
export function advisorContextSharingBlocked(settings: AdvisorSettings): boolean {
|
|
141
|
+
return settings.enabled
|
|
142
|
+
&& settings.model.trim() !== ""
|
|
143
|
+
&& settings.contextSharingConsent !== ADVISOR_CONTEXT_SHARING_CONSENT_VERSION;
|
|
144
|
+
}
|