@bitkyc08/opencodex 2.83.0 → 2.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -4
- package/bin/ocx.mjs +61 -3
- package/gui/dist/assets/App-BjlttVLy.js +58 -0
- package/gui/dist/assets/Tray-DyR9yBee.js +1 -0
- package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
- package/gui/dist/assets/index-BMssP4k3.js +94 -0
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +2 -3
- package/src/adapters/anthropic/beta-allowlist.ts +87 -8
- package/src/adapters/anthropic/client-identity.ts +1 -1
- package/src/adapters/anthropic/passthrough.ts +33 -4
- package/src/adapters/azure.ts +19 -1
- package/src/adapters/client-fingerprint.ts +24 -0
- package/src/adapters/coding-agent/protocol.ts +15 -8
- package/src/adapters/coding-agent/turn.ts +22 -9
- package/src/adapters/cursor/arg-codec.ts +17 -5
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/cursor/freeform-args.ts +75 -0
- package/src/adapters/cursor/protobuf-events.ts +36 -23
- package/src/adapters/google-antigravity-wire.ts +22 -1
- package/src/adapters/google.ts +2 -2
- package/src/adapters/ollama-native.ts +29 -1
- package/src/adapters/openai-chat/messages.ts +8 -4
- package/src/adapters/openai-chat/response-events.ts +59 -1
- package/src/adapters/openai-chat.ts +8 -5
- package/src/adapters/openai-responses/empty-assistant.ts +32 -0
- package/src/adapters/openai-responses/passthrough.ts +3 -0
- package/src/adapters/openai-responses/request-strips.ts +10 -2
- package/src/adapters/qoder/adapter.ts +56 -15
- package/src/advisor/consult.ts +220 -0
- package/src/advisor/context.ts +226 -0
- package/src/advisor/disclosure.ts +20 -0
- package/src/advisor/runtime.ts +305 -0
- package/src/advisor/settings.ts +135 -0
- package/src/advisor/state.ts +369 -0
- package/src/advisor/synthetic-tool.ts +33 -0
- package/src/bridge/errors.ts +24 -0
- package/src/bridge.ts +1 -1
- package/src/chat/inbound.ts +30 -6
- package/src/chat/outbound.ts +5 -0
- package/src/claude/context-windows.ts +54 -1
- package/src/claude/desktop-3p-library.ts +43 -3
- package/src/claude/desktop-3p.ts +22 -8
- package/src/claude/desktop-picker-profile.ts +11 -4
- package/src/claude/desktop-profile.ts +10 -1
- package/src/claude/desktop-remote-store-io.ts +5 -3
- package/src/claude/inbound.ts +121 -50
- package/src/claude/intercept/connect-proxy.ts +10 -3
- package/src/claude/intercept/local-ca.ts +8 -1
- package/src/claude/intercept/picker-ca-persistence.ts +3 -1
- package/src/claude/intercept/picker-listener.ts +49 -1
- package/src/claude/intercept/settings-no-proxy.ts +146 -0
- package/src/claude/intercept/settings.ts +101 -9
- package/src/claude/model-default-settings.ts +154 -0
- package/src/claude/model-info.ts +7 -7
- package/src/claude/outbound.ts +13 -6
- package/src/cli/account-api.ts +65 -0
- package/src/cli/account-auth.ts +26 -2
- package/src/cli/account.ts +41 -5
- package/src/cli/advisor.ts +151 -0
- package/src/cli/auxiliary-listener-preflight.ts +37 -0
- package/src/cli/capabilities-access-remote.ts +8 -0
- package/src/cli/capabilities-agents-routing.ts +18 -0
- package/src/cli/capabilities-base.ts +14 -2
- package/src/cli/catalog-command-result.ts +2 -1
- package/src/cli/chatgpt-command.ts +35 -14
- package/src/cli/claude.ts +60 -11
- package/src/cli/config-command.ts +8 -2
- package/src/cli/debug.ts +8 -0
- package/src/cli/dispatch.ts +21 -4
- package/src/cli/doctor.ts +9 -0
- package/src/cli/ensure-desktop-cold-start.ts +329 -0
- package/src/cli/export-command.ts +15 -2
- package/src/cli/help.ts +2 -1
- package/src/cli/hub-disable.ts +99 -0
- package/src/cli/hub.ts +4 -2
- package/src/cli/index.ts +26 -8
- package/src/cli/integration-plan-dto.ts +2 -0
- package/src/cli/link-child.ts +104 -0
- package/src/cli/link-enrollment.ts +96 -0
- package/src/cli/link.ts +39 -24
- package/src/cli/observe-stream.ts +6 -1
- package/src/cli/observe.ts +10 -2
- package/src/cli/provider.ts +12 -0
- package/src/cli/registry.ts +20 -7
- package/src/cli/restart-scope.ts +3 -1
- package/src/cli/runtime-api.ts +27 -2
- package/src/cli/status.ts +104 -20
- package/src/client/connect.ts +39 -4
- package/src/client/hub-client.ts +14 -7
- package/src/client/hub-state.ts +25 -14
- package/src/client/link-home.ts +248 -0
- package/src/client/link-management-relay.ts +61 -0
- package/src/client/link-relay.ts +6 -1
- package/src/client/link-status.ts +14 -0
- package/src/client/machine-api.ts +14 -7
- package/src/client/machine-listener.ts +29 -4
- package/src/clients/config-export/contracts.ts +1 -0
- package/src/clients/config-export/model-metadata.ts +3 -2
- package/src/clients/config-export/qoder.ts +93 -0
- package/src/clients/config-export.ts +59 -16
- package/src/clients/omo-role-models.ts +6 -3
- package/src/codex/agent-role-models.ts +26 -8
- package/src/codex/auth-api/account-list.ts +17 -2
- package/src/codex/auth-api/account-selection.ts +17 -7
- package/src/codex/auth-api/pool-mode-gate.ts +5 -2
- package/src/codex/auth-api/routes.ts +5 -1
- package/src/codex/auth-context.ts +79 -5
- package/src/codex/autostart-health.ts +7 -1
- package/src/codex/catalog/build-entries.ts +11 -2
- package/src/codex/catalog/bundled.ts +5 -1
- package/src/codex/catalog/configured-tiers.ts +50 -0
- package/src/codex/catalog/effort.ts +6 -3
- package/src/codex/catalog/metadata.ts +4 -2
- package/src/codex/catalog/model-hints.ts +21 -3
- package/src/codex/catalog/native-models.ts +16 -2
- package/src/codex/catalog/native-ultrafast.ts +106 -0
- package/src/codex/catalog/parsing.ts +5 -0
- package/src/codex/catalog/provider-models.ts +4 -0
- package/src/codex/catalog/retained-sync.ts +2 -0
- package/src/codex/catalog/routed-gather.ts +22 -4
- package/src/codex/catalog/vendor-context.ts +82 -0
- package/src/codex/config-write-lock.ts +40 -2
- package/src/codex/convergence.ts +2 -0
- package/src/codex/desktop-app/types.ts +2 -0
- package/src/codex/desktop-app/windows.ts +29 -16
- package/src/codex/desktop-app-restart.ts +4 -2
- package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
- package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
- package/src/codex/desktop-compatibility/connection-store.ts +87 -0
- package/src/codex/desktop-compatibility/installed-build.ts +55 -0
- package/src/codex/desktop-compatibility/json-body.ts +29 -0
- package/src/codex/desktop-compatibility/native-identity.ts +62 -0
- package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
- package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
- package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
- package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
- package/src/codex/desktop-compatibility/runtime.ts +173 -0
- package/src/codex/desktop-compatibility/service.ts +30 -0
- package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
- package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
- package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
- package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
- package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
- package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
- package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
- package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
- package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
- package/src/codex/desktop-compatibility/windows-key-protection.ts +59 -0
- package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
- package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
- package/src/codex/history-provider.ts +1 -1
- package/src/codex/inject/config-write-section.ts +4 -1
- package/src/codex/inject/remove.ts +18 -6
- package/src/codex/journal.ts +16 -1
- package/src/codex/main-account.ts +99 -15
- package/src/codex/native-main-refresh-mode.ts +217 -0
- package/src/codex/native-profile-startup.ts +4 -1
- package/src/codex/prompt-journal.ts +20 -2
- package/src/codex/prompt-layers/encoding.ts +49 -0
- package/src/codex/prompt-layers/import-source.ts +30 -0
- package/src/codex/prompt-layers/toml-edit.ts +269 -37
- package/src/codex/prompt-layers/toml-read.ts +52 -4
- package/src/codex/prompt-layers.ts +383 -20
- package/src/codex/prompt-lock-claim.ts +13 -3
- package/src/codex/prompt-lock-evidence.ts +3 -3
- package/src/codex/prompt-lock-owner.ts +14 -6
- package/src/codex/prompt-text-probe.ts +34 -5
- package/src/codex/quota.ts +13 -6
- package/src/codex/role-auto-assign.ts +2 -5
- package/src/codex/routing/active-account.ts +21 -0
- package/src/codex/routing/health-store.ts +1 -1
- package/src/codex/routing/pool-recovery.ts +79 -0
- package/src/codex/routing.ts +1 -1
- package/src/codex/runtime.ts +196 -8
- package/src/codex/shim-diagnostics.ts +6 -0
- package/src/codex/shim-overlay.ts +15 -6
- package/src/codex/shim.ts +1 -1
- package/src/combos/failover.ts +3 -0
- package/src/config/api-key-reconcile.ts +34 -0
- package/src/config/diagnostics.ts +18 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/live-reconcile.ts +56 -29
- package/src/config/live-topology.ts +29 -0
- package/src/config/load-degrade.ts +8 -2
- package/src/config/paths.ts +13 -3
- package/src/config/salvage.ts +3 -1
- package/src/config/schema/config-schema.ts +19 -0
- package/src/config/schema/desktop-compatibility.ts +9 -0
- package/src/config/schema/leaf-validators.ts +1 -0
- package/src/generated/compatibility-version.json +713 -285
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/grpc-web.ts +23 -1
- package/src/grok/reset-coupon-ledger.ts +75 -5
- package/src/grok/reset-coupons.ts +4 -2
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/config-io.ts +20 -2
- package/src/integrations/journal.ts +15 -1
- package/src/integrations/mutation-plan.ts +14 -7
- package/src/integrations/registry.ts +52 -11
- package/src/integrations/state.ts +19 -7
- package/src/integrations/store.ts +3 -0
- package/src/integrations/target.ts +42 -5
- package/src/integrations/writer.ts +24 -7
- package/src/lib/advisor-activation.ts +16 -0
- package/src/lib/app-owned-memory-stores.ts +4 -0
- package/src/lib/app-owned-memory.ts +11 -0
- package/src/lib/background-work.ts +65 -0
- package/src/lib/bounded-file-read.ts +58 -0
- package/src/lib/bun-path-runtime.d.mts +10 -1
- package/src/lib/bun-path-runtime.mjs +62 -3
- package/src/lib/desktop-proxy-route.ts +30 -0
- package/src/lib/desktop-upstream-tunnel.ts +107 -0
- package/src/lib/errors.ts +7 -0
- package/src/lib/json-byte-size.ts +31 -0
- package/src/lib/local-internal-call-capability.ts +55 -0
- package/src/lib/optional-shutdown-hooks.ts +35 -0
- package/src/lib/outbound-credential-registry.ts +210 -0
- package/src/lib/package-tree-integrity.ts +2 -2
- package/src/lib/passive-read.ts +35 -0
- package/src/lib/shadow-call.ts +44 -5
- package/src/lib/socks5-fetch.ts +48 -71
- package/src/lib/socks5-handshake.ts +112 -0
- package/src/lib/translator-budget.ts +7 -1
- package/src/link/codex-settings.ts +155 -0
- package/src/link/management-routes.ts +89 -0
- package/src/link/remote-probe.ts +38 -0
- package/src/link/ssh-argv.ts +26 -5
- package/src/link/ssh-runner.ts +58 -27
- package/src/link/status-projection.ts +22 -4
- package/src/link/supervisor.ts +144 -49
- package/src/link/tunnel-state.ts +11 -4
- package/src/oauth/anthropic-continuity.ts +4 -3
- package/src/oauth/anthropic-identity.ts +13 -2
- package/src/oauth/anthropic-model-quota.ts +13 -1
- package/src/oauth/anthropic-routing.ts +230 -23
- package/src/oauth/anthropic.ts +18 -4
- package/src/oauth/generic-account-failover.ts +29 -5
- package/src/oauth/google-antigravity.ts +24 -7
- package/src/oauth/index.ts +28 -17
- package/src/oauth/kiro-account-identity.ts +106 -0
- package/src/oauth/kiro-device-login.ts +2 -1
- package/src/oauth/kiro.ts +55 -6
- package/src/oauth/plan.ts +29 -0
- package/src/oauth/store.ts +40 -3
- package/src/oauth/types.ts +2 -0
- package/src/plugins/loader.ts +11 -0
- package/src/protocols/codecs/chat-ir.ts +467 -0
- package/src/protocols/codecs/chat.ts +10 -1
- package/src/protocols/codecs/messages-ir.ts +500 -0
- package/src/protocols/features.ts +10 -2
- package/src/protocols/path.ts +11 -1
- package/src/protocols/plan-snapshot.ts +39 -16
- package/src/protocols/plan.ts +9 -1
- package/src/protocols/settings.ts +8 -0
- package/src/providers/anthropic-billing-ownership.ts +74 -0
- package/src/providers/anthropic-billing-recovery.ts +31 -0
- package/src/providers/anthropic-billing-refusal.ts +55 -0
- package/src/providers/anthropic-reset-admission.ts +62 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +28 -6
- package/src/providers/antigravity-account-roster.ts +102 -0
- package/src/providers/antigravity-models.ts +85 -7
- package/src/providers/api-key-resolve.ts +17 -2
- package/src/providers/key-failover.ts +52 -5
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/model-rename-migration.ts +5 -3
- package/src/providers/model-rename-startup.ts +5 -3
- package/src/providers/quota/account-cache.ts +11 -0
- package/src/providers/quota/anthropic-account-quota.ts +4 -2
- package/src/providers/quota/report-cache.ts +14 -0
- package/src/providers/quota/vendor-probes-oauth.ts +3 -3
- package/src/providers/quota.ts +4 -1
- package/src/providers/registry/entries-core.ts +21 -20
- package/src/providers/registry/entries-extended.ts +19 -1
- package/src/providers/registry/model-seeds.ts +17 -0
- package/src/providers/request-pacing.ts +9 -0
- package/src/providers/stale-no-vision-seed-migration.ts +72 -0
- package/src/responses/compaction.ts +14 -0
- package/src/responses/parser.ts +37 -20
- package/src/responses/state.ts +32 -29
- package/src/routing/history/build-file.ts +92 -0
- package/src/routing/history/indexer.ts +248 -216
- package/src/routing/history/ingest.ts +238 -0
- package/src/routing/history/rebuild-job.ts +111 -0
- package/src/routing/history/rebuild-protocol.ts +15 -0
- package/src/routing/history/rebuild-worker.ts +34 -0
- package/src/server/admission-model-scope.ts +4 -1
- package/src/server/antigravity-direct-eligibility.ts +208 -0
- package/src/server/auth-cors.ts +46 -0
- package/src/server/chat-completions.ts +75 -9
- package/src/server/claude-messages.ts +110 -20
- package/src/server/configured-credentials.ts +194 -0
- package/src/server/index/desktop-compatibility-startup.ts +56 -0
- package/src/server/index/link-management.ts +58 -0
- package/src/server/index/optional-listeners.ts +5 -1
- package/src/server/index/serve-options.ts +15 -2
- package/src/server/index/startup-warnings.ts +9 -0
- package/src/server/index/websocket-handler.ts +9 -0
- package/src/server/index.ts +13 -10
- package/src/server/inference/client-encoder-delivery.ts +6 -5
- package/src/server/lifecycle.ts +2 -1
- package/src/server/login-shell-path.ts +146 -0
- package/src/server/management/advisor-routes.ts +191 -0
- package/src/server/management/agent-settings-routes.ts +50 -14
- package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
- package/src/server/management/codex-agent-role-routes.ts +19 -6
- package/src/server/management/codex-prompt-routes.ts +136 -5
- package/src/server/management/codex-role-auto-assign.ts +1 -1
- package/src/server/management/companion-routes.ts +4 -0
- package/src/server/management/config-routes.ts +2 -2
- package/src/server/management/context.ts +4 -0
- package/src/server/management/desktop-compatibility-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
- package/src/server/management/grok-coupon-routes.ts +78 -27
- package/src/server/management/link-enrollment.ts +192 -0
- package/src/server/management/link-route-state.ts +185 -0
- package/src/server/management/link-routes.ts +82 -402
- package/src/server/management/model-routes.ts +2 -2
- package/src/server/management/model-rows.ts +3 -1
- package/src/server/management/protocol-settings-patch.ts +1 -0
- package/src/server/management/provider-overwrite-carry.ts +10 -0
- package/src/server/management/provider-routes.ts +1 -0
- package/src/server/management/route-registry.ts +16 -3
- package/src/server/management/shared.ts +3 -0
- package/src/server/management/sibling-guard.ts +1 -0
- package/src/server/management-api.ts +9 -1
- package/src/server/messages-antigravity-direct.ts +195 -0
- package/src/server/messages-native.ts +73 -43
- package/src/server/relay-eager.ts +2 -0
- package/src/server/request-log-filter.ts +6 -1
- package/src/server/request-log-terminal-status.ts +63 -6
- package/src/server/request-log.ts +7 -0
- package/src/server/responses/adapter-continuation.ts +35 -18
- package/src/server/responses/adapter-delivery.ts +24 -2
- package/src/server/responses/adapter-dispatch.ts +261 -81
- package/src/server/responses/advisor-plan-slot.ts +27 -0
- package/src/server/responses/advisor-slot.ts +336 -0
- package/src/server/responses/code-mode-collab-surface.ts +313 -0
- package/src/server/responses/codex-ws-exchange.ts +147 -6
- package/src/server/responses/codex-ws-wire.ts +203 -0
- package/src/server/responses/collaboration.ts +36 -7
- package/src/server/responses/compact.ts +126 -48
- package/src/server/responses/context-overflow.ts +13 -1
- package/src/server/responses/core-codex-account.ts +105 -1
- package/src/server/responses/core-combo-failure.ts +24 -1
- package/src/server/responses/core-combo.ts +28 -2
- package/src/server/responses/core-errors.ts +29 -1
- package/src/server/responses/core-lifetime.ts +2 -0
- package/src/server/responses/core-normalize.ts +11 -0
- package/src/server/responses/core-options.ts +48 -0
- package/src/server/responses/core-replay.ts +7 -4
- package/src/server/responses/core.ts +1 -1
- package/src/server/responses/deferred-collab-surface.ts +158 -0
- package/src/server/responses/deferred-send-reporter.ts +25 -0
- package/src/server/responses/fetch-helpers.ts +12 -1
- package/src/server/responses/passthrough-delivery.ts +20 -11
- package/src/server/responses/passthrough-dispatch.ts +29 -7
- package/src/server/responses/request-prepare.ts +74 -9
- package/src/server/responses/request-transport.ts +22 -8
- package/src/server/responses/run-turn-execution.ts +70 -8
- package/src/server/responses/sidecar-execution.ts +89 -5
- package/src/server/responses/terminal-guard.ts +2 -1
- package/src/server/responses/ws-upstream.ts +9 -1
- package/src/server/responses-undeclared-tool-guard.ts +30 -9
- package/src/server/startup-action-control.ts +38 -4
- package/src/server/startup-health-cache.ts +5 -0
- package/src/server/system-env-shell.ts +7 -2
- package/src/server/system-env.ts +11 -1
- package/src/service/cli.ts +5 -1
- package/src/service/desktop-startup.ts +161 -34
- package/src/service/desktop-supervision.d.mts +27 -1
- package/src/service/desktop-supervision.mjs +111 -5
- package/src/service/health-sentinel-launch.ts +176 -0
- package/src/service/health-sentinel-main.ts +466 -0
- package/src/service/health-sentinel-policy.ts +241 -0
- package/src/service/health-sentinel-probe.ts +71 -0
- package/src/service/health-sentinel-windows.ts +87 -0
- package/src/service/linux-linger.ts +20 -0
- package/src/service/logging.ts +72 -0
- package/src/tray/windows-tray.ps1 +43 -24
- package/src/types/config.ts +68 -6
- package/src/types/provider.ts +7 -0
- package/src/types/request.ts +15 -0
- package/src/types/tools.ts +2 -0
- package/src/update/async-check.ts +1 -1
- package/src/update/badge.ts +2 -2
- package/src/update/index.ts +88 -15
- package/src/update/job-supervised-restart.ts +107 -0
- package/src/update/job.ts +32 -32
- package/src/update/notify.ts +1 -1
- package/src/update/refresh-scheduler.ts +2 -2
- package/src/update/supervised-recovery.d.mts +27 -0
- package/src/update/supervised-recovery.mjs +157 -0
- package/src/usage/cache-diagnostic.ts +2 -0
- package/src/usage/expected-prices.ts +16 -0
- package/src/usage/log.ts +19 -1
- package/src/web-search/gemini-executor.ts +2 -2
- package/assets/sponsors/packycode.png +0 -0
- package/gui/dist/assets/App-Ba8uG696.js +0 -54
- package/gui/dist/assets/Tray-IImbNPRy.js +0 -1
- package/gui/dist/assets/index-BHRZdJuv.js +0 -94
- package/gui/dist/assets/quota-summary-DiJO6sYY.js +0 -1
|
@@ -30,11 +30,14 @@ import {
|
|
|
30
30
|
composeProjection,
|
|
31
31
|
computePromptProbeStateFingerprint,
|
|
32
32
|
findInvalidCharacter,
|
|
33
|
+
importBaseVariant,
|
|
33
34
|
inspectOwnership,
|
|
34
35
|
normalizeBody,
|
|
35
36
|
previewAdopt,
|
|
37
|
+
previewBaseImport,
|
|
36
38
|
previewSalvage,
|
|
37
39
|
readPromptLayers,
|
|
40
|
+
recoverPromptJournal,
|
|
38
41
|
salvageProjection,
|
|
39
42
|
selectBaseVariant,
|
|
40
43
|
setToggle,
|
|
@@ -76,6 +79,7 @@ const WRITE_ERROR_STATUS: Record<WriteError, number> = {
|
|
|
76
79
|
unknown_layer: 400,
|
|
77
80
|
store_unreadable: 409,
|
|
78
81
|
invalid_characters: 400,
|
|
82
|
+
body_too_large: 400,
|
|
79
83
|
write_superseded: 409,
|
|
80
84
|
// Not the caller's fault and not a race: the filesystem refused the write and the
|
|
81
85
|
// transaction rolled itself back. 500 rather than 409 — retrying the same request
|
|
@@ -84,6 +88,8 @@ const WRITE_ERROR_STATUS: Record<WriteError, number> = {
|
|
|
84
88
|
recovery_required: 409,
|
|
85
89
|
locked: 409,
|
|
86
90
|
unsafe: 422,
|
|
91
|
+
import_body_changed: 409,
|
|
92
|
+
unsupported_form: 409,
|
|
87
93
|
};
|
|
88
94
|
|
|
89
95
|
/** Read-only view for the route test that asserts every mapping is a client error. */
|
|
@@ -335,7 +341,12 @@ export async function handleCodexPromptRoutes(ctx: ManagementContext): Promise<R
|
|
|
335
341
|
if (!body) return fail(ctx, "invalid_body", 400, "expected a JSON object");
|
|
336
342
|
const id = body.id;
|
|
337
343
|
if (typeof id !== "string") return fail(ctx, "invalid_body", 400, "id must be a string");
|
|
338
|
-
|
|
344
|
+
// `null` is the restore-the-default verb: it deletes the line instead of
|
|
345
|
+
// writing the documented default back as a literal, so a later upstream
|
|
346
|
+
// change to the default still reaches this file.
|
|
347
|
+
if (typeof body.enabled !== "boolean" && body.enabled !== null) {
|
|
348
|
+
return fail(ctx, "invalid_body", 400, "enabled must be a boolean or null");
|
|
349
|
+
}
|
|
339
350
|
const revision = revisionOf(body);
|
|
340
351
|
if (!revision) return fail(ctx, "stale_revision", 409, "revision required");
|
|
341
352
|
|
|
@@ -388,6 +399,109 @@ export async function handleCodexPromptRoutes(ctx: ManagementContext): Promise<R
|
|
|
388
399
|
return settle(ctx, selectBaseVariant(selection, revision, paths(ctx)));
|
|
389
400
|
}
|
|
390
401
|
|
|
402
|
+
if (url.pathname === "/api/codex-prompt/base/import" && req.method === "POST") {
|
|
403
|
+
// The adopt flow for `model_instructions_file`: preview first, write only on
|
|
404
|
+
// confirmation. The verb exists because /base/select refuses the `external`
|
|
405
|
+
// state by design — that refusal is about SILENT retargeting, and an
|
|
406
|
+
// explicit, previewed import is precisely the opt-in it protects.
|
|
407
|
+
//
|
|
408
|
+
// The preview carries the SERIALIZED variant file — `# {title}\n{body}` —
|
|
409
|
+
// not just the body: the heading is part of what Codex will read, so the
|
|
410
|
+
// thing confirmed must be the whole stored text, title included. Both byte
|
|
411
|
+
// counts are reported and separately named: `bodyBytes` is the budget the
|
|
412
|
+
// 64 KiB cap applies to, `serializedBytes` is the complete file.
|
|
413
|
+
const body = await readBody(ctx);
|
|
414
|
+
if (!body) return fail(ctx, "invalid_body", 400, "expected a JSON object");
|
|
415
|
+
const readState = readPromptLayers(paths(ctx));
|
|
416
|
+
if (!readState.readable) {
|
|
417
|
+
return fail(ctx, "config_unreadable", 409, "the configuration file exists but could not be read", { path: readState.configPath });
|
|
418
|
+
}
|
|
419
|
+
// The title is validated before anything is previewed or written: a caller
|
|
420
|
+
// that supplies one gets the same contract as a custom-layer title —
|
|
421
|
+
// 1-80 characters on a single line.
|
|
422
|
+
if (body.title !== undefined) {
|
|
423
|
+
const t = body.title;
|
|
424
|
+
if (typeof t !== "string" || t.trim().length === 0
|
|
425
|
+
|| t.length > MAX_TITLE_CHARS || /[\r\n]/.test(t)) {
|
|
426
|
+
return fail(ctx, "invalid_title", 400, "title must be 1-80 characters on a single line");
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const preview = previewBaseImport(paths(ctx), typeof body.title === "string" ? body.title : undefined);
|
|
430
|
+
if (preview.reason === "nothing_to_import") {
|
|
431
|
+
return fail(ctx, "nothing_to_import", 409,
|
|
432
|
+
"model_instructions_file is absent or already points at a managed variant");
|
|
433
|
+
}
|
|
434
|
+
if (preview.reason === "unsupported_form") {
|
|
435
|
+
return fail(ctx, "import_unsupported_form", 409, preview.detail, {
|
|
436
|
+
path: preview.rawPath,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
if (preview.reason === "file_unreadable") {
|
|
440
|
+
return fail(ctx, "import_file_unreadable", 409, preview.detail, {
|
|
441
|
+
path: preview.resolvedPath ?? preview.rawPath,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
if (preview.reason === "invalid_characters") {
|
|
445
|
+
return fail(ctx, "invalid_characters", 400, preview.detail, { path: preview.resolvedPath });
|
|
446
|
+
}
|
|
447
|
+
// Same boundary as every other write to this key: the 64 KiB cap applies to
|
|
448
|
+
// the BODY alone — the title heading lives outside that budget, unchanged
|
|
449
|
+
// from the managed-variant contract.
|
|
450
|
+
if (preview.reason === "body_too_large" || utf8Bytes(preview.body!) > MAX_BODY_BYTES) {
|
|
451
|
+
return fail(ctx, "body_too_large", 400, `the file body exceeds ${MAX_BODY_BYTES} bytes`, {
|
|
452
|
+
path: preview.resolvedPath,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
if (preview.reason === "slots_full") {
|
|
456
|
+
return fail(ctx, "variant_slots_full", 409,
|
|
457
|
+
`at most ${MAX_BASE_VARIANTS} base variants; delete one first`, {
|
|
458
|
+
maxBaseVariants: MAX_BASE_VARIANTS,
|
|
459
|
+
serialized: preview.serialized,
|
|
460
|
+
resolvedPath: preview.resolvedPath,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
if (body.confirm !== true) {
|
|
464
|
+
// Preview writes nothing, by construction: previewBaseImport is a pure read.
|
|
465
|
+
return jsonResponse({
|
|
466
|
+
ok: true,
|
|
467
|
+
changed: false,
|
|
468
|
+
preview: {
|
|
469
|
+
rawPath: preview.rawPath,
|
|
470
|
+
resolvedPath: preview.resolvedPath,
|
|
471
|
+
serialized: preview.serialized,
|
|
472
|
+
serializedBytes: utf8Bytes(preview.serialized!),
|
|
473
|
+
bodyBytes: utf8Bytes(preview.body!),
|
|
474
|
+
suggestedTitle: preview.suggestedTitle,
|
|
475
|
+
effectiveTitle: preview.effectiveTitle,
|
|
476
|
+
previewSha256: preview.previewSha256,
|
|
477
|
+
},
|
|
478
|
+
}, 200, req, ctx.config);
|
|
479
|
+
}
|
|
480
|
+
const revision = revisionOf(body);
|
|
481
|
+
if (!revision) return fail(ctx, "stale_revision", 409, "revision required");
|
|
482
|
+
// The preview hash is REQUIRED — a confirm that does not name exactly what
|
|
483
|
+
// was previewed (serialized text, title included) cannot be checked against
|
|
484
|
+
// it and is refused without writing.
|
|
485
|
+
const previewSha256 = body.previewSha256;
|
|
486
|
+
if (typeof previewSha256 !== "string" || !/^[0-9a-f]{64}$/.test(previewSha256)) {
|
|
487
|
+
return fail(ctx, "import_preview_required", 400,
|
|
488
|
+
"confirm requires the previewSha256 returned by a preview of the same title");
|
|
489
|
+
}
|
|
490
|
+
if (previewSha256 !== preview.previewSha256) {
|
|
491
|
+
// The file or the selected title moved between preview and confirm. Refuse
|
|
492
|
+
// rather than import a serialization nobody previewed.
|
|
493
|
+
return fail(ctx, "import_body_changed", 409,
|
|
494
|
+
"the preview no longer matches; preview it again", {
|
|
495
|
+
resolvedPath: preview.resolvedPath,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
return settle(ctx, importBaseVariant(
|
|
499
|
+
{ title: typeof body.title === "string" ? body.title : undefined, previewSha256 },
|
|
500
|
+
revision,
|
|
501
|
+
paths(ctx),
|
|
502
|
+
));
|
|
503
|
+
}
|
|
504
|
+
|
|
391
505
|
if (url.pathname === "/api/codex-prompt/base" && req.method === "PUT") {
|
|
392
506
|
const body = await readBody(ctx);
|
|
393
507
|
if (!body) return fail(ctx, "invalid_body", 400, "expected a JSON object");
|
|
@@ -568,11 +682,28 @@ export async function handleCodexPromptRoutes(ctx: ManagementContext): Promise<R
|
|
|
568
682
|
return settle(ctx, adoptDeveloperInstructions(revision, paths(ctx)));
|
|
569
683
|
}
|
|
570
684
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
685
|
+
if (drift === "journal-present") {
|
|
686
|
+
// Recovery as its own locked operation — never a disguised layers write.
|
|
687
|
+
// The GUI used to replay recovery through a byte-identical custom PUT,
|
|
688
|
+
// which projected custom=[] over a config still carrying instructions
|
|
689
|
+
// whenever the store file was missing.
|
|
690
|
+
if (!confirm) {
|
|
691
|
+
return jsonResponse({
|
|
692
|
+
ok: true,
|
|
693
|
+
changed: false,
|
|
694
|
+
preview: { drift, storePath: snapshot.storePath },
|
|
695
|
+
}, 200, req, ctx.config);
|
|
696
|
+
}
|
|
697
|
+
if (!revision) return fail(ctx, "stale_revision", 409, "revision required");
|
|
698
|
+
if (revision !== snapshot.revision) {
|
|
699
|
+
return fail(ctx, "stale_revision", 409, "the configuration moved since it was read");
|
|
700
|
+
}
|
|
701
|
+
return settle(ctx, recoverPromptJournal(paths(ctx)));
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// A drift kind this build does not repair: name it rather than guess.
|
|
574
705
|
return fail(ctx, "repair_unsupported", 409,
|
|
575
|
-
"
|
|
706
|
+
"this drift has no repair on this route", {
|
|
576
707
|
drift, storePath: snapshot.storePath,
|
|
577
708
|
});
|
|
578
709
|
}
|
|
@@ -209,7 +209,7 @@ export async function proposeDelegationModel(options: {
|
|
|
209
209
|
role: DELEGATED_WORK_ROLE,
|
|
210
210
|
model: options.config.injectionModel ?? null,
|
|
211
211
|
effort: options.config.injectionEffort ?? null,
|
|
212
|
-
}], outcomes, classified
|
|
212
|
+
}], outcomes, classified);
|
|
213
213
|
return {
|
|
214
214
|
sizingModel,
|
|
215
215
|
sizingError,
|
|
@@ -27,6 +27,10 @@ function response(): Response {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export async function handleCompanionRoutes(ctx: ManagementContext): Promise<Response | null> {
|
|
30
|
+
if (ctx.url.pathname === "/api/advisor/settings") {
|
|
31
|
+
const { handleAdvisorRoutes } = await import("./advisor-routes");
|
|
32
|
+
return handleAdvisorRoutes(ctx);
|
|
33
|
+
}
|
|
30
34
|
if (ctx.url.pathname === "/api/companion/open-in-browser" && ctx.req.method === "POST") {
|
|
31
35
|
let body: unknown;
|
|
32
36
|
try {
|
|
@@ -329,8 +329,8 @@ export async function syncEnabledClientIntegrations(
|
|
|
329
329
|
config,
|
|
330
330
|
port,
|
|
331
331
|
}, unattended
|
|
332
|
-
? ["mcode", "pi", "aside", "raycast", "omo", "commandcode", "droid", "opencode", "kilo"]
|
|
333
|
-
: ["mcode", "pi", "aside", "raycast", "omo", "cline", "commandcode", "droid", "opencode", "kilo"],
|
|
332
|
+
? ["mcode", "pi", "aside", "raycast", "omo", "commandcode", "droid", "opencode", "kilo", "qoder"]
|
|
333
|
+
: ["mcode", "pi", "aside", "raycast", "omo", "cline", "commandcode", "droid", "opencode", "kilo", "qoder"],
|
|
334
334
|
...unattended ? [{ refreshOnly: true, admit }] as const : []));
|
|
335
335
|
|
|
336
336
|
return out;
|
|
@@ -45,6 +45,10 @@ export interface ManagementRequestIngress {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export interface ManagementApiDeps {
|
|
48
|
+
desktopStartupSettings?: import("../../codex/desktop-compatibility/startup-settings").DesktopStartupSettingsService;
|
|
49
|
+
desktopCertificateService?: import("../../codex/desktop-compatibility/certificate-service").DesktopCertificateService;
|
|
50
|
+
desktopCompatibilityRuntime?: import("../../codex/desktop-compatibility/runtime").DesktopCompatibilityRuntime;
|
|
51
|
+
onDesktopCompatibilityShutdown?: (shutdown: () => Promise<void>) => void;
|
|
48
52
|
/** Bound to this server's lifecycle owner; absent in direct route tests. */
|
|
49
53
|
listLowQuotaEvents?: (limit?: number) => LowQuotaEvent[];
|
|
50
54
|
/** Bound Claude intercept state, injectable for isolated management-route tests. */
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsonResponse } from "../auth-cors";
|
|
2
|
+
import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
|
|
3
|
+
import type { ManagementContext } from "./context";
|
|
4
|
+
import type { DesktopCertificateService } from "../../codex/desktop-compatibility/certificate-service";
|
|
5
|
+
|
|
6
|
+
const PATH = "/api/codex/desktop-compatibility/certificate";
|
|
7
|
+
let service: DesktopCertificateService | undefined;
|
|
8
|
+
|
|
9
|
+
export async function handleDesktopCompatibilityRoutes(ctx: ManagementContext): Promise<Response | null> {
|
|
10
|
+
if (ctx.url.pathname !== PATH) return null;
|
|
11
|
+
if (ctx.req.method !== "GET" && ctx.req.method !== "POST") return jsonResponse({ error: "method_not_allowed" }, 405);
|
|
12
|
+
// A local dashboard confirmation precedes any persisted key or CurrentUser Root mutation.
|
|
13
|
+
// This is caller provenance, not protection against an arbitrary same-user process.
|
|
14
|
+
if (ctx.req.method === "POST" && (ctx.principal !== "gui-session" || !ctx.trustedLoopbackIngress)) {
|
|
15
|
+
return jsonResponse({ error: "local_dashboard_confirmation_required" }, 403);
|
|
16
|
+
}
|
|
17
|
+
let action: "prepare" | "trust" | "remove-trust" | "renew" | undefined, fingerprint: string | undefined;
|
|
18
|
+
if (ctx.req.method === "POST") {
|
|
19
|
+
let body: unknown;
|
|
20
|
+
try { body = await readManagementJsonBody(ctx.req); }
|
|
21
|
+
catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid_json" }, 400); }
|
|
22
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return jsonResponse({ error: "invalid_request" }, 400);
|
|
23
|
+
const value = body as Record<string, unknown>;
|
|
24
|
+
if (Object.keys(value).some(key => !["action", "fingerprint", "confirmed"].includes(key)) || value.confirmed !== true
|
|
25
|
+
|| typeof value.action !== "string" || !["prepare", "trust", "remove-trust", "renew"].includes(value.action)) return jsonResponse({ error: "invalid_request" }, 400);
|
|
26
|
+
action = value.action as typeof action;
|
|
27
|
+
if (action !== "prepare" && (typeof value.fingerprint !== "string" || !/^[A-F0-9]{64}$/.test(value.fingerprint))) {
|
|
28
|
+
return jsonResponse({ error: "certificate_fingerprint_required" }, 400);
|
|
29
|
+
}
|
|
30
|
+
if (action === "prepare" && value.fingerprint !== undefined) return jsonResponse({ error: "unexpected_fingerprint" }, 400);
|
|
31
|
+
fingerprint = value.fingerprint as string | undefined;
|
|
32
|
+
}
|
|
33
|
+
const controller = ctx.deps.desktopCertificateService ?? (service ??= (await import("../../codex/desktop-compatibility/certificate-service")).createDesktopCertificateService());
|
|
34
|
+
try {
|
|
35
|
+
const status = action === "prepare" ? await controller.prepare()
|
|
36
|
+
: action === "trust" ? await controller.trust(fingerprint!)
|
|
37
|
+
: action === "renew" ? await controller.renew(fingerprint!)
|
|
38
|
+
: action === "remove-trust" ? await controller.removeTrust(fingerprint!) : await controller.status();
|
|
39
|
+
return jsonResponse({ ok: true, certificate: status });
|
|
40
|
+
} catch (error) {
|
|
41
|
+
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "operation_failed";
|
|
42
|
+
const allowed = new Set(["unsupported", "busy", "not_prepared", "fingerprint_changed", "app_running", "runtime_running", "app_state_unknown", "trust_unknown", "trust_not_applied",
|
|
43
|
+
"unsafe_path", "unreadable", "expired", "renewal_required", "protection_failed"]);
|
|
44
|
+
return jsonResponse({ ok: false, error: allowed.has(code) ? code : "operation_failed" }, 409);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsonResponse } from "../auth-cors";
|
|
2
|
+
import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
|
|
3
|
+
import type { ManagementContext } from "./context";
|
|
4
|
+
import type { DesktopCompatibilityRuntime } from "../../codex/desktop-compatibility/runtime";
|
|
5
|
+
|
|
6
|
+
const PATH = "/api/codex/desktop-compatibility/runtime";
|
|
7
|
+
const ERROR_CODES = new Set(["busy", "unsupported", "test_environment", "stopping", "build_unverified", "egress_proxy_invalid",
|
|
8
|
+
"trust_required", "certificate_expiring", "certificate_not_prepared", "certificate_invalid", "certificate_expired",
|
|
9
|
+
"native_identity_unverified", "native_routing_unverified", "cleanup_incomplete", "not_running", "connection_invalid", "connection_changed", "connection_cleanup_required", "connection_unavailable"]);
|
|
10
|
+
|
|
11
|
+
export async function handleDesktopCompatibilityRuntimeRoutes(ctx: ManagementContext): Promise<Response | null> {
|
|
12
|
+
if (ctx.url.pathname !== PATH) return null;
|
|
13
|
+
if (ctx.req.method !== "GET" && ctx.req.method !== "POST") return jsonResponse({ error: "method_not_allowed" }, 405);
|
|
14
|
+
if (ctx.req.method === "POST" && (ctx.principal !== "gui-session" || !ctx.trustedLoopbackIngress)) return jsonResponse({ error: "local_dashboard_confirmation_required" }, 403);
|
|
15
|
+
let action: "start" | "stop" | "observe" | "apply" | "launch" | undefined;
|
|
16
|
+
if (ctx.req.method === "POST") {
|
|
17
|
+
let body: unknown;
|
|
18
|
+
try { body = await readManagementJsonBody(ctx.req); }
|
|
19
|
+
catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid_json" }, 400); }
|
|
20
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return jsonResponse({ error: "invalid_request" }, 400);
|
|
21
|
+
const value = body as Record<string, unknown>;
|
|
22
|
+
if (Object.keys(value).some(key => !["action", "confirmed", "accountWideConsent"].includes(key)) || value.confirmed !== true
|
|
23
|
+
|| typeof value.action !== "string" || !["start", "stop", "observe", "apply", "launch"].includes(value.action)) return jsonResponse({ error: "invalid_request" }, 400);
|
|
24
|
+
if (value.action === "apply" ? value.accountWideConsent !== true : value.accountWideConsent !== undefined) return jsonResponse({ error: "invalid_consent_scope" }, 400);
|
|
25
|
+
action = value.action as typeof action;
|
|
26
|
+
}
|
|
27
|
+
const module = ctx.deps.desktopCompatibilityRuntime ? null : await import("../../codex/desktop-compatibility/service");
|
|
28
|
+
const service: DesktopCompatibilityRuntime = ctx.deps.desktopCompatibilityRuntime ?? module!.getDesktopCompatibilityRuntime();
|
|
29
|
+
if (module) ctx.deps.onDesktopCompatibilityShutdown?.(module.shutdownDesktopCompatibility);
|
|
30
|
+
try {
|
|
31
|
+
if (action === "apply") {
|
|
32
|
+
const result = await service.apply(true);
|
|
33
|
+
return jsonResponse({ ok: result.accepted, activation: result, runtime: service.status() }, result.accepted ? 202 : 409);
|
|
34
|
+
}
|
|
35
|
+
if (action === "launch") {
|
|
36
|
+
const result = await service.launch();
|
|
37
|
+
return jsonResponse({ ok: result.status === "started", launch: result, runtime: service.status() }, result.status === "started" ? 200 : 409);
|
|
38
|
+
}
|
|
39
|
+
const result = action === "start" ? await service.start() : action === "stop" ? await service.stop()
|
|
40
|
+
: action === "observe" ? await service.observe() : service.status();
|
|
41
|
+
return jsonResponse({ ok: true, runtime: result });
|
|
42
|
+
} catch (error) {
|
|
43
|
+
const code = error instanceof Error ? error.message.replace(/^desktop_compatibility_/, "") : "operation_failed";
|
|
44
|
+
return jsonResponse({ ok: false, error: ERROR_CODES.has(code) ? code : "operation_failed", runtime: service.status() }, 409);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsonResponse } from "../auth-cors";
|
|
2
|
+
import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
|
|
3
|
+
import type { ManagementContext } from "./context";
|
|
4
|
+
|
|
5
|
+
const PATH = "/api/codex/desktop-compatibility/settings";
|
|
6
|
+
export async function handleDesktopCompatibilitySettingsRoutes(ctx: ManagementContext): Promise<Response | null> {
|
|
7
|
+
if (ctx.url.pathname !== PATH) return null;
|
|
8
|
+
if (ctx.req.method !== "GET" && ctx.req.method !== "POST") return jsonResponse({ error: "method_not_allowed" }, 405);
|
|
9
|
+
if (ctx.req.method === "POST" && (ctx.principal !== "gui-session" || !ctx.trustedLoopbackIngress)) return jsonResponse({ error: "local_dashboard_confirmation_required" }, 403);
|
|
10
|
+
let update: { startOnProxyStart: boolean; revision: string } | undefined;
|
|
11
|
+
if (ctx.req.method === "POST") {
|
|
12
|
+
let body: unknown;
|
|
13
|
+
try { body = await readManagementJsonBody(ctx.req); }
|
|
14
|
+
catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid_json" }, 400); }
|
|
15
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return jsonResponse({ error: "invalid_request" }, 400);
|
|
16
|
+
const value = body as Record<string, unknown>;
|
|
17
|
+
if (Object.keys(value).some(key => !["startOnProxyStart", "revision", "confirmed"].includes(key)) || value.confirmed !== true
|
|
18
|
+
|| typeof value.startOnProxyStart !== "boolean" || typeof value.revision !== "string" || !/^[a-f0-9]{64}$/.test(value.revision)) return jsonResponse({ error: "invalid_request" }, 400);
|
|
19
|
+
update = { startOnProxyStart: value.startOnProxyStart, revision: value.revision };
|
|
20
|
+
}
|
|
21
|
+
const service = ctx.deps.desktopStartupSettings ?? (await import("../../codex/desktop-compatibility/startup-settings")).createDesktopStartupSettings({ liveConfig: ctx.config });
|
|
22
|
+
try { return jsonResponse({ ok: true, settings: update ? service.set(update.startOnProxyStart, update.revision) : service.status() }); }
|
|
23
|
+
catch (error) {
|
|
24
|
+
const code = error instanceof Error ? error.message.replace(/^desktop_compatibility_settings_/, "") : "operation_unconfirmed";
|
|
25
|
+
return jsonResponse({ ok: false, error: ["unavailable", "invalid", "changed", "invalid_request"].includes(code) ? `settings_${code}` : "settings_operation_unconfirmed" }, 409);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -22,10 +22,12 @@ import {
|
|
|
22
22
|
} from "../../grok/reset-coupons";
|
|
23
23
|
import {
|
|
24
24
|
getGrokResetCouponOperationAccountId,
|
|
25
|
+
isTerminalGrokCouponReplay,
|
|
25
26
|
markGrokResetCouponAttempt,
|
|
26
27
|
openGrokResetCouponOperation,
|
|
27
28
|
recordGrokResetCouponSettlement,
|
|
28
29
|
readGrokResetCouponTerminalReplay,
|
|
30
|
+
refuseGrokResetCouponBeforeOpen,
|
|
29
31
|
settleGrokResetCouponPreflightRefusal,
|
|
30
32
|
type GrokResetCouponOperationRecord,
|
|
31
33
|
} from "../../grok/reset-coupon-ledger";
|
|
@@ -140,10 +142,6 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
|
|
|
140
142
|
|
|
141
143
|
const { accountId: rawAccountId, tokenId: requestedTokenId, operationId } = body;
|
|
142
144
|
|
|
143
|
-
if (requestedTokenId !== undefined && (typeof requestedTokenId !== "string" || requestedTokenId.trim() === "")) {
|
|
144
|
-
return jsonResponse({ error: { code: "invalid_token_id", message: "tokenId must be a non-empty string" } }, 400, req, config);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
145
|
if (operationId !== undefined && !isCodexResetCreditOperationId(operationId)) {
|
|
148
146
|
return jsonResponse(
|
|
149
147
|
{ error: { code: "invalid_operation_id", message: "operationId must be a valid UUIDv4" } },
|
|
@@ -153,47 +151,87 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
|
|
|
153
151
|
);
|
|
154
152
|
}
|
|
155
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Once a caller supplies an operationId, that id may already name an operation that
|
|
156
|
+
* another request is spending. A refusal from this request must not read as
|
|
157
|
+
* definitive then, or the caller drops its hold and mints a replacement.
|
|
158
|
+
*/
|
|
159
|
+
const uncertain = (id: string): Response => jsonResponse({ operationId: id, error: {
|
|
160
|
+
code: "attempt_in_progress",
|
|
161
|
+
message: "This request did not attempt a redemption, but another request may hold this operation; retry the same operationId to read its durable state",
|
|
162
|
+
} }, 409, req, config);
|
|
163
|
+
/** Refuse before open: definitive only when the supplied id is durably reserved as failed. */
|
|
164
|
+
const preOpenRefusal = (definitive: Response, code: string, knownAccountId?: string): Response => {
|
|
165
|
+
if (operationId === undefined) return definitive;
|
|
166
|
+
if (knownAccountId === undefined) return uncertain(operationId);
|
|
167
|
+
try {
|
|
168
|
+
const result = refuseGrokResetCouponBeforeOpen({
|
|
169
|
+
operationId, accountId: knownAccountId, code,
|
|
170
|
+
tokenId: typeof requestedTokenId === "string" ? requestedTokenId : undefined,
|
|
171
|
+
});
|
|
172
|
+
if (result.kind === "recorded") return definitive;
|
|
173
|
+
if (result.kind === "replay") return jsonResponse({
|
|
174
|
+
code: result.code, replayed: true, tokenId: result.tokenId, settledAt: result.settledAt,
|
|
175
|
+
}, 200, req, config);
|
|
176
|
+
if (result.kind === "identity-mismatch") return jsonResponse({ error: {
|
|
177
|
+
code: "operation_id_owned_by_another_account",
|
|
178
|
+
message: "Operation ID was previously registered with a different account or token",
|
|
179
|
+
} }, 409, req, config);
|
|
180
|
+
return uncertain(operationId);
|
|
181
|
+
} catch {
|
|
182
|
+
return uncertain(operationId);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
if (requestedTokenId !== undefined && (typeof requestedTokenId !== "string" || requestedTokenId.trim() === "")) {
|
|
187
|
+
return preOpenRefusal(jsonResponse({ error: { code: "invalid_token_id", message: "tokenId must be a non-empty string" } }, 400, req, config), "invalid_token_id");
|
|
188
|
+
}
|
|
189
|
+
|
|
156
190
|
let recordedAccountId: string | undefined;
|
|
157
191
|
if (rawAccountId === undefined && operationId !== undefined) {
|
|
158
192
|
try { recordedAccountId = getGrokResetCouponOperationAccountId(operationId); }
|
|
159
193
|
catch {
|
|
160
|
-
return
|
|
194
|
+
return uncertain(operationId);
|
|
161
195
|
}
|
|
162
196
|
}
|
|
163
197
|
let accountId: string;
|
|
164
198
|
try {
|
|
165
199
|
accountId = resolveTargetAccountId(rawAccountId ?? recordedAccountId);
|
|
166
200
|
} catch (err) {
|
|
167
|
-
return jsonResponse(
|
|
201
|
+
return preOpenRefusal(jsonResponse(
|
|
168
202
|
{ error: { code: "no_account", message: err instanceof Error ? err.message : String(err) } },
|
|
169
203
|
400,
|
|
170
204
|
req,
|
|
171
205
|
config,
|
|
172
|
-
);
|
|
206
|
+
), "no_account");
|
|
173
207
|
}
|
|
174
208
|
|
|
175
209
|
let tokenSnapshot;
|
|
176
210
|
try {
|
|
177
211
|
tokenSnapshot = await getValidAccessSnapshotForAccount("xai", accountId, { requireUsableAccount: true });
|
|
178
212
|
} catch (err) {
|
|
179
|
-
return jsonResponse(
|
|
213
|
+
return preOpenRefusal(jsonResponse(
|
|
180
214
|
{ error: { code: "auth_failed", message: "Failed to resolve valid xAI credentials for account" } },
|
|
181
215
|
401,
|
|
182
216
|
req,
|
|
183
217
|
config,
|
|
184
|
-
);
|
|
218
|
+
), "auth_failed", accountId);
|
|
185
219
|
}
|
|
186
220
|
|
|
187
221
|
// Journaling and Idempotency settlement check
|
|
188
222
|
const effectiveOpId = operationId ?? crypto.randomUUID();
|
|
189
|
-
if (grokCouponInFlightAttempts.has(effectiveOpId)) return jsonResponse({ error: {
|
|
223
|
+
if (grokCouponInFlightAttempts.has(effectiveOpId)) return jsonResponse({ operationId: effectiveOpId, error: {
|
|
190
224
|
code: "attempt_in_progress", message: "This operation still has an active request; retry later",
|
|
191
225
|
} }, 409, req, config);
|
|
192
226
|
let opRecord: GrokResetCouponOperationRecord;
|
|
193
227
|
try {
|
|
194
228
|
opRecord = openGrokResetCouponOperation({ accountId, tokenId: requestedTokenId, operationId: effectiveOpId });
|
|
195
229
|
} catch {
|
|
196
|
-
|
|
230
|
+
// A failed open cannot prove that no earlier request already opened this id.
|
|
231
|
+
return jsonResponse({ operationId: effectiveOpId, error: {
|
|
232
|
+
code: "ledger_unavailable",
|
|
233
|
+
message: "Coupon ledger could not be read or locked; this request did not attempt a redemption. Retry the same operationId",
|
|
234
|
+
} }, 503, req, config);
|
|
197
235
|
}
|
|
198
236
|
|
|
199
237
|
let resolvedTokenId = requestedTokenId ?? opRecord.tokenId;
|
|
@@ -201,6 +239,14 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
|
|
|
201
239
|
|
|
202
240
|
if (opRecord.kind === "replay") {
|
|
203
241
|
if (opRecord.code !== undefined) {
|
|
242
|
+
// Replay only a definitive outcome; a recorded code that means "unconfirmed"
|
|
243
|
+
// must keep the caller on this operation.
|
|
244
|
+
if (!isTerminalGrokCouponReplay(opRecord, accountId, requestedTokenId)) {
|
|
245
|
+
return jsonResponse({ operationId: effectiveOpId, error: {
|
|
246
|
+
code: "attempt_unresolved",
|
|
247
|
+
message: "This operation's recorded outcome is unconfirmed; preserve this operationId and do not create a replacement attempt",
|
|
248
|
+
} }, 409, req, config);
|
|
249
|
+
}
|
|
204
250
|
return jsonResponse(
|
|
205
251
|
{
|
|
206
252
|
code: opRecord.code,
|
|
@@ -230,6 +276,7 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
|
|
|
230
276
|
// retry with the recorded token and preserve the operationId.
|
|
231
277
|
return jsonResponse(
|
|
232
278
|
{
|
|
279
|
+
operationId: effectiveOpId,
|
|
233
280
|
error: {
|
|
234
281
|
code: "operation_token_mismatch",
|
|
235
282
|
message: "Operation was attempted with a different coupon; preserve the operationId and retry with the recorded tokenId",
|
|
@@ -282,7 +329,7 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
|
|
|
282
329
|
} }, 409, req, config);
|
|
283
330
|
}
|
|
284
331
|
|
|
285
|
-
if (opRecord.kind === "token-mismatch") return jsonResponse({ error: {
|
|
332
|
+
if (opRecord.kind === "token-mismatch") return jsonResponse({ operationId: effectiveOpId, error: {
|
|
286
333
|
code: "operation_token_mismatch", message: "Operation ID was previously registered with a different coupon",
|
|
287
334
|
} }, 409, req, config);
|
|
288
335
|
|
|
@@ -301,11 +348,14 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
|
|
|
301
348
|
}
|
|
302
349
|
|
|
303
350
|
if (opRecord.kind !== "execute") {
|
|
351
|
+
// A capacity refusal cannot reserve the id, so an earlier same-id request still in
|
|
352
|
+
// credential resolution may open it once records expire: keep the caller on the id.
|
|
304
353
|
return jsonResponse(
|
|
305
354
|
{
|
|
355
|
+
...(operationId !== undefined ? { operationId: effectiveOpId } : {}),
|
|
306
356
|
error: {
|
|
307
357
|
code: opRecord.kind,
|
|
308
|
-
message: "Coupon ledger capacity
|
|
358
|
+
message: "Coupon ledger is at capacity; this request did not attempt a redemption. Retry the same operationId later",
|
|
309
359
|
},
|
|
310
360
|
},
|
|
311
361
|
503,
|
|
@@ -324,14 +374,13 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
|
|
|
324
374
|
if (result.kind === "replay") return jsonResponse({
|
|
325
375
|
code: result.code, replayed: true, tokenId: result.tokenId, settledAt: result.settledAt,
|
|
326
376
|
}, 200, req, config);
|
|
327
|
-
return jsonResponse({ error: {
|
|
377
|
+
return jsonResponse({ operationId: effectiveOpId, error: {
|
|
328
378
|
code: "operation_state_changed",
|
|
329
379
|
message: "Another request changed this operation during inspection; retry the same operationId to read its durable state",
|
|
330
380
|
} }, 409, req, config);
|
|
331
381
|
} catch {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
} }, 503, req, config);
|
|
382
|
+
// The refusal could not be recorded, so the operation may still be claimed elsewhere.
|
|
383
|
+
return uncertain(effectiveOpId);
|
|
335
384
|
}
|
|
336
385
|
};
|
|
337
386
|
|
|
@@ -345,6 +394,10 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
|
|
|
345
394
|
const remaining = await getGrokRemainingResets({ accessToken: tokenSnapshot.accessToken });
|
|
346
395
|
tokens = remaining.tokens ?? [];
|
|
347
396
|
} catch (err) {
|
|
397
|
+
// Close the still-open operation before reporting a definitive failure: once it is
|
|
398
|
+
// recorded as failed it can never be claimed, so the caller may safely start over.
|
|
399
|
+
const changed = settlePreflightRefusal("fetch_resets_failed", resolvedTokenId);
|
|
400
|
+
if (changed) return changed;
|
|
348
401
|
return jsonResponse(
|
|
349
402
|
{ error: { code: "fetch_resets_failed", message: err instanceof Error ? err.message : String(err) } },
|
|
350
403
|
502,
|
|
@@ -389,6 +442,8 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
|
|
|
389
442
|
|
|
390
443
|
if (resolvedTokenId === undefined) {
|
|
391
444
|
// Unreachable: every path above either resolves a token or returns.
|
|
445
|
+
const changed = settlePreflightRefusal("token_unresolved");
|
|
446
|
+
if (changed) return changed;
|
|
392
447
|
return jsonResponse(
|
|
393
448
|
{ error: { code: "token_unresolved", message: "No reset coupon token could be resolved" } },
|
|
394
449
|
500,
|
|
@@ -403,6 +458,7 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
|
|
|
403
458
|
// on this operationId again regardless of how stale the mark looks.
|
|
404
459
|
return jsonResponse(
|
|
405
460
|
{
|
|
461
|
+
operationId: effectiveOpId,
|
|
406
462
|
error: {
|
|
407
463
|
code: "attempt_in_progress",
|
|
408
464
|
message: "A redemption attempt for this operation is still running; retry later",
|
|
@@ -433,17 +489,12 @@ export async function handleGrokCouponRoutes(ctx: ManagementContext): Promise<Re
|
|
|
433
489
|
if (winner) return jsonResponse({
|
|
434
490
|
code: winner.code, replayed: true, tokenId: winner.tokenId, settledAt: winner.settledAt,
|
|
435
491
|
}, 200, req, config);
|
|
436
|
-
return
|
|
437
|
-
code: "attempt_in_progress", message: "Another request already claimed this operation; no redemption was attempted",
|
|
438
|
-
} }, 409, req, config);
|
|
492
|
+
return uncertain(effectiveOpId);
|
|
439
493
|
}
|
|
440
|
-
} catch
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
req,
|
|
445
|
-
config,
|
|
446
|
-
);
|
|
494
|
+
} catch {
|
|
495
|
+
// The claim write failed or was contended: this request dispatched nothing, but a
|
|
496
|
+
// concurrent holder of this operation may be spending, so keep the caller on it.
|
|
497
|
+
return uncertain(effectiveOpId);
|
|
447
498
|
}
|
|
448
499
|
|
|
449
500
|
try {
|