@iislee/opencodex 2.11.0 → 2.35.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/AGENTS_INSTALL.md +109 -0
- package/README.md +117 -22
- package/bin/ocx.mjs +245 -88
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +21 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +475 -62
- package/src/adapters/base.ts +82 -7
- package/src/adapters/client-fingerprint.ts +18 -12
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +637 -0
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +308 -0
- package/src/adapters/cursor/cursor-errors.ts +144 -6
- package/src/adapters/cursor/discovery.ts +122 -14
- package/src/adapters/cursor/effort-map.ts +106 -4
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/h2-pool.ts +123 -0
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/images.ts +704 -0
- package/src/adapters/cursor/live-models.ts +180 -59
- package/src/adapters/cursor/live-transport.ts +623 -170
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- package/src/adapters/cursor/native-exec-fs.ts +10 -7
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +5 -3
- package/src/adapters/cursor/native-exec.ts +121 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +447 -75
- package/src/adapters/cursor/request-builder.ts +271 -35
- package/src/adapters/cursor/tool-definitions.ts +252 -12
- package/src/adapters/cursor/tool-result-normalize.ts +115 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +48 -1
- package/src/adapters/cursor.ts +336 -39
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +713 -41
- package/src/adapters/google-antigravity-wire.ts +48 -10
- package/src/adapters/google-errors.ts +44 -12
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +742 -86
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +18 -2
- package/src/adapters/kiro-tools.ts +65 -17
- package/src/adapters/kiro.ts +270 -73
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1377 -344
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +1164 -64
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +105 -21
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +186 -0
- package/src/bridge.ts +512 -92
- package/src/chat/inbound.ts +57 -20
- package/src/chat/outbound.ts +97 -33
- package/src/claude/agents-inject.ts +40 -10
- package/src/claude/context-windows.ts +37 -14
- package/src/claude/desktop-3p.ts +243 -9
- package/src/claude/gateway-cache.ts +41 -4
- package/src/claude/inbound.ts +72 -3
- package/src/claude/model-info.ts +38 -15
- package/src/claude/outbound.ts +88 -21
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +150 -19
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +648 -28
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +97 -19
- package/src/cli/agent.ts +53 -2
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +60 -15
- package/src/cli/claude.ts +129 -35
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +76 -0
- package/src/cli/combo.ts +8 -0
- package/src/cli/config-command.ts +74 -10
- package/src/cli/dispatch.ts +821 -0
- package/src/cli/doctor.ts +440 -56
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +34 -274
- package/src/cli/index.ts +417 -528
- package/src/cli/init.ts +5 -17
- package/src/cli/inspect.ts +230 -0
- package/src/cli/integrations.ts +120 -2
- package/src/cli/lab.ts +607 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/minimax.ts +497 -0
- package/src/cli/models-runtime.ts +130 -2
- package/src/cli/models.ts +118 -16
- package/src/cli/observe.ts +123 -11
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +34 -3
- package/src/cli/provider.ts +25 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +476 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +57 -10
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +51 -4
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +137 -18
- package/src/cli/version-skew.ts +46 -0
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1442 -23
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +35 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +84 -0
- package/src/codex/account-store.ts +29 -2
- package/src/codex/account-usability.ts +25 -2
- package/src/codex/admission.ts +256 -0
- package/src/codex/affinity-debug.ts +162 -0
- package/src/codex/app-server-processes.ts +570 -107
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +893 -246
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +348 -32
- package/src/codex/autostart-health.ts +24 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +86 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +138 -32
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +555 -45
- package/src/codex/catalog/native-models.ts +75 -0
- package/src/codex/catalog/parsing.ts +269 -30
- package/src/codex/catalog/provider-fetch.ts +1626 -144
- package/src/codex/catalog/sync.ts +1576 -187
- package/src/codex/catalog-admission.ts +199 -0
- package/src/codex/catalog-refresh-status.ts +105 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +6 -3
- package/src/codex/codex-write-lock.ts +386 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +676 -0
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/desktop-app-restart.ts +355 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +436 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +30 -24
- package/src/codex/history-provider.ts +1016 -235
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +223 -0
- package/src/codex/inject-coordination.ts +373 -0
- package/src/codex/inject.ts +1114 -152
- package/src/codex/injected-marker.ts +37 -3
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +81 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +524 -0
- package/src/codex/log-guard/lock.ts +150 -0
- package/src/codex/log-guard/maintenance.ts +403 -0
- package/src/codex/log-guard/path-safety.ts +88 -0
- package/src/codex/log-guard/policy.ts +44 -0
- package/src/codex/log-guard/processes.ts +205 -0
- package/src/codex/log-guard/protection.ts +489 -0
- package/src/codex/log-guard/sqlite-errors.ts +9 -0
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/main-account.ts +29 -1
- package/src/codex/management-convergence.ts +167 -0
- package/src/codex/model-cache.ts +56 -10
- package/src/codex/model-entitlements.ts +360 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +178 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +622 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +675 -0
- package/src/codex/paths.ts +80 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +40 -0
- package/src/codex/plugins-doctor.ts +1 -1
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/project-config-warnings.ts +20 -6
- package/src/codex/prompt-journal.ts +352 -0
- package/src/codex/prompt-layers.ts +1313 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +264 -21
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +514 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1065 -31
- package/src/codex/subagent-model-fallback.ts +437 -43
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +720 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +557 -0
- package/src/codex/warmup.ts +187 -81
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +47 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +91 -9
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-name.ts +24 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +1713 -709
- package/src/generated/compatibility-version.json +3324 -0
- package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
- package/src/grok/inject.ts +16 -5
- package/src/grok/inspect.ts +45 -0
- package/src/grok/sync.ts +2 -2
- package/src/images/loop.ts +157 -33
- package/src/images/plan.ts +24 -13
- package/src/integrations/config-io.ts +269 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/mutation-flight.ts +71 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/omp-yaml-source.ts +358 -0
- package/src/integrations/owned-refresh.ts +74 -0
- package/src/integrations/ownership-policy.ts +160 -0
- package/src/integrations/ownership.ts +155 -0
- package/src/integrations/registry.ts +166 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +433 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +715 -0
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +499 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +64 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +164 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +68 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +214 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +229 -0
- package/src/lab/events/validate.ts +781 -0
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +489 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +54 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +310 -0
- package/src/lab/ledger/store.ts +532 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +336 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +118 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +301 -0
- package/src/lab/observe/from-live.ts +117 -0
- package/src/lab/paths.ts +153 -0
- package/src/lab/projection/rebuild.ts +495 -0
- package/src/lab/projection/schema.ts +135 -0
- package/src/lab/projection/verdicts.ts +474 -0
- package/src/lab/projection/verification.ts +412 -0
- package/src/lab/public/bundle.ts +217 -0
- package/src/lab/public/community-authority.ts +175 -0
- package/src/lab/public/community-files.ts +29 -0
- package/src/lab/public/community.ts +479 -0
- package/src/lab/public/file-safety.ts +155 -0
- package/src/lab/public/ids.ts +26 -0
- package/src/lab/public/index.ts +16 -0
- package/src/lab/public/mutation-lock.ts +424 -0
- package/src/lab/public/operator.ts +353 -0
- package/src/lab/public/origin-purge.ts +79 -0
- package/src/lab/public/origin.ts +203 -0
- package/src/lab/public/privacy.ts +143 -0
- package/src/lab/public/private-file.ts +261 -0
- package/src/lab/public/project.ts +124 -0
- package/src/lab/public/purge-test-fault.ts +21 -0
- package/src/lab/public/purge.ts +223 -0
- package/src/lab/public/registry.ts +44 -0
- package/src/lab/public/revocation.ts +252 -0
- package/src/lab/public/signature.ts +243 -0
- package/src/lab/public/storage.ts +105 -0
- package/src/lab/public/strict-json.ts +206 -0
- package/src/lab/public/time.ts +26 -0
- package/src/lab/public/types.ts +172 -0
- package/src/lab/public/validate.ts +391 -0
- package/src/lab/query/catalog.ts +101 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +45 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +112 -0
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +74 -0
- package/src/lib/admin-secrets.ts +24 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +167 -11
- package/src/lib/bun-runtime.ts +125 -12
- package/src/lib/bun-stream-caps.ts +13 -9
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/config-ownership.ts +26 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +80 -5
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/lab-activation.ts +223 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +56 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/lab-passive-linker-registration.ts +26 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/local-provider-reload-contract.ts +100 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +6 -2
- package/src/lib/provider-outbound.ts +49 -9
- package/src/lib/redact.ts +419 -3
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/service-secrets.ts +15 -0
- package/src/lib/shadow-call.ts +35 -4
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/state-store-registrations.ts +10 -2
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/token-estimate.ts +19 -2
- package/src/lib/tool-argument-integers.ts +253 -0
- package/src/lib/translator-budget.ts +44 -0
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-reachability.ts +95 -0
- package/src/lib/upstream-retry.ts +156 -3
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-elevation.ts +70 -2
- package/src/lib/windows-secret-acl.ts +409 -69
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/lib/windows-text.ts +106 -0
- package/src/lib/windows-user-principal.ts +341 -0
- package/src/lib/winsw.ts +33 -5
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic.ts +12 -1
- package/src/oauth/callback-server.ts +30 -4
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +462 -71
- package/src/oauth/key-providers.ts +25 -0
- package/src/oauth/kimi.ts +25 -3
- package/src/oauth/kiro.ts +95 -6
- package/src/oauth/local-token-detect.ts +13 -2
- package/src/oauth/log.ts +3 -1
- package/src/oauth/login-cli.ts +88 -28
- package/src/oauth/nous.ts +798 -0
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +133 -26
- package/src/oauth/token-guardian.ts +9 -3
- package/src/oauth/types.ts +15 -0
- package/src/pi/models.ts +2 -2
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/antigravity-models.ts +521 -31
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +176 -0
- package/src/providers/context-cap.ts +26 -8
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +281 -6
- package/src/providers/fastwire.ts +507 -0
- package/src/providers/free-directory.ts +10 -7
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/label.ts +35 -2
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/openai-sidecar.ts +72 -4
- package/src/providers/openai-tier-startup.ts +31 -2
- package/src/providers/openai-tiers.ts +119 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +30 -0
- package/src/providers/quota.ts +1379 -40
- package/src/providers/registry.ts +1545 -112
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +94 -6
- package/src/providers/static-model-discovery.ts +86 -0
- package/src/providers/xai-responses-opt-in.ts +15 -0
- package/src/providers/xai-transport.ts +32 -4
- package/src/reasoning-effort.ts +68 -3
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +384 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +435 -0
- package/src/responses/parser.ts +225 -38
- package/src/responses/provider-continuation.ts +98 -0
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +504 -0
- package/src/responses/schema.ts +12 -2
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +659 -31
- package/src/responses/thought-signature-replay.ts +364 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +405 -31
- package/src/routing/analytics.ts +379 -0
- package/src/routing/capability.ts +244 -0
- package/src/routing/compatibility/assemble.ts +73 -0
- package/src/routing/compatibility/behavior.ts +278 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/provider-slot.ts +56 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +495 -0
- package/src/routing/health.ts +412 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +547 -0
- package/src/routing/quota.ts +155 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +776 -0
- package/src/server/adapter-resolve.ts +2 -29
- package/src/server/auth-cors.ts +289 -48
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +154 -72
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +445 -0
- package/src/server/claude-messages.ts +160 -44
- package/src/server/direct-local-http.ts +347 -0
- package/src/server/effort-policy.ts +18 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +94 -12
- package/src/server/index.ts +1021 -190
- package/src/server/lifecycle.ts +336 -14
- package/src/server/live.ts +136 -17
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/local-provider-reload-client.ts +137 -0
- package/src/server/management/agent-settings-routes.ts +433 -116
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +64 -25
- package/src/server/management/config-routes.ts +511 -51
- package/src/server/management/context.ts +89 -2
- package/src/server/management/integration-routes.ts +498 -0
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +563 -0
- package/src/server/management/logs-usage-routes.ts +127 -52
- package/src/server/management/model-routes.ts +461 -133
- package/src/server/management/model-rows.ts +167 -0
- package/src/server/management/native-integration-routes.ts +770 -0
- package/src/server/management/oauth-account-routes.ts +90 -8
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +836 -159
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/route-registry.ts +332 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +380 -0
- package/src/server/management/shared.ts +27 -11
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/storage-log-guard-routes.ts +186 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +289 -33
- package/src/server/management/system-routes.ts +77 -0
- package/src/server/management/usage-summary-cache.ts +9 -1
- package/src/server/management/vision-sidecar-options.ts +167 -0
- package/src/server/management/web-search-sidecar-options.ts +120 -0
- package/src/server/management-api.ts +126 -23
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +161 -10
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +221 -54
- package/src/server/relay.ts +479 -87
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +298 -31
- package/src/server/responses/agent-task-recovery-cache.ts +143 -0
- package/src/server/responses/agent-task-recovery.ts +465 -0
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +239 -36
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +444 -72
- package/src/server/responses/core.ts +3579 -436
- package/src/server/responses/empty-completion-guard.ts +333 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +89 -99
- package/src/server/responses/input-admission.ts +187 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +343 -0
- package/src/server/responses/terminal-guard.ts +26 -5
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses/ws-upstream.ts +381 -0
- package/src/server/responses-custom-tool-repair.ts +363 -0
- package/src/server/responses-item-id-repair.ts +54 -6
- package/src/server/responses-json-events.ts +90 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses-terminal-repair.ts +363 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +376 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +319 -0
- package/src/server/sse-payload-rewrite.ts +110 -22
- package/src/server/startup-action-control.ts +8 -1
- package/src/server/startup-health-cache.ts +19 -1
- package/src/server/system-env.ts +80 -9
- package/src/server/ws-bridge.ts +53 -40
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1153 -94
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- package/src/storage/scanner.ts +1 -1
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +83 -9
- package/src/tray/windows.ts +43 -16
- package/src/types/accounts.ts +37 -0
- package/src/types/config.ts +911 -0
- package/src/types/provider.ts +614 -0
- package/src/types/request.ts +397 -0
- package/src/types/tools.ts +236 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +107 -1237
- package/src/update/index.ts +37 -23
- package/src/update/job.ts +445 -68
- package/src/update/notify.ts +12 -6
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +266 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +287 -16
- package/src/usage/log.ts +666 -50
- package/src/usage/summary.ts +388 -17
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/vision/backends.ts +97 -0
- package/src/vision/describe.ts +27 -16
- package/src/vision/eligibility.ts +250 -0
- package/src/vision/index.ts +238 -24
- package/src/vision/reasoning.ts +55 -0
- package/src/vision/routed-describe.ts +175 -0
- package/src/vision/timeout-bounds.ts +9 -0
- package/src/web-search/anthropic-executor.ts +13 -7
- package/src/web-search/backends.ts +108 -0
- package/src/web-search/exa-executor.ts +119 -0
- package/src/web-search/executor.ts +21 -6
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +284 -54
- package/src/web-search/parse.ts +125 -30
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/xai-executor.ts +219 -0
- package/gui/dist/assets/index-DTpMHS4F.js +0 -67
- package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
- package/src/cli/internal-dispatch.ts +0 -20
|
@@ -0,0 +1,770 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toggle routes for the integrations that are NOT file-merged clients.
|
|
3
|
+
*
|
|
4
|
+
* The six file clients live in `integration-routes.ts` and go through
|
|
5
|
+
* `src/integrations/writer.ts`, which merges config fragments and journals every
|
|
6
|
+
* write. These do not: Claude Code is a flag in our own config, and Grok owns a
|
|
7
|
+
* fenced region of a file we do not otherwise write. Neither has a merged
|
|
8
|
+
* fragment to own, so neither needs a snapshot, a journal row, or a restore
|
|
9
|
+
* route — turning them back on is the undo.
|
|
10
|
+
*
|
|
11
|
+
* That conclusion cost eleven audit rounds; the reasoning is in
|
|
12
|
+
* devlog/_fin/260803_integrations_toggle_all/, and 007 records why Codex and
|
|
13
|
+
* Claude Desktop are NOT here: their state spans several artifacts and a live
|
|
14
|
+
* database, so they need a durable operation record this module deliberately
|
|
15
|
+
* does not have.
|
|
16
|
+
*
|
|
17
|
+
* Design of record: devlog/_fin/260803_integrations_toggle_all/030 (routes),
|
|
18
|
+
* 011 (Claude Code), 012 (Grok).
|
|
19
|
+
*/
|
|
20
|
+
import { loadConfig, saveConfigPreservingClaudeCode } from "../../config";
|
|
21
|
+
import { readRuntimePort } from "../../config/process-state";
|
|
22
|
+
import { desktopVisibleNativeSlugs, filterCatalogVisibleModels, nativeContextLimits, nativeOpenAiContextWindow, visibleNativeSlugs } from "../../codex/catalog";
|
|
23
|
+
import { providerContextCap } from "../../providers/context-cap";
|
|
24
|
+
import { OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";
|
|
25
|
+
import { inspectDesktop3pConfigLibrary, removeDesktop3pStandardPivot, writeDesktop3pConfig } from "../../claude/desktop-3p";
|
|
26
|
+
import { injectGrokConfig, stripGrokConfig, type GrokInjectModel } from "../../grok/inject";
|
|
27
|
+
import { inspectGrokConfig } from "../../grok/inspect";
|
|
28
|
+
import { grokConfigPath } from "../../grok/status";
|
|
29
|
+
import { assertNativeTeardownOwned } from "../../integrations/native/ownership-preflight";
|
|
30
|
+
import type { CodexNativeRestoreResult } from "../../codex/inject";
|
|
31
|
+
import type { OcxConfig } from "../../types";
|
|
32
|
+
import { jsonResponse } from "../auth-cors";
|
|
33
|
+
import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
|
|
34
|
+
import type { ManagementContext } from "./context";
|
|
35
|
+
|
|
36
|
+
export type NativeIntegrationClientId = "claude" | "grok" | "codex" | "claude-desktop";
|
|
37
|
+
|
|
38
|
+
/** Every reason this module can decline, in one place (audit r3 #6). */
|
|
39
|
+
export type NativeRefusalReason =
|
|
40
|
+
| "not_installed"
|
|
41
|
+
| "orphaned_marker"
|
|
42
|
+
| "home_mismatch"
|
|
43
|
+
| "config_busy"
|
|
44
|
+
| "write_failed"
|
|
45
|
+
| "metadata_unreadable"
|
|
46
|
+
| "cleanup_incomplete"
|
|
47
|
+
| "desired_state_changed";
|
|
48
|
+
|
|
49
|
+
export interface NativeStatus {
|
|
50
|
+
clientId: NativeIntegrationClientId;
|
|
51
|
+
state: "absent" | "current" | "unsafe";
|
|
52
|
+
installed: boolean;
|
|
53
|
+
configPath: string;
|
|
54
|
+
desiredEnabled: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Set when a disable would be refused right now. ADVISORY: the file can
|
|
57
|
+
* change before the PUT, which re-checks and whose answer is authoritative.
|
|
58
|
+
* It exists so the GUI does not offer an action we already know is blocked.
|
|
59
|
+
*/
|
|
60
|
+
disableBlocked: { reason: NativeRefusalReason; message: string } | null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface NativeStatusListEnvelope {
|
|
64
|
+
clients: NativeStatus[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface NativeToggleEnvelope {
|
|
68
|
+
ok: true;
|
|
69
|
+
clientId: NativeIntegrationClientId;
|
|
70
|
+
changed: boolean;
|
|
71
|
+
state: NativeStatus["state"];
|
|
72
|
+
message: string;
|
|
73
|
+
desiredEnabled: boolean;
|
|
74
|
+
/** Present when the outcome needs more than success/failure to be honest. */
|
|
75
|
+
reason?: string;
|
|
76
|
+
artifacts?: CodexNativeRestoreResult["artifacts"];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface NativeRefusalEnvelope {
|
|
80
|
+
error: string;
|
|
81
|
+
code: "native_integration_refused" | "native_integration_failed";
|
|
82
|
+
clientId: NativeIntegrationClientId;
|
|
83
|
+
reason: NativeRefusalReason;
|
|
84
|
+
message: string;
|
|
85
|
+
/** Present on every post-commit refusal; absent only for pre-commit refusals. */
|
|
86
|
+
desiredEnabled?: boolean;
|
|
87
|
+
residualPaths?: string[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export type NativePostCommitRefusalEnvelope = Omit<NativeRefusalEnvelope, "desiredEnabled"> & {
|
|
91
|
+
desiredEnabled: boolean;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
function refusal(
|
|
95
|
+
status: number,
|
|
96
|
+
clientId: NativeIntegrationClientId,
|
|
97
|
+
reason: NativeRefusalReason,
|
|
98
|
+
message: string,
|
|
99
|
+
extra: Pick<NativeRefusalEnvelope, "desiredEnabled" | "residualPaths"> = {},
|
|
100
|
+
): Response {
|
|
101
|
+
return jsonResponse({
|
|
102
|
+
error: status >= 500 ? "native integration change failed" : "native integration change refused",
|
|
103
|
+
code: status >= 500 ? "native_integration_failed" : "native_integration_refused",
|
|
104
|
+
clientId, reason, message, ...extra,
|
|
105
|
+
} satisfies NativeRefusalEnvelope, status);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function postCommitRefusal(
|
|
109
|
+
status: number,
|
|
110
|
+
clientId: NativeIntegrationClientId,
|
|
111
|
+
reason: NativeRefusalReason,
|
|
112
|
+
message: string,
|
|
113
|
+
extra: Pick<NativePostCommitRefusalEnvelope, "desiredEnabled" | "residualPaths">,
|
|
114
|
+
): Response {
|
|
115
|
+
return jsonResponse({
|
|
116
|
+
error: status >= 500 ? "native integration change failed" : "native integration change refused",
|
|
117
|
+
code: status >= 500 ? "native_integration_failed" : "native_integration_refused",
|
|
118
|
+
clientId, reason, message, ...extra,
|
|
119
|
+
} satisfies NativePostCommitRefusalEnvelope, status);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function desktopStatus(config: ManagementContext["config"]): NativeStatus {
|
|
123
|
+
const seen = inspectDesktop3pConfigLibrary({
|
|
124
|
+
appliedFingerprint: config.claudeCode?.desktopProfile?.appliedFingerprint ?? null,
|
|
125
|
+
});
|
|
126
|
+
const state: NativeStatus["state"] = seen.kind === "gateway_ours"
|
|
127
|
+
? "current"
|
|
128
|
+
: seen.kind === "unsafe" || seen.kind === "broken" ? "unsafe" : "absent";
|
|
129
|
+
const disableBlocked = seen.kind === "unsafe" || seen.kind === "broken" || seen.kind === "foreign"
|
|
130
|
+
? {
|
|
131
|
+
reason: seen.kind === "unsafe" && seen.reason === "metadata_unreadable" ? "metadata_unreadable" as const : "write_failed" as const,
|
|
132
|
+
message: "Claude Desktop configuration cannot be changed safely.",
|
|
133
|
+
}
|
|
134
|
+
: null;
|
|
135
|
+
return {
|
|
136
|
+
clientId: "claude-desktop",
|
|
137
|
+
state,
|
|
138
|
+
installed: seen.kind !== "not_installed",
|
|
139
|
+
configPath: seen.libraryPath,
|
|
140
|
+
desiredEnabled: config.clientIntegrations?.["claude-desktop"] !== false,
|
|
141
|
+
disableBlocked,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Absent means ON: the six read sites all treat only an explicit `false` as off. */
|
|
146
|
+
export function claudeCodeEnabled(config: ManagementContext["config"]): boolean {
|
|
147
|
+
return config.claudeCode?.enabled !== false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function claudeStatus(config: ManagementContext["config"], configPath: string): NativeStatus {
|
|
151
|
+
return {
|
|
152
|
+
clientId: "claude",
|
|
153
|
+
state: claudeCodeEnabled(config) ? "current" : "absent",
|
|
154
|
+
// The surface exists wherever the proxy does; there is no separate install.
|
|
155
|
+
installed: true,
|
|
156
|
+
configPath,
|
|
157
|
+
desiredEnabled: claudeCodeEnabled(config),
|
|
158
|
+
// Nothing can refuse this disable: no external file, no shared teardown.
|
|
159
|
+
disableBlocked: null,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function codexStatus(config: ManagementContext["config"], configPath: string): NativeStatus {
|
|
164
|
+
const desiredEnabled = config.clientIntegrations?.codex !== false;
|
|
165
|
+
return {
|
|
166
|
+
clientId: "codex",
|
|
167
|
+
state: desiredEnabled ? "current" : "absent",
|
|
168
|
+
installed: true,
|
|
169
|
+
configPath,
|
|
170
|
+
desiredEnabled,
|
|
171
|
+
disableBlocked: null,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Grok's GET row (030 §field table). `disableBlocked` is ADVISORY — the file
|
|
177
|
+
* can change before the PUT, which re-checks with the same inspector and whose
|
|
178
|
+
* answer is authoritative.
|
|
179
|
+
*/
|
|
180
|
+
function grokStatus(config: ManagementContext["config"]): NativeStatus {
|
|
181
|
+
const seen = inspectGrokConfig();
|
|
182
|
+
let disableBlocked: NativeStatus["disableBlocked"] = null;
|
|
183
|
+
if (seen.kind === "orphaned_marker") {
|
|
184
|
+
disableBlocked = {
|
|
185
|
+
reason: "orphaned_marker",
|
|
186
|
+
message: "The managed block in the Grok config is ambiguous (a begin marker with no end marker), so opencodex cannot tell where it ends and will not touch it. Remove the opencodex markers by hand, then retry.",
|
|
187
|
+
};
|
|
188
|
+
} else {
|
|
189
|
+
const owned = assertNativeTeardownOwned();
|
|
190
|
+
if (!owned.ok) disableBlocked = { reason: "home_mismatch", message: owned.message };
|
|
191
|
+
}
|
|
192
|
+
// A switch with a `never` guard, not a ternary: a future fifth GrokInspection
|
|
193
|
+
// kind must fail to COMPILE here, not silently read as `absent` (C-gate nit).
|
|
194
|
+
let state: NativeStatus["state"];
|
|
195
|
+
switch (seen.kind) {
|
|
196
|
+
case "present": state = "current"; break;
|
|
197
|
+
case "orphaned_marker": state = "unsafe"; break;
|
|
198
|
+
case "absent": case "not_installed": state = "absent"; break;
|
|
199
|
+
default: {
|
|
200
|
+
const exhaustive: never = seen;
|
|
201
|
+
throw new Error(`unhandled Grok inspection kind: ${JSON.stringify(exhaustive)}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
clientId: "grok",
|
|
206
|
+
state,
|
|
207
|
+
installed: seen.kind !== "not_installed",
|
|
208
|
+
configPath: grokConfigPath(),
|
|
209
|
+
desiredEnabled: config.clientIntegrations?.grok !== false,
|
|
210
|
+
disableBlocked,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Genuine lock contention, as opposed to a lock we could not open at all.
|
|
216
|
+
*
|
|
217
|
+
* `ConfigMutationLockError` carries a constant `code` and wraps EVERY
|
|
218
|
+
* acquisition failure — an unopenable database, an ACL that would not set, as
|
|
219
|
+
* well as a real conflict. Only the cause distinguishes them, and mapping the
|
|
220
|
+
* whole class to a retryable 409 would tell the user to retry a lock file they
|
|
221
|
+
* cannot open, which fails identically forever (audit r8 #2).
|
|
222
|
+
*/
|
|
223
|
+
function isLockContention(error: unknown): boolean {
|
|
224
|
+
if (!error || typeof error !== "object") return false;
|
|
225
|
+
const cause = (error as { cause?: { code?: unknown } }).cause;
|
|
226
|
+
return cause?.code === "SQLITE_BUSY";
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function isConfigLockError(error: unknown): boolean {
|
|
230
|
+
return !!error && typeof error === "object"
|
|
231
|
+
&& (error as { code?: unknown }).code === "CONFIG_MUTATION_LOCK_UNAVAILABLE";
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/*
|
|
235
|
+
* Single-flight for the Grok toggle (030 §Concurrency): two overlapping strips
|
|
236
|
+
* of the same file would race on bytes, so a second concurrent PUT is REFUSED
|
|
237
|
+
* with 409 config_busy — nothing was written, and retrying is correct advice.
|
|
238
|
+
* This deliberately does not share runGrokApplyFlight's JOIN semantics
|
|
239
|
+
* (/api/grok/apply joins an identical in-flight operation); toggle-vs-apply
|
|
240
|
+
* interleaving is covered not by exclusion but by the post-write inspection:
|
|
241
|
+
* whatever the file holds afterwards, the reported state is the last read
|
|
242
|
+
* (012 Rev 3 N3).
|
|
243
|
+
*/
|
|
244
|
+
let grokToggleFlight: Promise<Response> | null = null;
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Dynamic import, never static: management-api.ts statically imports THIS
|
|
248
|
+
* module, so a static import of fetchAllModels back from it would close a
|
|
249
|
+
* cycle. sync.ts:18-21 dodges the same cycle the same way.
|
|
250
|
+
*/
|
|
251
|
+
async function defaultFetchAllModels(config: OcxConfig) {
|
|
252
|
+
const { fetchAllModels } = await import("../management-api");
|
|
253
|
+
return fetchAllModels(config);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const ORPHANED_MARKER_MESSAGE =
|
|
257
|
+
"The managed block in the Grok config is ambiguous (a begin marker with no end marker), "
|
|
258
|
+
+ "so opencodex cannot tell where it ends and will not touch it. "
|
|
259
|
+
+ "Remove the opencodex markers by hand, then retry.";
|
|
260
|
+
|
|
261
|
+
const NOT_INSTALLED_MESSAGE =
|
|
262
|
+
"Grok home was not found, so there is nothing to change. Install Grok Build first.";
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* One Codex change at a time, for the same reason Grok has this: the guard
|
|
266
|
+
* stands BEFORE the first await, or two concurrent PUTs both pass it while their
|
|
267
|
+
* bodies are still parsing.
|
|
268
|
+
*/
|
|
269
|
+
let codexToggleFlight: Promise<Response> | null = null;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Turn native Codex routing on or off.
|
|
273
|
+
*
|
|
274
|
+
* THE PROXY STAYS UP. Turning Codex off is not `ocx stop`: other clients keep
|
|
275
|
+
* routing through this process, `/healthz` keeps answering, and only Codex goes
|
|
276
|
+
* back to its own path. That is the entire point of having a per-client switch
|
|
277
|
+
* rather than a kill switch, and it is why this route restores rather than
|
|
278
|
+
* stopping anything.
|
|
279
|
+
*
|
|
280
|
+
* Two writes, in this order:
|
|
281
|
+
* 1. persist the desired state, so the decision survives the next `ocx start`;
|
|
282
|
+
* 2. converge the artifacts to match it.
|
|
283
|
+
*
|
|
284
|
+
* Intent first is deliberate. If the process dies between them, the next start
|
|
285
|
+
* reads the persisted intent and converges — whereas converging first and dying
|
|
286
|
+
* would leave artifacts the next start silently undoes.
|
|
287
|
+
*/
|
|
288
|
+
async function handleCodexToggle(ctx: ManagementContext): Promise<Response> {
|
|
289
|
+
const { req } = ctx;
|
|
290
|
+
if (codexToggleFlight) {
|
|
291
|
+
return refusal(409, "codex", "config_busy",
|
|
292
|
+
"Another Codex change is already in flight. Nothing was written — try again in a moment.");
|
|
293
|
+
}
|
|
294
|
+
codexToggleFlight = (async (): Promise<Response> => {
|
|
295
|
+
let body: { enabled?: unknown };
|
|
296
|
+
try {
|
|
297
|
+
body = await readManagementJsonBody(req);
|
|
298
|
+
} catch (error) {
|
|
299
|
+
rethrowManagementBodyTooLarge(error);
|
|
300
|
+
return jsonResponse({ error: "invalid JSON body" }, 400);
|
|
301
|
+
}
|
|
302
|
+
if (typeof body.enabled !== "boolean") {
|
|
303
|
+
return jsonResponse({ error: "enabled must be a boolean" }, 400);
|
|
304
|
+
}
|
|
305
|
+
const enabled = body.enabled;
|
|
306
|
+
|
|
307
|
+
const { setCodexIntegrationEnabled } = await import("../../codex/desired-state");
|
|
308
|
+
const persisted = setCodexIntegrationEnabled(enabled);
|
|
309
|
+
/*
|
|
310
|
+
* `missing` does not block the switch — see the Grok route for the reasoning.
|
|
311
|
+
* A user with no config file yet still gets the artifact change; what they
|
|
312
|
+
* lose is durability across a restart, and the envelope says so rather than
|
|
313
|
+
* implying the switch failed.
|
|
314
|
+
*/
|
|
315
|
+
if (!persisted.ok && persisted.reason !== "missing") {
|
|
316
|
+
return refusal(
|
|
317
|
+
persisted.retryable ? 409 : 500,
|
|
318
|
+
"codex",
|
|
319
|
+
persisted.retryable ? "config_busy" : "write_failed",
|
|
320
|
+
persisted.message,
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
const durable = persisted.ok;
|
|
324
|
+
|
|
325
|
+
if (enabled) {
|
|
326
|
+
// The port this process actually BOUND, not what config.json last recorded.
|
|
327
|
+
// A stale config port would inject a base_url pointing at nothing — the
|
|
328
|
+
// same trap `runGrokApplyFlight` documents below.
|
|
329
|
+
const runtime = (ctx.deps.readRuntimePort ?? readRuntimePort)(process.pid);
|
|
330
|
+
const port = runtime?.port ?? ctx.config.port;
|
|
331
|
+
const { syncModelsToCodex } = await import("../../codex/sync");
|
|
332
|
+
const applied = await syncModelsToCodex(port);
|
|
333
|
+
if (applied.status === "skipped") {
|
|
334
|
+
return jsonResponse({
|
|
335
|
+
ok: true, clientId: "codex", changed: durable && persisted.status === "committed",
|
|
336
|
+
state: "absent",
|
|
337
|
+
desiredEnabled: enabled,
|
|
338
|
+
message: "Codex integration is OFF; enable did not change Codex.",
|
|
339
|
+
reason: "apply_incomplete",
|
|
340
|
+
} satisfies NativeToggleEnvelope);
|
|
341
|
+
}
|
|
342
|
+
return jsonResponse({
|
|
343
|
+
ok: true, clientId: "codex", changed: durable && persisted.status === "committed",
|
|
344
|
+
state: applied.ok ? "current" : "absent",
|
|
345
|
+
desiredEnabled: enabled,
|
|
346
|
+
message: applied.ok
|
|
347
|
+
? "Codex now routes through opencodex"
|
|
348
|
+
: `Codex intent saved, but applying it did not complete: ${applied.message}`,
|
|
349
|
+
...(applied.ok
|
|
350
|
+
? (durable ? {} : { reason: "not_durable" })
|
|
351
|
+
: { reason: "apply_incomplete" }),
|
|
352
|
+
} satisfies NativeToggleEnvelope);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// OFF. Restore the native path; the proxy keeps serving every other client.
|
|
356
|
+
if (durable && persisted.status === "unchanged") {
|
|
357
|
+
const { classifyNativeRoutedResidue } = await import("../../codex/native-residue");
|
|
358
|
+
if (classifyNativeRoutedResidue().kind === "clean") {
|
|
359
|
+
return jsonResponse({
|
|
360
|
+
ok: true, clientId: "codex", changed: false, state: "absent", desiredEnabled: false,
|
|
361
|
+
message: "Codex integration is already OFF and native; no Codex files changed.",
|
|
362
|
+
} satisfies NativeToggleEnvelope);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const { restoreNativeCodexAsync } = await import("../../codex/inject");
|
|
366
|
+
const restored = await restoreNativeCodexAsync({ revalidateDesiredState: true });
|
|
367
|
+
return jsonResponse({
|
|
368
|
+
ok: true, clientId: "codex", changed: durable && persisted.status === "committed",
|
|
369
|
+
state: restored.success ? "absent" : "unsafe",
|
|
370
|
+
desiredEnabled: enabled,
|
|
371
|
+
message: restored.success
|
|
372
|
+
? "Codex restored to its native path; the proxy is still serving other clients"
|
|
373
|
+
: `Codex intent saved, but restoring the native path did not complete: ${restored.message}`,
|
|
374
|
+
...(restored.success
|
|
375
|
+
? (durable ? {} : { reason: "not_durable" })
|
|
376
|
+
: { reason: "restore_incomplete" }),
|
|
377
|
+
artifacts: restored.artifacts,
|
|
378
|
+
} satisfies NativeToggleEnvelope);
|
|
379
|
+
})();
|
|
380
|
+
try {
|
|
381
|
+
return await codexToggleFlight;
|
|
382
|
+
} finally {
|
|
383
|
+
codexToggleFlight = null;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
async function handleGrokToggle(ctx: ManagementContext): Promise<Response> {
|
|
388
|
+
const { req, config, deps } = ctx;
|
|
389
|
+
/*
|
|
390
|
+
* The guard stands BEFORE the first await, or two concurrent PUTs could both
|
|
391
|
+
* pass it while their bodies are still parsing — the race the guard exists
|
|
392
|
+
* to close.
|
|
393
|
+
*/
|
|
394
|
+
if (grokToggleFlight) {
|
|
395
|
+
return refusal(409, "grok", "config_busy",
|
|
396
|
+
"Another Grok change is already in flight. Nothing was written — try again in a moment.");
|
|
397
|
+
}
|
|
398
|
+
grokToggleFlight = (async (): Promise<Response> => {
|
|
399
|
+
let body: { enabled?: unknown };
|
|
400
|
+
try {
|
|
401
|
+
body = await readManagementJsonBody(req);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
rethrowManagementBodyTooLarge(error);
|
|
404
|
+
return jsonResponse({ error: "invalid JSON body" }, 400);
|
|
405
|
+
}
|
|
406
|
+
if (typeof body.enabled !== "boolean") {
|
|
407
|
+
return jsonResponse({ error: "enabled must be a boolean" }, 400);
|
|
408
|
+
}
|
|
409
|
+
const enabled = body.enabled;
|
|
410
|
+
|
|
411
|
+
/*
|
|
412
|
+
* The inspector runs BEFORE either delegate, in BOTH directions (012 §In
|
|
413
|
+
* PUT it is the authoritative preflight): an ambiguous fence never reaches
|
|
414
|
+
* the code path that would misreport it, and the refusal is identical
|
|
415
|
+
* whichever direction the user was heading, because the reason is the same.
|
|
416
|
+
*/
|
|
417
|
+
const seen = inspectGrokConfig();
|
|
418
|
+
if (seen.kind === "not_installed") return refusal(404, "grok", "not_installed", NOT_INSTALLED_MESSAGE);
|
|
419
|
+
if (seen.kind === "orphaned_marker") return refusal(409, "grok", "orphaned_marker", ORPHANED_MARKER_MESSAGE);
|
|
420
|
+
|
|
421
|
+
/*
|
|
422
|
+
* Persist the DECISION before touching the fence.
|
|
423
|
+
*
|
|
424
|
+
* This route shipped without it, which is the whole bug: stripping the fence
|
|
425
|
+
* records nothing, so the next `ocx start` calls syncGrokConfig
|
|
426
|
+
* unconditionally and writes it straight back. The switch worked and lasted
|
|
427
|
+
* exactly one restart.
|
|
428
|
+
*
|
|
429
|
+
* Intent first, artifacts second, for the same reason as the Codex route: a
|
|
430
|
+
* process that dies between them leaves a decision the next start can act
|
|
431
|
+
* on, where the other order leaves artifacts the next start undoes.
|
|
432
|
+
*/
|
|
433
|
+
const { setGrokIntegrationEnabled } = await import("../../codex/desired-state");
|
|
434
|
+
const persisted = setGrokIntegrationEnabled(enabled);
|
|
435
|
+
/*
|
|
436
|
+
* `missing` does NOT block the toggle here.
|
|
437
|
+
*
|
|
438
|
+
* A config file that does not exist yet is a normal state for someone who
|
|
439
|
+
* has never saved settings, and refusing their switch because of it would
|
|
440
|
+
* make the button dead for exactly the users least able to diagnose why.
|
|
441
|
+
* The fence change is still worth performing and still reports honestly;
|
|
442
|
+
* what they lose is durability across a restart, which is what the reason
|
|
443
|
+
* on the envelope says. `conflict` and `invalid` are different: another
|
|
444
|
+
* writer won, or the file is malformed and must not be overwritten.
|
|
445
|
+
*/
|
|
446
|
+
if (!persisted.ok && persisted.reason !== "missing") {
|
|
447
|
+
return refusal(
|
|
448
|
+
persisted.retryable ? 409 : 500,
|
|
449
|
+
"grok",
|
|
450
|
+
persisted.retryable ? "config_busy" : "write_failed",
|
|
451
|
+
persisted.message,
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
const durable = persisted.ok;
|
|
455
|
+
const desiredEnabled = durable ? loadConfig().clientIntegrations?.grok !== false : enabled;
|
|
456
|
+
|
|
457
|
+
if (!enabled) {
|
|
458
|
+
/*
|
|
459
|
+
* Disable only: a strip is a SHARED teardown — it must not run under a
|
|
460
|
+
* service installed from another home (audit r1 #5). Enable is not
|
|
461
|
+
* gated: writing our own fence tears nothing down.
|
|
462
|
+
*/
|
|
463
|
+
const owned = assertNativeTeardownOwned();
|
|
464
|
+
if (!owned.ok) return postCommitRefusal(409, "grok", "home_mismatch", owned.message, { desiredEnabled });
|
|
465
|
+
|
|
466
|
+
const result = stripGrokConfig();
|
|
467
|
+
if (result.skippedReason === "no-grok-home") {
|
|
468
|
+
return postCommitRefusal(404, "grok", "not_installed", NOT_INSTALLED_MESSAGE, { desiredEnabled });
|
|
469
|
+
}
|
|
470
|
+
if (result.skippedReason === "orphaned-marker" || !result.ok) {
|
|
471
|
+
// Orphaned can still arrive between the preflight and the strip; the
|
|
472
|
+
// writer refuses it correctly and we map the refusal, never a retry lie.
|
|
473
|
+
return result.skippedReason === "orphaned-marker"
|
|
474
|
+
? postCommitRefusal(409, "grok", "orphaned_marker", ORPHANED_MARKER_MESSAGE, { desiredEnabled })
|
|
475
|
+
: postCommitRefusal(500, "grok", "write_failed", result.message, { desiredEnabled });
|
|
476
|
+
}
|
|
477
|
+
// The writer's own read IS the last read within this synchronous
|
|
478
|
+
// operation (012 Rev 3 N4): strip removed the fence, so absent.
|
|
479
|
+
return jsonResponse({
|
|
480
|
+
ok: true, clientId: "grok", changed: result.changed,
|
|
481
|
+
state: "absent",
|
|
482
|
+
desiredEnabled,
|
|
483
|
+
message: result.changed
|
|
484
|
+
? "Grok integration disabled — the opencodex block was removed. Re-enabling regenerates it from the current model list."
|
|
485
|
+
: "Grok integration is already off",
|
|
486
|
+
} satisfies NativeToggleEnvelope);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/*
|
|
490
|
+
* Enable. The fence must name the host/port the RUNNING process bound, not
|
|
491
|
+
* what config.json last recorded (Rev 3 N1; runGrokApplyFlight pattern):
|
|
492
|
+
* a stale config.hostname picks the wrong loopback policy branch entirely.
|
|
493
|
+
*/
|
|
494
|
+
const runtime = (deps.readRuntimePort ?? readRuntimePort)(process.pid);
|
|
495
|
+
const port = runtime?.port ?? (Number(ctx.url.port) || config.port);
|
|
496
|
+
const hostname = runtime?.hostname ?? config.hostname;
|
|
497
|
+
|
|
498
|
+
/*
|
|
499
|
+
* The catalog fetch is the ONLY await between check and write. Whatever
|
|
500
|
+
* the file looked like before it means nothing after it — so the recheck
|
|
501
|
+
* below runs after the fetch and immediately before the writer, which is
|
|
502
|
+
* synchronous from entry (012 §One preflight is not enough).
|
|
503
|
+
*/
|
|
504
|
+
const fetchModels = deps.fetchAllModels ?? defaultFetchAllModels;
|
|
505
|
+
let models: GrokInjectModel[];
|
|
506
|
+
try {
|
|
507
|
+
const routed = filterCatalogVisibleModels(await fetchModels(config), config);
|
|
508
|
+
models = [
|
|
509
|
+
// Native slugs carry their context window: without it Grok falls back
|
|
510
|
+
// to its own 200k default and understates a 372k model.
|
|
511
|
+
...visibleNativeSlugs(config).map(id => {
|
|
512
|
+
const contextWindow = nativeOpenAiContextWindow(id, nativeContextLimits(config));
|
|
513
|
+
return { id, ...(contextWindow !== undefined ? { contextWindow } : {}) };
|
|
514
|
+
}),
|
|
515
|
+
...routed.map(m => ({
|
|
516
|
+
id: m.alias ?? `${m.provider}/${m.id}`,
|
|
517
|
+
...(m.contextWindow !== undefined ? { contextWindow: m.contextWindow } : {}),
|
|
518
|
+
})),
|
|
519
|
+
];
|
|
520
|
+
} catch (error) {
|
|
521
|
+
// A catalog failure must never write an empty fence (syncGrokConfig
|
|
522
|
+
// guards this; the route inherits the rule). Nothing was written.
|
|
523
|
+
return postCommitRefusal(500, "grok", "write_failed",
|
|
524
|
+
`The model catalog is unavailable, so nothing was written (${error instanceof Error ? error.message : String(error)}). Try again once provider discovery recovers.`, { desiredEnabled });
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const recheck = inspectGrokConfig();
|
|
528
|
+
if (recheck.kind === "not_installed") return postCommitRefusal(404, "grok", "not_installed", NOT_INSTALLED_MESSAGE, { desiredEnabled });
|
|
529
|
+
if (recheck.kind === "orphaned_marker") return postCommitRefusal(409, "grok", "orphaned_marker", ORPHANED_MARKER_MESSAGE, { desiredEnabled });
|
|
530
|
+
|
|
531
|
+
const inject = deps.injectGrokConfig ?? injectGrokConfig;
|
|
532
|
+
const result = inject(port, models, {
|
|
533
|
+
...(hostname !== undefined ? { hostname } : {}),
|
|
534
|
+
// The FULL list plus the exclusion set, never a pre-filtered list: the
|
|
535
|
+
// writer allocates aliases over everything, so a model's alias never
|
|
536
|
+
// depends on its neighbours' switches.
|
|
537
|
+
excluded: new Set(config.grokExcludedModels ?? []),
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
if (result.skippedReason === "non-loopback") {
|
|
541
|
+
/*
|
|
542
|
+
* The non-loopback branch reports its own strip only through `changed`,
|
|
543
|
+
* so a strip that REFUSED and a strip that found nothing are
|
|
544
|
+
* indistinguishable in the result. EXHAUSTIVE post-inspection decides —
|
|
545
|
+
* the reported state is whatever the LAST read observed, never what the
|
|
546
|
+
* operation intended (audit r9).
|
|
547
|
+
*/
|
|
548
|
+
const after = inspectGrokConfig();
|
|
549
|
+
switch (after.kind) {
|
|
550
|
+
case "orphaned_marker":
|
|
551
|
+
return postCommitRefusal(409, "grok", "orphaned_marker", ORPHANED_MARKER_MESSAGE, { desiredEnabled });
|
|
552
|
+
case "present":
|
|
553
|
+
// A well-formed fence arrived from elsewhere between the strip and
|
|
554
|
+
// this read (`ocx ensure`, another proxy, a hand edit). It is not
|
|
555
|
+
// ours to remove under a policy that just declined to write one,
|
|
556
|
+
// and calling it `absent` would contradict the read.
|
|
557
|
+
return jsonResponse({
|
|
558
|
+
ok: true, clientId: "grok", changed: result.changed,
|
|
559
|
+
state: "current", reason: "non_loopback_superseded",
|
|
560
|
+
desiredEnabled,
|
|
561
|
+
message: "opencodex is bound to a non-loopback address, so this request did not write a block — but a well-formed opencodex block is present in the Grok config, written by something else. The card shows what is on disk.",
|
|
562
|
+
} satisfies NativeToggleEnvelope);
|
|
563
|
+
case "not_installed":
|
|
564
|
+
return postCommitRefusal(404, "grok", "not_installed", NOT_INSTALLED_MESSAGE, { desiredEnabled });
|
|
565
|
+
case "absent":
|
|
566
|
+
return jsonResponse({
|
|
567
|
+
ok: true, clientId: "grok", changed: result.changed,
|
|
568
|
+
state: "absent", reason: "non_loopback_removed",
|
|
569
|
+
desiredEnabled,
|
|
570
|
+
message: "opencodex is bound to a non-loopback address, so Grok cannot be auto-registered. The previously generated block was removed because it pointed at a loopback address that no longer serves.",
|
|
571
|
+
} satisfies NativeToggleEnvelope);
|
|
572
|
+
default: {
|
|
573
|
+
// A future fifth GrokInspection kind must fail to COMPILE, not fall
|
|
574
|
+
// through to a success-looking answer (C-gate nit).
|
|
575
|
+
const exhaustive: never = after;
|
|
576
|
+
throw new Error(`unhandled Grok inspection kind: ${JSON.stringify(exhaustive)}`);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (result.skippedReason === "no-grok-home") {
|
|
582
|
+
return postCommitRefusal(404, "grok", "not_installed", NOT_INSTALLED_MESSAGE, { desiredEnabled });
|
|
583
|
+
}
|
|
584
|
+
if (result.skippedReason === "orphaned-marker") {
|
|
585
|
+
return postCommitRefusal(409, "grok", "orphaned_marker", ORPHANED_MARKER_MESSAGE, { desiredEnabled });
|
|
586
|
+
}
|
|
587
|
+
if (!result.ok) {
|
|
588
|
+
return postCommitRefusal(500, "grok", "write_failed", result.message, { desiredEnabled });
|
|
589
|
+
}
|
|
590
|
+
return jsonResponse({
|
|
591
|
+
ok: true, clientId: "grok", changed: result.changed,
|
|
592
|
+
state: "current",
|
|
593
|
+
desiredEnabled,
|
|
594
|
+
message: result.changed ? "Grok integration enabled — the opencodex block was regenerated from the current model list." : "Grok integration is already on",
|
|
595
|
+
} satisfies NativeToggleEnvelope);
|
|
596
|
+
})();
|
|
597
|
+
try {
|
|
598
|
+
return await grokToggleFlight;
|
|
599
|
+
} finally {
|
|
600
|
+
grokToggleFlight = null;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
let claudeDesktopToggleFlight: Promise<Response> | null = null;
|
|
605
|
+
|
|
606
|
+
async function handleClaudeDesktopToggle(ctx: ManagementContext): Promise<Response> {
|
|
607
|
+
if (claudeDesktopToggleFlight) {
|
|
608
|
+
return refusal(409, "claude-desktop", "config_busy",
|
|
609
|
+
"Another Claude Desktop change is already in flight. Nothing was written — try again in a moment.");
|
|
610
|
+
}
|
|
611
|
+
claudeDesktopToggleFlight = (async (): Promise<Response> => {
|
|
612
|
+
let body: { enabled?: unknown };
|
|
613
|
+
try {
|
|
614
|
+
body = await readManagementJsonBody(ctx.req);
|
|
615
|
+
} catch (error) {
|
|
616
|
+
rethrowManagementBodyTooLarge(error);
|
|
617
|
+
return jsonResponse({ error: "invalid JSON body" }, 400);
|
|
618
|
+
}
|
|
619
|
+
if (typeof body.enabled !== "boolean") return jsonResponse({ error: "enabled must be a boolean" }, 400);
|
|
620
|
+
|
|
621
|
+
const { setIntegrationEnabled } = await import("../../codex/desired-state");
|
|
622
|
+
const persisted = setIntegrationEnabled("claude-desktop", body.enabled);
|
|
623
|
+
if (!persisted.ok) {
|
|
624
|
+
return refusal(persisted.retryable ? 409 : 500, "claude-desktop", persisted.retryable ? "config_busy" : "write_failed", persisted.message);
|
|
625
|
+
}
|
|
626
|
+
const desiredEnabled = loadConfig().clientIntegrations?.["claude-desktop"] !== false;
|
|
627
|
+
const current = loadConfig();
|
|
628
|
+
const fingerprint = current.claudeCode?.desktopProfile?.appliedFingerprint ?? null;
|
|
629
|
+
|
|
630
|
+
if (!body.enabled) {
|
|
631
|
+
const removed = (ctx.deps.removeDesktop3pStandardPivot ?? removeDesktop3pStandardPivot)({ appliedFingerprint: fingerprint });
|
|
632
|
+
if (removed.kind === "cleanup_incomplete") {
|
|
633
|
+
return postCommitRefusal(500, "claude-desktop", "cleanup_incomplete",
|
|
634
|
+
"Claude Desktop now points at standard mode, but credential cleanup is incomplete.",
|
|
635
|
+
{ desiredEnabled, residualPaths: removed.residualPaths ?? [] });
|
|
636
|
+
}
|
|
637
|
+
if (!removed.ok) {
|
|
638
|
+
return postCommitRefusal(409, "claude-desktop", removed.reason === "metadata_unreadable" ? "metadata_unreadable" : "write_failed",
|
|
639
|
+
"Claude Desktop configuration could not be changed safely.", { desiredEnabled });
|
|
640
|
+
}
|
|
641
|
+
return jsonResponse({
|
|
642
|
+
ok: true, clientId: "claude-desktop", changed: removed.changed, state: "absent", desiredEnabled,
|
|
643
|
+
message: removed.changed ? "Claude Desktop integration disabled." : "Claude Desktop integration is already off.",
|
|
644
|
+
} satisfies NativeToggleEnvelope);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const fetchModels = ctx.deps.fetchAllModels ?? defaultFetchAllModels;
|
|
648
|
+
try {
|
|
649
|
+
const fetched = await fetchModels(current);
|
|
650
|
+
const latest = loadConfig();
|
|
651
|
+
const latestDesiredEnabled = latest.clientIntegrations?.["claude-desktop"] !== false;
|
|
652
|
+
if (!latestDesiredEnabled) {
|
|
653
|
+
return postCommitRefusal(409, "claude-desktop", "desired_state_changed",
|
|
654
|
+
"Claude Desktop enable was cancelled because the desired state changed to off.", { desiredEnabled: latestDesiredEnabled });
|
|
655
|
+
}
|
|
656
|
+
const routed = filterCatalogVisibleModels(fetched, latest).map(model => ({
|
|
657
|
+
provider: model.provider, id: model.id, contextWindow: model.contextWindow,
|
|
658
|
+
}));
|
|
659
|
+
const runtime = (ctx.deps.readRuntimePort ?? readRuntimePort)(process.pid);
|
|
660
|
+
const result = (ctx.deps.writeDesktop3pConfig ?? writeDesktop3pConfig)(
|
|
661
|
+
runtime?.port ?? latest.port,
|
|
662
|
+
[...desktopVisibleNativeSlugs(latest)],
|
|
663
|
+
routed,
|
|
664
|
+
latest.apiKeys?.[0]?.key,
|
|
665
|
+
"static",
|
|
666
|
+
latest.claudeCode?.desktopProfile,
|
|
667
|
+
nativeContextLimits(latest),
|
|
668
|
+
);
|
|
669
|
+
if (!result.written) return postCommitRefusal(500, "claude-desktop", "write_failed", "Claude Desktop apply failed.", { desiredEnabled: latestDesiredEnabled });
|
|
670
|
+
return jsonResponse({
|
|
671
|
+
ok: true, clientId: "claude-desktop", changed: true, state: "current", desiredEnabled: latestDesiredEnabled,
|
|
672
|
+
message: "Claude Desktop integration enabled.",
|
|
673
|
+
} satisfies NativeToggleEnvelope);
|
|
674
|
+
} catch {
|
|
675
|
+
return postCommitRefusal(500, "claude-desktop", "write_failed", "Claude Desktop apply failed.", { desiredEnabled });
|
|
676
|
+
}
|
|
677
|
+
})();
|
|
678
|
+
try {
|
|
679
|
+
return await claudeDesktopToggleFlight;
|
|
680
|
+
} finally {
|
|
681
|
+
claudeDesktopToggleFlight = null;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
export async function handleNativeIntegrationRoutes(ctx: ManagementContext): Promise<Response | null> {
|
|
686
|
+
const { req, url, config, deps } = ctx;
|
|
687
|
+
|
|
688
|
+
if (url.pathname === "/api/native-integrations" && req.method === "GET") {
|
|
689
|
+
const { getConfigPath } = await import("../../config");
|
|
690
|
+
return jsonResponse({
|
|
691
|
+
clients: [claudeStatus(config, getConfigPath()), grokStatus(config), codexStatus(config, getConfigPath()), desktopStatus(config)],
|
|
692
|
+
} satisfies NativeStatusListEnvelope);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (url.pathname === "/api/native-integrations/claude" && req.method === "PUT") {
|
|
696
|
+
let body: { enabled?: unknown };
|
|
697
|
+
try {
|
|
698
|
+
body = await readManagementJsonBody(req);
|
|
699
|
+
} catch (error) {
|
|
700
|
+
rethrowManagementBodyTooLarge(error);
|
|
701
|
+
return jsonResponse({ error: "invalid JSON body" }, 400);
|
|
702
|
+
}
|
|
703
|
+
if (!body || typeof body !== "object" || Array.isArray(body) || typeof body.enabled !== "boolean") {
|
|
704
|
+
return jsonResponse({ error: "enabled must be a boolean" }, 400);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const enabled = body.enabled;
|
|
708
|
+
if (claudeCodeEnabled(config) === enabled) {
|
|
709
|
+
return jsonResponse({
|
|
710
|
+
ok: true, clientId: "claude", changed: false,
|
|
711
|
+
state: enabled ? "current" : "absent",
|
|
712
|
+
desiredEnabled: enabled,
|
|
713
|
+
message: enabled ? "Claude inbound is already on" : "Claude inbound is already off",
|
|
714
|
+
} satisfies NativeToggleEnvelope);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const next = { ...(config.claudeCode ?? {}), enabled };
|
|
718
|
+
/*
|
|
719
|
+
* Stamp the migration sentinel on every persist of this block, exactly as
|
|
720
|
+
* PUT /api/claude-code does (agent-settings-routes.ts:1068).
|
|
721
|
+
*
|
|
722
|
+
* The migration reads "a claudeCode block with no authMode" as a pre-upgrade
|
|
723
|
+
* subscriber and pins it to literal subscription. Toggling Claude ON is one
|
|
724
|
+
* of the two ways a block gets CREATED, so without this the next startServer
|
|
725
|
+
* would silently convert a user's Auto auth mode into a sticky manual
|
|
726
|
+
* subscription — a failure that surfaces nowhere near this route.
|
|
727
|
+
*/
|
|
728
|
+
if (!next.authModeMigratedAt) next.authModeMigratedAt = new Date().toISOString();
|
|
729
|
+
config.claudeCode = next;
|
|
730
|
+
|
|
731
|
+
/*
|
|
732
|
+
* `deps.` first: ManagementApiDeps carries this seam so route tests with an
|
|
733
|
+
* in-memory fixture cannot overwrite the developer's real OPENCODEX_HOME.
|
|
734
|
+
*/
|
|
735
|
+
const persist = deps.saveConfigPreservingClaudeCode ?? saveConfigPreservingClaudeCode;
|
|
736
|
+
try {
|
|
737
|
+
persist(config);
|
|
738
|
+
} catch (error) {
|
|
739
|
+
if (isConfigLockError(error)) {
|
|
740
|
+
return isLockContention(error)
|
|
741
|
+
? refusal(409, "claude", "config_busy",
|
|
742
|
+
"Another process is saving the configuration right now. Try again in a moment.")
|
|
743
|
+
: refusal(500, "claude", "write_failed",
|
|
744
|
+
`The configuration lock could not be acquired: ${error instanceof Error ? error.message : String(error)}`);
|
|
745
|
+
}
|
|
746
|
+
throw error;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
return jsonResponse({
|
|
750
|
+
ok: true, clientId: "claude", changed: true,
|
|
751
|
+
state: enabled ? "current" : "absent",
|
|
752
|
+
desiredEnabled: enabled,
|
|
753
|
+
message: enabled ? "Claude inbound enabled" : "Claude inbound disabled",
|
|
754
|
+
} satisfies NativeToggleEnvelope);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (url.pathname === "/api/native-integrations/grok" && req.method === "PUT") {
|
|
758
|
+
return handleGrokToggle(ctx);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (url.pathname === "/api/native-integrations/codex" && req.method === "PUT") {
|
|
762
|
+
return handleCodexToggle(ctx);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
if (url.pathname === "/api/native-integrations/claude-desktop" && req.method === "PUT") {
|
|
766
|
+
return handleClaudeDesktopToggle(ctx);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
return null;
|
|
770
|
+
}
|