@remodex/rmx 1.0.2
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 +91 -0
- package/LICENSE +21 -0
- package/README.md +242 -0
- package/assets/architecture.png +0 -0
- package/assets/banner.png +0 -0
- package/assets/claude-code-models.gif +0 -0
- package/assets/codex-app-picker.png +0 -0
- package/bin/ocx.mjs +584 -0
- package/bin/package-main.mjs +9 -0
- package/gui/dist/assets/index-CZqebSPQ.css +1 -0
- package/gui/dist/assets/index-CkETtt7P.js +71 -0
- package/gui/dist/favicon.png +0 -0
- package/gui/dist/fonts/google-sans-cyrillic.woff2 +0 -0
- package/gui/dist/fonts/google-sans-latin.woff2 +0 -0
- package/gui/dist/icons.svg +24 -0
- package/gui/dist/index.html +25 -0
- package/gui/dist/logo.png +0 -0
- package/gui/dist/provider-icons/alibaba-color.svg +1 -0
- package/gui/dist/provider-icons/antigravity-color.svg +1 -0
- package/gui/dist/provider-icons/claude-color.svg +1 -0
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -0
- package/gui/dist/provider-icons/cursor-color.svg +2 -0
- package/gui/dist/provider-icons/deepseek-color.svg +1 -0
- package/gui/dist/provider-icons/discord.svg +1 -0
- package/gui/dist/provider-icons/firepass-color.svg +1 -0
- package/gui/dist/provider-icons/fireworks-color.svg +1 -0
- package/gui/dist/provider-icons/gemini-color.svg +1 -0
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
- package/gui/dist/provider-icons/grok.svg +1 -0
- package/gui/dist/provider-icons/groq-color.svg +1 -0
- package/gui/dist/provider-icons/huggingface-color.svg +1 -0
- package/gui/dist/provider-icons/kimi-color.svg +1 -0
- package/gui/dist/provider-icons/kiro-color.svg +15 -0
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
- package/gui/dist/provider-icons/mistral-color.svg +1 -0
- package/gui/dist/provider-icons/moonshot-color.svg +1 -0
- package/gui/dist/provider-icons/nvidia-color.svg +1 -0
- package/gui/dist/provider-icons/ollama-color.svg +1 -0
- package/gui/dist/provider-icons/openai.svg +1 -0
- package/gui/dist/provider-icons/opencode.svg +2 -0
- package/gui/dist/provider-icons/openrouter-color.svg +1 -0
- package/gui/dist/provider-icons/pi.svg +21 -0
- package/gui/dist/provider-icons/qianfan-color.svg +1 -0
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
- package/gui/dist/provider-icons/telegram.svg +1 -0
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/vllm-color.svg +1 -0
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
- package/package.json +118 -0
- package/src/AGENTS.md +28 -0
- package/src/adapters/anthropic-image-guard.ts +251 -0
- package/src/adapters/anthropic-image-normalize.ts +518 -0
- package/src/adapters/anthropic.ts +1205 -0
- package/src/adapters/azure.ts +36 -0
- package/src/adapters/base.ts +83 -0
- package/src/adapters/client-fingerprint.ts +59 -0
- package/src/adapters/command-code.ts +453 -0
- package/src/adapters/cursor/arg-codec.ts +38 -0
- package/src/adapters/cursor/arg-normalize.ts +104 -0
- package/src/adapters/cursor/cursor-errors.ts +165 -0
- package/src/adapters/cursor/discovery.ts +276 -0
- package/src/adapters/cursor/effort-map.ts +139 -0
- package/src/adapters/cursor/exec-policy.ts +88 -0
- package/src/adapters/cursor/framing.ts +250 -0
- package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
- package/src/adapters/cursor/kv-store.ts +52 -0
- package/src/adapters/cursor/live-models.ts +153 -0
- package/src/adapters/cursor/live-smoke-gate.ts +41 -0
- package/src/adapters/cursor/live-transport.ts +1235 -0
- package/src/adapters/cursor/mcp-config.ts +42 -0
- package/src/adapters/cursor/mcp-manager.ts +333 -0
- package/src/adapters/cursor/message-mapper.ts +49 -0
- package/src/adapters/cursor/native-exec-common.ts +59 -0
- package/src/adapters/cursor/native-exec-desktop.ts +184 -0
- package/src/adapters/cursor/native-exec-fs.ts +332 -0
- package/src/adapters/cursor/native-exec-mcp.ts +153 -0
- package/src/adapters/cursor/native-exec-network.ts +43 -0
- package/src/adapters/cursor/native-exec-shell.ts +548 -0
- package/src/adapters/cursor/native-exec-tools.ts +118 -0
- package/src/adapters/cursor/native-exec.ts +604 -0
- package/src/adapters/cursor/protobuf-events.ts +735 -0
- package/src/adapters/cursor/protobuf-request.ts +719 -0
- package/src/adapters/cursor/request-builder.ts +280 -0
- package/src/adapters/cursor/thread-continuity.ts +67 -0
- package/src/adapters/cursor/tool-definitions.ts +621 -0
- package/src/adapters/cursor/transport-retry.ts +132 -0
- package/src/adapters/cursor/transport.ts +57 -0
- package/src/adapters/cursor/types.ts +59 -0
- package/src/adapters/cursor.ts +196 -0
- package/src/adapters/google-antigravity-replay.ts +520 -0
- package/src/adapters/google-antigravity-wire.ts +140 -0
- package/src/adapters/google-errors.ts +85 -0
- package/src/adapters/google-http.ts +100 -0
- package/src/adapters/google-tool-schema.ts +173 -0
- package/src/adapters/google-truncation.ts +24 -0
- package/src/adapters/google-wire-compiler.ts +232 -0
- package/src/adapters/google.ts +859 -0
- package/src/adapters/identity.ts +77 -0
- package/src/adapters/image.ts +23 -0
- package/src/adapters/kiro-constants.ts +16 -0
- package/src/adapters/kiro-errors.ts +208 -0
- package/src/adapters/kiro-events.ts +197 -0
- package/src/adapters/kiro-images.ts +129 -0
- package/src/adapters/kiro-retry.ts +312 -0
- package/src/adapters/kiro-thinking.ts +104 -0
- package/src/adapters/kiro-tool-fallback.ts +36 -0
- package/src/adapters/kiro-tools.ts +224 -0
- package/src/adapters/kiro-truncation.ts +33 -0
- package/src/adapters/kiro-wire.ts +129 -0
- package/src/adapters/kiro.ts +1924 -0
- package/src/adapters/mimo-free.ts +263 -0
- package/src/adapters/openai-chat.ts +1265 -0
- package/src/adapters/openai-responses.ts +1309 -0
- package/src/adapters/run-turn-queue.ts +114 -0
- package/src/adapters/tool-catalog-nudge.ts +71 -0
- package/src/adapters/upstream-http-error.ts +48 -0
- package/src/android-remote/assets.ts +218 -0
- package/src/android-remote/attachments.ts +168 -0
- package/src/android-remote/auth.ts +237 -0
- package/src/android-remote/cloudflare-provisioning.ts +409 -0
- package/src/android-remote/cloudflare-secret.ts +106 -0
- package/src/android-remote/cloudflare-tunnel.ts +488 -0
- package/src/android-remote/cloudflared.ts +286 -0
- package/src/android-remote/codex-app-server.ts +565 -0
- package/src/android-remote/desktop-history-page.ts +936 -0
- package/src/android-remote/desktop-ipc.ts +3643 -0
- package/src/android-remote/desktop-ownership-store.ts +98 -0
- package/src/android-remote/desktop-project-registration.ts +129 -0
- package/src/android-remote/desktop-session-stream.ts +1110 -0
- package/src/android-remote/desktop-workspace-state.ts +443 -0
- package/src/android-remote/file-change-parser.ts +112 -0
- package/src/android-remote/gateway.ts +9108 -0
- package/src/android-remote/mutation-store.ts +299 -0
- package/src/android-remote/projection.ts +1780 -0
- package/src/android-remote/queued-turn-store.ts +248 -0
- package/src/android-remote/session-command-recovery.ts +1384 -0
- package/src/android-remote/store.ts +466 -0
- package/src/android-remote/thread-reconciliation.ts +133 -0
- package/src/android-remote/thread-source-paths.ts +307 -0
- package/src/android-remote/thread-stream.ts +546 -0
- package/src/android-remote/turn-activity.ts +235 -0
- package/src/android-remote/user-message-identity.ts +94 -0
- package/src/bridge.ts +1793 -0
- package/src/chat/inbound.ts +295 -0
- package/src/chat/outbound.ts +821 -0
- package/src/claude/agents-inject.ts +267 -0
- package/src/claude/alias.ts +149 -0
- package/src/claude/auth-detect.ts +229 -0
- package/src/claude/auth-mode-migration.ts +32 -0
- package/src/claude/auth-mode.ts +62 -0
- package/src/claude/context-windows.ts +189 -0
- package/src/claude/desktop-3p-guard.ts +35 -0
- package/src/claude/desktop-3p-paths.ts +84 -0
- package/src/claude/desktop-3p.ts +601 -0
- package/src/claude/desktop-health.ts +26 -0
- package/src/claude/desktop-profile.ts +263 -0
- package/src/claude/gateway-cache.ts +70 -0
- package/src/claude/inbound-debug.ts +163 -0
- package/src/claude/inbound.ts +519 -0
- package/src/claude/model-info.ts +154 -0
- package/src/claude/outbound.ts +898 -0
- package/src/cli/access.ts +108 -0
- package/src/cli/account-api.ts +296 -0
- package/src/cli/account-auth.ts +250 -0
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +476 -0
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +297 -0
- package/src/cli/agent-driven.ts +70 -0
- package/src/cli/agent.ts +184 -0
- package/src/cli/catalog-prewarm.ts +27 -0
- package/src/cli/claude-desktop.ts +211 -0
- package/src/cli/claude.ts +302 -0
- package/src/cli/codex-shim-autorestore.ts +45 -0
- package/src/cli/codex-shim-readiness.ts +69 -0
- package/src/cli/combo.ts +124 -0
- package/src/cli/config-command.ts +183 -0
- package/src/cli/debug.ts +228 -0
- package/src/cli/desktop-first-run.ts +25 -0
- package/src/cli/doctor.ts +1022 -0
- package/src/cli/export-command.ts +201 -0
- package/src/cli/help.ts +370 -0
- package/src/cli/index.ts +1565 -0
- package/src/cli/init.ts +224 -0
- package/src/cli/integrations.ts +225 -0
- package/src/cli/interactive-confirm.ts +133 -0
- package/src/cli/internal-dispatch.ts +35 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/models-runtime.ts +224 -0
- package/src/cli/models.ts +340 -0
- package/src/cli/observe.ts +170 -0
- package/src/cli/opencode.ts +587 -0
- package/src/cli/provider-runtime.ts +179 -0
- package/src/cli/provider.ts +476 -0
- package/src/cli/ready.ts +301 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +328 -0
- package/src/cli/star-prompt.ts +211 -0
- package/src/cli/status-oauth.ts +78 -0
- package/src/cli/status.ts +321 -0
- package/src/cli/system-command.ts +196 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +205 -0
- package/src/cli/v2.ts +200 -0
- package/src/cli.ts +10 -0
- package/src/clients/config-export.ts +1109 -0
- package/src/codex/account-id.ts +34 -0
- package/src/codex/account-label.ts +34 -0
- package/src/codex/account-lifecycle.ts +172 -0
- package/src/codex/account-namespace-match.ts +63 -0
- package/src/codex/account-namespaces.ts +195 -0
- package/src/codex/account-pause.ts +20 -0
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/account-runtime-state.ts +31 -0
- package/src/codex/account-store.ts +517 -0
- package/src/codex/account-usability.ts +40 -0
- package/src/codex/admission.ts +263 -0
- package/src/codex/app-server-processes.ts +799 -0
- package/src/codex/auth-api.ts +2098 -0
- package/src/codex/auth-collision.ts +107 -0
- package/src/codex/auth-context.ts +480 -0
- package/src/codex/autostart-health.ts +156 -0
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +471 -0
- package/src/codex/catalog/bundled.ts +533 -0
- package/src/codex/catalog/effort.ts +432 -0
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +287 -0
- package/src/codex/catalog/native-models.ts +7 -0
- package/src/codex/catalog/parsing.ts +503 -0
- package/src/codex/catalog/provider-fetch.ts +2267 -0
- package/src/codex/catalog/sync.ts +1606 -0
- package/src/codex/catalog-admission.ts +197 -0
- package/src/codex/catalog-refresh-status.ts +87 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +15 -0
- package/src/codex/codex-write-lock.ts +384 -0
- package/src/codex/convergence-types.ts +593 -0
- package/src/codex/convergence.ts +580 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/data/upstream-models.json +830 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/desktop-client-processes.ts +521 -0
- package/src/codex/exec-invocation.ts +22 -0
- package/src/codex/features.ts +1091 -0
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +347 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-migration-guardian.ts +115 -0
- package/src/codex/history-provider.ts +1075 -0
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +204 -0
- package/src/codex/home.ts +206 -0
- package/src/codex/inject-coordination.ts +257 -0
- package/src/codex/inject.ts +1857 -0
- package/src/codex/injected-marker.ts +79 -0
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +105 -0
- package/src/codex/journal.ts +172 -0
- package/src/codex/main-account-cache.ts +56 -0
- package/src/codex/main-account.ts +40 -0
- package/src/codex/management-convergence.ts +114 -0
- package/src/codex/model-cache.ts +267 -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 +167 -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 +348 -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 +691 -0
- package/src/codex/paths.ts +78 -0
- package/src/codex/plugins-doctor.ts +242 -0
- package/src/codex/pool-rotation.ts +295 -0
- package/src/codex/project-config-warnings.ts +426 -0
- package/src/codex/prompt-journal.ts +311 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/provider-adoption.ts +242 -0
- package/src/codex/quota-rejection.ts +224 -0
- package/src/codex/quota.ts +494 -0
- package/src/codex/refresh.ts +60 -0
- package/src/codex/routing.ts +1855 -0
- package/src/codex/runtime.ts +659 -0
- package/src/codex/shim.ts +1215 -0
- package/src/codex/subagent-defaults.ts +557 -0
- package/src/codex/subagent-model-fallback.ts +560 -0
- package/src/codex/sync.ts +238 -0
- package/src/codex/transition-state.ts +612 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +374 -0
- package/src/codex/warmup.ts +192 -0
- package/src/codex/websocket-registry.ts +100 -0
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +140 -0
- package/src/combos/index.ts +44 -0
- package/src/combos/request.ts +64 -0
- package/src/combos/resolve.ts +232 -0
- package/src/combos/types.ts +392 -0
- package/src/config.ts +3270 -0
- package/src/generated/model-metadata.ts +144 -0
- package/src/github/star-state.ts +203 -0
- package/src/grok/inject.ts +540 -0
- package/src/grok/inspect.ts +45 -0
- package/src/grok/status.ts +127 -0
- package/src/grok/sync.ts +66 -0
- package/src/images/artifacts.ts +516 -0
- package/src/images/fulfill-video.ts +163 -0
- package/src/images/fulfill.ts +149 -0
- package/src/images/index.ts +4 -0
- package/src/images/loop.ts +922 -0
- package/src/images/plan.ts +133 -0
- package/src/images/synthetic-tool.ts +133 -0
- package/src/images/types.ts +41 -0
- package/src/images/xai-client.ts +141 -0
- package/src/images/xai-video-client.ts +163 -0
- package/src/index.ts +22 -0
- package/src/integrations/config-io.ts +151 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +108 -0
- package/src/integrations/serialize.ts +235 -0
- package/src/integrations/state.ts +290 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer.ts +492 -0
- package/src/lib/abort.ts +146 -0
- package/src/lib/admin-secrets.ts +25 -0
- package/src/lib/admission.ts +83 -0
- package/src/lib/app-owned-memory-stores.ts +173 -0
- package/src/lib/app-owned-memory.ts +265 -0
- package/src/lib/bounded-body.ts +242 -0
- package/src/lib/bun-binary-validator.d.mts +3 -0
- package/src/lib/bun-binary-validator.mjs +18 -0
- package/src/lib/bun-runtime.ts +184 -0
- package/src/lib/bun-stream-caps.ts +127 -0
- package/src/lib/config-ownership.ts +438 -0
- package/src/lib/crash-guard.ts +344 -0
- package/src/lib/debug-log-buffer.ts +83 -0
- package/src/lib/debug-settings.ts +108 -0
- package/src/lib/debug.ts +31 -0
- package/src/lib/destination-policy.ts +316 -0
- package/src/lib/errors.ts +364 -0
- package/src/lib/eventstream-decoder.ts +253 -0
- package/src/lib/gcp-adc.ts +341 -0
- package/src/lib/injection-debug-log.ts +58 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/open-url.ts +25 -0
- package/src/lib/pinned-http.ts +182 -0
- package/src/lib/privacy.ts +20 -0
- package/src/lib/process-control.ts +168 -0
- package/src/lib/provider-environment.ts +470 -0
- package/src/lib/provider-outbound.ts +203 -0
- package/src/lib/provider-url.ts +14 -0
- package/src/lib/proxy-env.ts +18 -0
- package/src/lib/redact.ts +510 -0
- package/src/lib/remodex-home.ts +616 -0
- package/src/lib/retry-after.ts +55 -0
- package/src/lib/service-secrets.ts +178 -0
- package/src/lib/shadow-call.ts +54 -0
- package/src/lib/sidecar-tracker.ts +52 -0
- package/src/lib/sse-decoder.ts +364 -0
- package/src/lib/state-store-registrations.ts +109 -0
- package/src/lib/state-store-sweeper.ts +184 -0
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/test-home-guard.ts +98 -0
- package/src/lib/token-estimate.ts +69 -0
- package/src/lib/translator-budget.ts +366 -0
- package/src/lib/upstream-reachability.ts +91 -0
- package/src/lib/upstream-retry.ts +508 -0
- package/src/lib/win-exec.ts +115 -0
- package/src/lib/win-paths.ts +68 -0
- package/src/lib/windows-elevation.ts +705 -0
- package/src/lib/windows-secret-acl.ts +817 -0
- package/src/lib/windows-user-principal.ts +283 -0
- package/src/lib/winsw.ts +402 -0
- package/src/model-sources.ts +73 -0
- package/src/oauth/anthropic-routing.ts +594 -0
- package/src/oauth/anthropic.ts +177 -0
- package/src/oauth/callback-server.ts +294 -0
- package/src/oauth/chatgpt.ts +150 -0
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +231 -0
- package/src/oauth/github-copilot.ts +428 -0
- package/src/oauth/google-antigravity.ts +230 -0
- package/src/oauth/health.ts +443 -0
- package/src/oauth/index.ts +1280 -0
- package/src/oauth/key-providers.ts +128 -0
- package/src/oauth/kimi.ts +213 -0
- package/src/oauth/kiro-credentials.ts +726 -0
- package/src/oauth/kiro.ts +621 -0
- package/src/oauth/local-token-detect.ts +121 -0
- package/src/oauth/log.ts +48 -0
- package/src/oauth/login-cli.ts +163 -0
- package/src/oauth/pkce.ts +15 -0
- package/src/oauth/store.ts +655 -0
- package/src/oauth/token-guardian.ts +309 -0
- package/src/oauth/types.ts +62 -0
- package/src/oauth/xai.ts +241 -0
- package/src/providers/alibaba-region-backup.ts +75 -0
- package/src/providers/alibaba-region-migration.ts +156 -0
- package/src/providers/alibaba-region-startup.ts +36 -0
- package/src/providers/antigravity-models.ts +317 -0
- package/src/providers/api-keys.ts +140 -0
- package/src/providers/base-url-choices.ts +64 -0
- package/src/providers/codex-capacity.ts +288 -0
- package/src/providers/command-code-efforts.ts +85 -0
- package/src/providers/context-cap.ts +73 -0
- package/src/providers/derive.ts +451 -0
- package/src/providers/free-directory.ts +187 -0
- package/src/providers/github-copilot-transport.ts +56 -0
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +271 -0
- package/src/providers/kiro-models.ts +67 -0
- package/src/providers/label.ts +19 -0
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +361 -0
- package/src/providers/openai-sidecar.ts +235 -0
- package/src/providers/openai-tier-startup.ts +27 -0
- package/src/providers/openai-tiers.ts +301 -0
- package/src/providers/openai-virtual-models.ts +83 -0
- package/src/providers/openrouter-routing.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +179 -0
- package/src/providers/quota.ts +1942 -0
- package/src/providers/reasoning-capabilities.ts +336 -0
- package/src/providers/registry.ts +2375 -0
- package/src/providers/slug-codec.ts +74 -0
- package/src/providers/xai-transport.ts +149 -0
- package/src/reasoning-effort.ts +243 -0
- package/src/responses/compaction.ts +124 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/parser.ts +714 -0
- package/src/responses/reasoning-envelope.ts +60 -0
- package/src/responses/reasoning-replay-cache.ts +106 -0
- package/src/responses/schema.ts +159 -0
- package/src/responses/spill-store.ts +431 -0
- package/src/responses/state.ts +1039 -0
- package/src/responses/tool-groups.ts +19 -0
- package/src/router.ts +802 -0
- package/src/routing/analytics.ts +377 -0
- package/src/routing/capability.ts +205 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +444 -0
- package/src/routing/health.ts +401 -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 +424 -0
- package/src/routing/quota.ts +145 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +686 -0
- package/src/server/adapter-resolve.ts +83 -0
- package/src/server/auth-cors.ts +606 -0
- package/src/server/chat-completions.ts +379 -0
- package/src/server/claude-messages.ts +980 -0
- package/src/server/effort-policy.ts +251 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +152 -0
- package/src/server/image-retry.ts +42 -0
- package/src/server/images.ts +485 -0
- package/src/server/index.ts +1633 -0
- package/src/server/lifecycle.ts +482 -0
- package/src/server/live.ts +609 -0
- package/src/server/management/agent-settings-routes.ts +1180 -0
- package/src/server/management/android-remote-routes.ts +390 -0
- package/src/server/management/api-access.ts +141 -0
- package/src/server/management/api-key-usage.ts +167 -0
- package/src/server/management/body.ts +35 -0
- package/src/server/management/combo-routes.ts +244 -0
- package/src/server/management/config-routes.ts +602 -0
- package/src/server/management/context.ts +88 -0
- package/src/server/management/integration-routes.ts +538 -0
- package/src/server/management/logs-usage-routes.ts +516 -0
- package/src/server/management/model-routes.ts +519 -0
- package/src/server/management/model-rows.ts +143 -0
- package/src/server/management/native-integration-routes.ts +781 -0
- package/src/server/management/oauth-account-routes.ts +573 -0
- package/src/server/management/provider-routes.ts +781 -0
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +384 -0
- package/src/server/management/shared.ts +277 -0
- package/src/server/management/sidebar-routes.ts +106 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +433 -0
- package/src/server/management/system-routes.ts +141 -0
- package/src/server/management/usage-summary-cache.ts +86 -0
- package/src/server/management-api.ts +269 -0
- package/src/server/management-auth.ts +353 -0
- package/src/server/memory-watchdog.ts +156 -0
- package/src/server/port-reclaim.ts +307 -0
- package/src/server/ports.ts +156 -0
- package/src/server/proxy-liveness.ts +326 -0
- package/src/server/proxy-stop.ts +92 -0
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +353 -0
- package/src/server/relay.ts +1179 -0
- package/src/server/request-decompress.ts +132 -0
- package/src/server/request-log-conversation.ts +168 -0
- package/src/server/request-log.ts +1072 -0
- package/src/server/responses/collaboration.ts +409 -0
- package/src/server/responses/compact.ts +710 -0
- package/src/server/responses/core.ts +3561 -0
- package/src/server/responses/encrypted-payload.ts +308 -0
- package/src/server/responses/fetch-helpers.ts +171 -0
- package/src/server/responses/passthrough-error.ts +78 -0
- package/src/server/responses/policy-fallback.ts +152 -0
- package/src/server/responses/terminal-guard.ts +230 -0
- package/src/server/responses/upstream-error.ts +48 -0
- package/src/server/responses-image-gen-repair.ts +132 -0
- package/src/server/responses-item-id-repair.ts +272 -0
- package/src/server/responses-json-events.ts +52 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses.ts +10 -0
- package/src/server/search.ts +181 -0
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +263 -0
- package/src/server/startup-action-control.ts +308 -0
- package/src/server/startup-health-cache.ts +119 -0
- package/src/server/system-env.ts +418 -0
- package/src/server/windows-tcp-drop.ts +184 -0
- package/src/server/windows-tray-control.ts +41 -0
- package/src/server/ws-bridge.ts +470 -0
- package/src/service-manager-probe.ts +824 -0
- package/src/service.ts +3011 -0
- package/src/stall-timeout.ts +20 -0
- package/src/storage/cleanup-job.ts +57 -0
- package/src/storage/cleanup.ts +3085 -0
- package/src/storage/policy-job.ts +457 -0
- package/src/storage/policy-scheduler.ts +40 -0
- package/src/storage/policy-worker.ts +59 -0
- package/src/storage/policy.ts +527 -0
- package/src/storage/restore-job.ts +299 -0
- package/src/storage/restore-worker.ts +58 -0
- package/src/storage/scanner.ts +238 -0
- package/src/storage/storage-mutation-coordinator.ts +139 -0
- package/src/storage/worker-lifecycle.ts +215 -0
- package/src/tray/assets/opencodex-tray-offline.ico +0 -0
- package/src/tray/assets/opencodex-tray-online.ico +0 -0
- package/src/tray/assets/opencodex-tray-warning.ico +0 -0
- package/src/tray/assets/opencodex-tray.png +0 -0
- package/src/tray/windows-tray.ps1 +364 -0
- package/src/tray/windows.ts +738 -0
- package/src/types.ts +1531 -0
- package/src/update/badge.ts +72 -0
- package/src/update/desktop-release.ts +1620 -0
- package/src/update/index.ts +402 -0
- package/src/update/job.ts +1906 -0
- package/src/update/notify.ts +261 -0
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/npm-invocation.d.mts +23 -0
- package/src/update/npm-invocation.mjs +94 -0
- package/src/update/tray-update-plan.d.mts +18 -0
- package/src/update/tray-update-plan.mjs +38 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/debug.ts +97 -0
- package/src/usage/expected-prices.ts +283 -0
- package/src/usage/log.ts +695 -0
- package/src/usage/summary.ts +585 -0
- package/src/usage/totals.ts +14 -0
- package/src/vision/anthropic-describe.ts +185 -0
- package/src/vision/describe.ts +127 -0
- package/src/vision/index.ts +558 -0
- package/src/vision/reasoning.ts +55 -0
- package/src/web-search/anthropic-executor.ts +189 -0
- package/src/web-search/executor.ts +105 -0
- package/src/web-search/format-result.ts +89 -0
- package/src/web-search/index.ts +196 -0
- package/src/web-search/loop.ts +791 -0
- package/src/web-search/parse.ts +235 -0
- package/src/web-search/progress-stream.ts +342 -0
- package/src/web-search/synthetic-tool.ts +47 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { OcxConfig } from "../../types";
|
|
2
|
+
import type { NativeProfileApiDeps } from "../../codex/native-profile-api";
|
|
3
|
+
import type { StartupInstallAction } from "../startup-action-control";
|
|
4
|
+
import type { ManagementPrincipal } from "../management-auth";
|
|
5
|
+
import type { CatalogModel } from "../../codex/catalog";
|
|
6
|
+
import type { injectGrokConfig } from "../../grok/inject";
|
|
7
|
+
import type { removeDesktop3pStandardPivot, writeDesktop3pConfig } from "../../claude/desktop-3p";
|
|
8
|
+
import type { RuntimePortState } from "../../config";
|
|
9
|
+
import type { CatalogDisposition, ConvergeCodex } from "../../codex/convergence-types";
|
|
10
|
+
import type { AndroidRemoteStore } from "../../android-remote/store";
|
|
11
|
+
import type { AndroidRemoteGatewayController } from "../../android-remote/gateway";
|
|
12
|
+
import type { AndroidRemoteCloudflareProvisioner } from "../../android-remote/cloudflare-provisioning";
|
|
13
|
+
|
|
14
|
+
export interface ManagementApiDeps {
|
|
15
|
+
/** Native desktop-update seams keep route tests off the network and real owner state. */
|
|
16
|
+
checkDesktopReleaseUpdate?: typeof import("../../update/desktop-release").checkDesktopReleaseUpdate;
|
|
17
|
+
startDesktopUpdateJob?: typeof import("../../update/desktop-release").startDesktopUpdateJob;
|
|
18
|
+
readDesktopUpdateState?: typeof import("../../update/desktop-release").readDesktopUpdateState;
|
|
19
|
+
/** Android Remote persistence seam. Tests inject an in-memory store. */
|
|
20
|
+
androidRemoteStore?: AndroidRemoteStore;
|
|
21
|
+
/** Shared Android Remote gateway. Production and management routes use one controller. */
|
|
22
|
+
androidRemoteController?: AndroidRemoteGatewayController;
|
|
23
|
+
/** Cloudflare API seam. Tests inject a bounded fake and never contact a real account. */
|
|
24
|
+
androidRemoteCloudflareProvisioner?: AndroidRemoteCloudflareProvisioner;
|
|
25
|
+
toggleCodexMultiAgentV2?: (enabled: boolean) => void;
|
|
26
|
+
toggleDefaultModeRequestUserInput?: (enabled: boolean) => void;
|
|
27
|
+
createManagementConvergeCodex?: (config: Readonly<OcxConfig>) => ConvergeCodex;
|
|
28
|
+
/**
|
|
29
|
+
* Persistence seam for route-level tests. Production leaves this unset and uses
|
|
30
|
+
* `saveConfigPreservingClaudeCode`; tests that pass an in-memory fixture config
|
|
31
|
+
* MUST inject a no-op/spy so the fixture can never overwrite the user's real
|
|
32
|
+
* OPENCODEX_HOME (incident: devlog 260730.../070).
|
|
33
|
+
*/
|
|
34
|
+
saveConfigPreservingClaudeCode?: (config: OcxConfig) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Catalog seam for the Grok toggle (WP2, devlog 260803_integrations_toggle_all
|
|
37
|
+
* Rev 3 N2). Production leaves this unset and the route dynamic-imports the
|
|
38
|
+
* real one — a static import would close a cycle with management-api.ts.
|
|
39
|
+
* Tests stub it to orphan the fixture file mid-fetch (the r7 recheck test).
|
|
40
|
+
*/
|
|
41
|
+
fetchAllModels?: (config: OcxConfig) => Promise<CatalogModel[]>;
|
|
42
|
+
/**
|
|
43
|
+
* Writer seam for the Grok toggle: lets a test place the file in any state
|
|
44
|
+
* between the pre-write recheck and the write itself (the r8 post-inspection
|
|
45
|
+
* tests). Production leaves this unset and uses the real writer.
|
|
46
|
+
*/
|
|
47
|
+
injectGrokConfig?: typeof injectGrokConfig;
|
|
48
|
+
/** Desktop mutation seams keep route tests inside temporary config libraries. */
|
|
49
|
+
removeDesktop3pStandardPivot?: typeof removeDesktop3pStandardPivot;
|
|
50
|
+
writeDesktop3pConfig?: typeof writeDesktop3pConfig;
|
|
51
|
+
/**
|
|
52
|
+
* Runtime-state seam: the fence must name the host/port the RUNNING process
|
|
53
|
+
* bound (agent-settings-routes.ts:99-103 pattern), and a test must not depend
|
|
54
|
+
* on the developer's real runtime state file.
|
|
55
|
+
*/
|
|
56
|
+
readRuntimePort?: (pid: number) => RuntimePortState | null;
|
|
57
|
+
clearThreadAccountMap?: () => void;
|
|
58
|
+
clearProviderQuotaCache?: () => void;
|
|
59
|
+
primeCodexPoolQuotas?: (config: OcxConfig, reason: string) => Promise<void> | void;
|
|
60
|
+
runStartupInstallAction?: (
|
|
61
|
+
action: StartupInstallAction,
|
|
62
|
+
options?: { repair?: boolean },
|
|
63
|
+
) => Promise<{ message: string }>;
|
|
64
|
+
/**
|
|
65
|
+
* Native-main profile persistence seam for server-boundary tests. Production
|
|
66
|
+
* leaves this unset, so the route creates its normal NativeProfileManager.
|
|
67
|
+
*/
|
|
68
|
+
nativeProfileApi?: NativeProfileApiDeps;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
export interface ManagementContext {
|
|
73
|
+
req: Request;
|
|
74
|
+
url: URL;
|
|
75
|
+
config: OcxConfig;
|
|
76
|
+
deps: ManagementApiDeps;
|
|
77
|
+
/**
|
|
78
|
+
* Which credential authorized this request, resolved by the auth gate before
|
|
79
|
+
* dispatch. Routes that spend the USER's identity (not just the proxy's) must
|
|
80
|
+
* branch on this instead of on request headers: the admin token is readable by
|
|
81
|
+
* anything running as the user, so a token holder can forge any header a route
|
|
82
|
+
* might otherwise treat as browser evidence. Undefined only in direct-dispatch
|
|
83
|
+
* tests, which are treated as the untrusted `admin-token` case.
|
|
84
|
+
*/
|
|
85
|
+
principal?: ManagementPrincipal;
|
|
86
|
+
convergeCodexCatalog: () => Promise<CatalogDisposition>;
|
|
87
|
+
syncClaudeAgentDefsBestEffort: () => Promise<void>;
|
|
88
|
+
}
|
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Management routes for the client-integration toggle.
|
|
3
|
+
*
|
|
4
|
+
* This module is an HTTP adapter and nothing more: every policy decision —
|
|
5
|
+
* what counts as ownership, when a mutation is refused, what gets journaled —
|
|
6
|
+
* belongs to src/integrations/writer.ts. Duplicating any of it here is how the
|
|
7
|
+
* API and the writer would start disagreeing about what happened to a file.
|
|
8
|
+
*
|
|
9
|
+
* Design of record: devlog/_fin/260802_client_toggle_api/040_wp4_management_api.md.
|
|
10
|
+
*/
|
|
11
|
+
import { readFileSync } from "node:fs";
|
|
12
|
+
import type { IntegrationIO } from "../../integrations/config-io";
|
|
13
|
+
import { matchesOperationResult } from "../../integrations/journal";
|
|
14
|
+
import {
|
|
15
|
+
INTEGRATION_CLIENT_IDS,
|
|
16
|
+
isIntegrationClientId,
|
|
17
|
+
type IntegrationClientId,
|
|
18
|
+
} from "../../integrations/registry";
|
|
19
|
+
import { readIntegrationState } from "../../integrations/state";
|
|
20
|
+
import { createIntegrationStateStore, type IntegrationStateStore } from "../../integrations/store";
|
|
21
|
+
import {
|
|
22
|
+
applyIntegration,
|
|
23
|
+
disableIntegration,
|
|
24
|
+
restoreIntegration,
|
|
25
|
+
type IntegrationRestoreInput,
|
|
26
|
+
type IntegrationWriteInput,
|
|
27
|
+
type WriteRefused,
|
|
28
|
+
} from "../../integrations/writer";
|
|
29
|
+
import { jsonResponse } from "../auth-cors";
|
|
30
|
+
import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
|
|
31
|
+
import type { ManagementContext } from "./context";
|
|
32
|
+
import { loadExportModels } from "./model-rows";
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
const INTEGRATION_ROUTE_PREFIX = "/api/client-integrations/";
|
|
36
|
+
const INTEGRATION_MUTATION_JOIN_MS = 120_000;
|
|
37
|
+
export const INTEGRATION_MUTATION_TERMINAL_MS = 10 * 60_000;
|
|
38
|
+
|
|
39
|
+
type IntegrationStateRecord = Awaited<ReturnType<typeof readIntegrationState>>;
|
|
40
|
+
type ApplyResult = Awaited<ReturnType<typeof applyIntegration>>;
|
|
41
|
+
type DisableResult = Awaited<ReturnType<typeof disableIntegration>>;
|
|
42
|
+
type RestoreResult = Awaited<ReturnType<typeof restoreIntegration>>;
|
|
43
|
+
|
|
44
|
+
export type IntegrationStateEnvelope = {
|
|
45
|
+
clientId: IntegrationClientId;
|
|
46
|
+
} & IntegrationStateRecord;
|
|
47
|
+
|
|
48
|
+
export interface IntegrationStateListEnvelope {
|
|
49
|
+
clients: IntegrationStateEnvelope[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type IntegrationToggleEnvelope =
|
|
53
|
+
| ({ clientId: IntegrationClientId } & ApplyResult)
|
|
54
|
+
| ({ clientId: IntegrationClientId } & DisableResult);
|
|
55
|
+
|
|
56
|
+
export type IntegrationRestoreEnvelope = {
|
|
57
|
+
clientId: IntegrationClientId;
|
|
58
|
+
} & RestoreResult;
|
|
59
|
+
|
|
60
|
+
export interface IntegrationJournalEnvelope {
|
|
61
|
+
operations: IntegrationJournalRow[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface IntegrationJournalRow {
|
|
65
|
+
opId: string;
|
|
66
|
+
clientId: IntegrationClientId;
|
|
67
|
+
kind: "apply" | "disable" | "refresh" | "restore";
|
|
68
|
+
at: string;
|
|
69
|
+
configPath: string;
|
|
70
|
+
snapshot: "none" | "stored" | "expired";
|
|
71
|
+
undoable: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface IntegrationToggleBody {
|
|
75
|
+
enabled: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface IntegrationRestoreBody {
|
|
79
|
+
opId: string;
|
|
80
|
+
confirmDrift?: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface IntegrationMutationFlight {
|
|
84
|
+
key: string;
|
|
85
|
+
startedAt: number;
|
|
86
|
+
promise: Promise<unknown>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
class IntegrationMutationBusyError extends Error {
|
|
90
|
+
constructor(readonly clientId: IntegrationClientId) {
|
|
91
|
+
super("integration_mutation_busy");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const integrationMutationFlights = new Map<IntegrationClientId, IntegrationMutationFlight>();
|
|
96
|
+
let integrationMutationTestHooks: {
|
|
97
|
+
io?: IntegrationIO;
|
|
98
|
+
/**
|
|
99
|
+
* Bind every read and write in the request to one store. Without this a
|
|
100
|
+
* route test could isolate the writer but not the journal listing or the
|
|
101
|
+
* restore preflight (A-gate round 12).
|
|
102
|
+
*/
|
|
103
|
+
store?: IntegrationStateStore;
|
|
104
|
+
run?: (operation: () => Promise<unknown>) => Promise<unknown>;
|
|
105
|
+
} | null = null;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Home and environment overrides for tests.
|
|
109
|
+
*
|
|
110
|
+
* Bun's `os.homedir()` snapshots the real home at startup and ignores a later
|
|
111
|
+
* `process.env.HOME` assignment, so a test that only rewrites `HOME` still
|
|
112
|
+
* resolves the DEVELOPER'S client configs — which is exactly how a route test
|
|
113
|
+
* wrote a real `~/.hermes/config.yaml` during this work package. The writer
|
|
114
|
+
* and the state reader both already take `env`/`home` explicitly; the route
|
|
115
|
+
* simply had no way to pass them. It does now, and production leaves it unset.
|
|
116
|
+
*/
|
|
117
|
+
let integrationPathTestHooks: { env?: NodeJS.ProcessEnv; home?: string } | null = null;
|
|
118
|
+
|
|
119
|
+
export function setIntegrationPathTestHooks(hooks: { env?: NodeJS.ProcessEnv; home?: string } | null): void {
|
|
120
|
+
integrationPathTestHooks = hooks;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** The `env`/`home` overrides, spread into every registry-resolving call. */
|
|
124
|
+
function pathOverrides(): { env?: NodeJS.ProcessEnv; home?: string } {
|
|
125
|
+
return {
|
|
126
|
+
...(integrationPathTestHooks?.env ? { env: integrationPathTestHooks.env } : {}),
|
|
127
|
+
...(integrationPathTestHooks?.home ? { home: integrationPathTestHooks.home } : {}),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
132
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function decodeClientPath(pathname: string): string | null {
|
|
136
|
+
if (!pathname.startsWith(INTEGRATION_ROUTE_PREFIX)) return null;
|
|
137
|
+
const encoded = pathname.slice(INTEGRATION_ROUTE_PREFIX.length);
|
|
138
|
+
if (!encoded || encoded.includes("/")) return null;
|
|
139
|
+
try {
|
|
140
|
+
return decodeURIComponent(encoded);
|
|
141
|
+
} catch {
|
|
142
|
+
return encoded;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function runIntegrationMutationFlight<T>(
|
|
147
|
+
clientId: IntegrationClientId,
|
|
148
|
+
key: string,
|
|
149
|
+
now: () => number,
|
|
150
|
+
operation: () => Promise<T>,
|
|
151
|
+
): Promise<T> {
|
|
152
|
+
const startedAt = now();
|
|
153
|
+
const current = integrationMutationFlights.get(clientId);
|
|
154
|
+
if (current) {
|
|
155
|
+
const age = startedAt - current.startedAt;
|
|
156
|
+
if (current.key === key && age < INTEGRATION_MUTATION_JOIN_MS) {
|
|
157
|
+
return current.promise as Promise<T>;
|
|
158
|
+
}
|
|
159
|
+
if (age <= INTEGRATION_MUTATION_TERMINAL_MS) {
|
|
160
|
+
return Promise.reject(new IntegrationMutationBusyError(clientId));
|
|
161
|
+
}
|
|
162
|
+
if (integrationMutationFlights.get(clientId) === current) {
|
|
163
|
+
integrationMutationFlights.delete(clientId);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const flight: IntegrationMutationFlight = {
|
|
168
|
+
key,
|
|
169
|
+
startedAt,
|
|
170
|
+
promise: Promise.resolve(),
|
|
171
|
+
};
|
|
172
|
+
const run = async (): Promise<unknown> => operation();
|
|
173
|
+
flight.promise = (integrationMutationTestHooks?.run
|
|
174
|
+
? integrationMutationTestHooks.run(run)
|
|
175
|
+
: run()
|
|
176
|
+
).finally(() => {
|
|
177
|
+
if (integrationMutationFlights.get(clientId) === flight) {
|
|
178
|
+
integrationMutationFlights.delete(clientId);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
integrationMutationFlights.set(clientId, flight);
|
|
182
|
+
return flight.promise as Promise<T>;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function setIntegrationMutationFlightTestHooks(
|
|
186
|
+
hooks: {
|
|
187
|
+
io?: IntegrationIO;
|
|
188
|
+
/** Binds the WHOLE request — reads, writes and journal — to one store. */
|
|
189
|
+
store?: IntegrationStateStore;
|
|
190
|
+
run?: (operation: () => Promise<unknown>) => Promise<unknown>;
|
|
191
|
+
} | null,
|
|
192
|
+
): void {
|
|
193
|
+
integrationMutationTestHooks = hooks;
|
|
194
|
+
integrationMutationFlights.clear();
|
|
195
|
+
// Path overrides are part of the same isolation contract: clearing flights
|
|
196
|
+
// while leaving a temp home bound would let the next suite write real files.
|
|
197
|
+
if (hooks === null) integrationPathTestHooks = null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* ONE store per request, used by every read and every write in that request.
|
|
202
|
+
* The route previously called module-level `listOperations`/`readSnapshot`
|
|
203
|
+
* while handing the writer a separate default store, so a test could not bind
|
|
204
|
+
* the whole operation to a temp root (A-gate round 12).
|
|
205
|
+
*/
|
|
206
|
+
function integrationStore(): IntegrationStateStore {
|
|
207
|
+
return integrationMutationTestHooks?.store ?? createIntegrationStateStore();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function buildIntegrationWriteInput(
|
|
211
|
+
clientId: IntegrationClientId,
|
|
212
|
+
ctx: ManagementContext,
|
|
213
|
+
store: IntegrationStateStore,
|
|
214
|
+
): Promise<IntegrationWriteInput> {
|
|
215
|
+
return {
|
|
216
|
+
clientId,
|
|
217
|
+
models: await loadExportModels(ctx.config),
|
|
218
|
+
config: ctx.config,
|
|
219
|
+
port: Number(ctx.url.port) || ctx.config.port,
|
|
220
|
+
store,
|
|
221
|
+
io: integrationMutationTestHooks?.io,
|
|
222
|
+
...pathOverrides(),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* The file's current bytes, or `null` when it is missing.
|
|
228
|
+
*
|
|
229
|
+
* `null` is NOT the same as `""`: an absent file and an empty one are
|
|
230
|
+
* different states, and collapsing them is what made this route disagree with
|
|
231
|
+
* the writer about whether an absence-result operation had drifted.
|
|
232
|
+
* `undefined` means we could not read it at all, which is neither.
|
|
233
|
+
*/
|
|
234
|
+
function currentConfigText(configPath: string): string | null | undefined {
|
|
235
|
+
try {
|
|
236
|
+
return readFileSync(configPath, "utf8");
|
|
237
|
+
} catch (error) {
|
|
238
|
+
if (isPlainRecord(error) && error.code === "ENOENT") return null;
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function invalidClientResponse(ctx: ManagementContext): Response {
|
|
244
|
+
return jsonResponse({
|
|
245
|
+
error: "invalid integration client",
|
|
246
|
+
code: "invalid_integration_client",
|
|
247
|
+
validClients: INTEGRATION_CLIENT_IDS,
|
|
248
|
+
}, 400, ctx.req, ctx.config);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function internalErrorResponse(error: unknown, ctx: ManagementContext): Response {
|
|
252
|
+
return jsonResponse({
|
|
253
|
+
error: error instanceof Error ? error.message : String(error),
|
|
254
|
+
code: "integration_internal_error",
|
|
255
|
+
}, 500, ctx.req, ctx.config);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function readJsonBody(ctx: ManagementContext): Promise<unknown | Response> {
|
|
259
|
+
try {
|
|
260
|
+
return await readManagementJsonBody(ctx.req);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
rethrowManagementBodyTooLarge(error);
|
|
263
|
+
return jsonResponse({
|
|
264
|
+
error: "invalid JSON body",
|
|
265
|
+
code: "invalid_json_body",
|
|
266
|
+
}, 400, ctx.req, ctx.config);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function writerFailureResponse(
|
|
271
|
+
clientId: IntegrationClientId,
|
|
272
|
+
/*
|
|
273
|
+
* The writer's own refusal type, not a structural echo of it.
|
|
274
|
+
*
|
|
275
|
+
* A local shape with an optional `message` accepted a refusal that had lost
|
|
276
|
+
* its message on the way here, which is exactly how the drift branch shipped
|
|
277
|
+
* without one. `WriteRefused` requires it, so the compiler now objects.
|
|
278
|
+
*/
|
|
279
|
+
result: WriteRefused,
|
|
280
|
+
ctx: ManagementContext,
|
|
281
|
+
): Response {
|
|
282
|
+
/*
|
|
283
|
+
* Routed by `reason`, never by `state` (006 §5). A `write_failed` that
|
|
284
|
+
* happens to occur while the file is in a `conflict` state is still a write
|
|
285
|
+
* failure, and mapping on state first silently dropped its message,
|
|
286
|
+
* snapshotPath, and residual — the recovery information the flag exists to
|
|
287
|
+
* carry (A-gate round 5, blocker 3).
|
|
288
|
+
*/
|
|
289
|
+
const recovery = {
|
|
290
|
+
message: result.message,
|
|
291
|
+
...(result.snapshotPath ? { snapshotPath: result.snapshotPath } : {}),
|
|
292
|
+
...(result.residual ? { residual: true } : {}),
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
if (result.reason === "unsafe") {
|
|
296
|
+
return jsonResponse({
|
|
297
|
+
error: "integration config is unsafe",
|
|
298
|
+
code: "integration_unsafe",
|
|
299
|
+
clientId, state: result.state, reason: result.reason, ...recovery,
|
|
300
|
+
}, 409, ctx.req, ctx.config);
|
|
301
|
+
}
|
|
302
|
+
if (result.reason === "conflict") {
|
|
303
|
+
return jsonResponse({
|
|
304
|
+
error: "integration config conflicts with ownership record",
|
|
305
|
+
code: "integration_conflict",
|
|
306
|
+
clientId, state: result.state, reason: result.reason, ...recovery,
|
|
307
|
+
}, 409, ctx.req, ctx.config);
|
|
308
|
+
}
|
|
309
|
+
if (result.reason === "drift_requires_confirm") {
|
|
310
|
+
return jsonResponse({
|
|
311
|
+
error: "restore requires drift confirmation",
|
|
312
|
+
code: "integration_drift_confirmation_required",
|
|
313
|
+
clientId, state: result.state, reason: result.reason, ...recovery,
|
|
314
|
+
}, 409, ctx.req, ctx.config);
|
|
315
|
+
}
|
|
316
|
+
if (result.reason === "snapshot_expired") {
|
|
317
|
+
return jsonResponse({
|
|
318
|
+
error: "integration snapshot expired",
|
|
319
|
+
code: "integration_snapshot_expired",
|
|
320
|
+
clientId, state: result.state, reason: result.reason, ...recovery,
|
|
321
|
+
}, 410, ctx.req, ctx.config);
|
|
322
|
+
}
|
|
323
|
+
// not_installed, non_loopback, write_failed — always carry recovery fields.
|
|
324
|
+
return jsonResponse({
|
|
325
|
+
error: "integration mutation failed",
|
|
326
|
+
code: "integration_mutation_failed",
|
|
327
|
+
clientId, state: result.state, reason: result.reason, ...recovery,
|
|
328
|
+
}, 500, ctx.req, ctx.config);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export async function handleIntegrationRoutes(ctx: ManagementContext): Promise<Response | null> {
|
|
332
|
+
const { req, url } = ctx;
|
|
333
|
+
|
|
334
|
+
if (url.pathname === "/api/client-integrations" && req.method === "GET") {
|
|
335
|
+
try {
|
|
336
|
+
const models = await loadExportModels(ctx.config);
|
|
337
|
+
const port = Number(url.port) || ctx.config.port;
|
|
338
|
+
// One store for the whole collection read: without it this route retried
|
|
339
|
+
// maintenance and counted snapshots in the default store even when the
|
|
340
|
+
// caller had bound everything else to a temp root (A-gate round 13).
|
|
341
|
+
const store = integrationStore();
|
|
342
|
+
/*
|
|
343
|
+
* The envelope carries `clientId`, but it is not restated here: WP2 and
|
|
344
|
+
* WP3 both echo `input.clientId` back in their result, and `input`
|
|
345
|
+
* IS this route's client. Writing it twice made the compiler pick the
|
|
346
|
+
* later one silently — a duplicate that could only ever hide a
|
|
347
|
+
* disagreement, never surface it.
|
|
348
|
+
*/
|
|
349
|
+
const clients = INTEGRATION_CLIENT_IDS.map(clientId =>
|
|
350
|
+
readIntegrationState({ clientId, models, config: ctx.config, port, store, ...pathOverrides() }));
|
|
351
|
+
return jsonResponse({ clients } satisfies IntegrationStateListEnvelope, 200, req, ctx.config);
|
|
352
|
+
} catch (error) {
|
|
353
|
+
return internalErrorResponse(error, ctx);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (url.pathname === "/api/client-integrations/journal") {
|
|
358
|
+
if (req.method !== "GET") return null;
|
|
359
|
+
const requestedClient = url.searchParams.get("client");
|
|
360
|
+
if (requestedClient !== null && !isIntegrationClientId(requestedClient)) {
|
|
361
|
+
return invalidClientResponse(ctx);
|
|
362
|
+
}
|
|
363
|
+
try {
|
|
364
|
+
const store = integrationStore();
|
|
365
|
+
const storedOperations = store.listOperations(requestedClient ?? undefined);
|
|
366
|
+
const newestByClient = new Map<IntegrationClientId, string>();
|
|
367
|
+
for (const operation of storedOperations) {
|
|
368
|
+
if (!newestByClient.has(operation.clientId)) {
|
|
369
|
+
newestByClient.set(operation.clientId, operation.opId);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const operations: IntegrationJournalRow[] = storedOperations.map(operation => {
|
|
373
|
+
/*
|
|
374
|
+
* Resolved against the DISK, not read off the row.
|
|
375
|
+
*
|
|
376
|
+
* Retention deletes snapshot files and deliberately leaves the row's
|
|
377
|
+
* persisted tag saying `stored`, so copying that tag advertised undo
|
|
378
|
+
* for bytes that no longer exist — the GUI would offer the button and
|
|
379
|
+
* the restore route would answer 410. `readSnapshot` is the same
|
|
380
|
+
* resolver that preflight uses, which is what keeps the two agreeing.
|
|
381
|
+
*/
|
|
382
|
+
const snapshot = store.readSnapshot(operation).kind;
|
|
383
|
+
return {
|
|
384
|
+
opId: operation.opId,
|
|
385
|
+
clientId: operation.clientId,
|
|
386
|
+
kind: operation.kind,
|
|
387
|
+
at: operation.at,
|
|
388
|
+
configPath: operation.configPath,
|
|
389
|
+
snapshot,
|
|
390
|
+
/*
|
|
391
|
+
* The SAME resolution decides `undoable`. Reporting the tag honestly
|
|
392
|
+
* and then offering undo anyway is the identical defect one field
|
|
393
|
+
* over: restore would answer 410 for a row the GUI drew a button on.
|
|
394
|
+
* `none` stays undoable — restoring an op that created a file means
|
|
395
|
+
* deleting it, and that needs no snapshot bytes.
|
|
396
|
+
*/
|
|
397
|
+
/*
|
|
398
|
+
* Eligibility goes through the SAME matcher restore uses. This route
|
|
399
|
+
* used to represent a missing file as `""` and call that a match,
|
|
400
|
+
* while restore hashed `""` into a real digest — so the row was
|
|
401
|
+
* offered as Undo and then refused as drift.
|
|
402
|
+
*/
|
|
403
|
+
undoable: (() => {
|
|
404
|
+
if (snapshot === "expired") return false;
|
|
405
|
+
if (newestByClient.get(operation.clientId) !== operation.opId) return false;
|
|
406
|
+
const current = currentConfigText(operation.configPath);
|
|
407
|
+
return current === undefined ? false : matchesOperationResult(operation, current);
|
|
408
|
+
})(),
|
|
409
|
+
};
|
|
410
|
+
});
|
|
411
|
+
return jsonResponse({ operations } satisfies IntegrationJournalEnvelope, 200, req, ctx.config);
|
|
412
|
+
} catch (error) {
|
|
413
|
+
return internalErrorResponse(error, ctx);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (url.pathname === "/api/client-integrations/restore") {
|
|
418
|
+
if (req.method !== "POST") return null;
|
|
419
|
+
const parsed = await readJsonBody(ctx);
|
|
420
|
+
if (parsed instanceof Response) return parsed;
|
|
421
|
+
if (!isPlainRecord(parsed) || typeof parsed.opId !== "string" || parsed.opId.trim().length === 0) {
|
|
422
|
+
return jsonResponse({
|
|
423
|
+
error: "opId must be a non-empty string",
|
|
424
|
+
code: "invalid_op_id",
|
|
425
|
+
}, 400, req, ctx.config);
|
|
426
|
+
}
|
|
427
|
+
if (parsed.confirmDrift !== undefined && typeof parsed.confirmDrift !== "boolean") {
|
|
428
|
+
return jsonResponse({
|
|
429
|
+
error: "confirmDrift must be a boolean",
|
|
430
|
+
code: "invalid_confirm_drift",
|
|
431
|
+
}, 400, req, ctx.config);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const opId = parsed.opId.trim();
|
|
435
|
+
const confirmDrift = parsed.confirmDrift ?? false;
|
|
436
|
+
try {
|
|
437
|
+
const store = integrationStore();
|
|
438
|
+
const operation = store.findOperation(opId);
|
|
439
|
+
if (!operation) {
|
|
440
|
+
return jsonResponse({
|
|
441
|
+
error: "integration operation not found",
|
|
442
|
+
code: "integration_operation_not_found",
|
|
443
|
+
opId,
|
|
444
|
+
}, 404, req, ctx.config);
|
|
445
|
+
}
|
|
446
|
+
const snapshot = store.readSnapshot(operation);
|
|
447
|
+
if (snapshot.kind === "expired") {
|
|
448
|
+
return jsonResponse({
|
|
449
|
+
error: "integration snapshot expired",
|
|
450
|
+
code: "integration_snapshot_expired",
|
|
451
|
+
opId,
|
|
452
|
+
}, 410, req, ctx.config);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const writeInput = await buildIntegrationWriteInput(operation.clientId, ctx, store);
|
|
456
|
+
const restoreInput: IntegrationRestoreInput = {
|
|
457
|
+
...writeInput,
|
|
458
|
+
opId,
|
|
459
|
+
confirmDrift,
|
|
460
|
+
};
|
|
461
|
+
const result = await runIntegrationMutationFlight(
|
|
462
|
+
operation.clientId,
|
|
463
|
+
`restore:${opId}:${confirmDrift}`,
|
|
464
|
+
writeInput.io?.now ?? Date.now,
|
|
465
|
+
() => Promise.resolve(restoreIntegration(restoreInput)),
|
|
466
|
+
);
|
|
467
|
+
if (!result.ok) {
|
|
468
|
+
/*
|
|
469
|
+
* Drift is NOT special-cased here.
|
|
470
|
+
*
|
|
471
|
+
* It used to be, and the hand-written branch dropped the writer's
|
|
472
|
+
* `message` — which is the only thing that tells the user WHICH file
|
|
473
|
+
* drifted and where its backup went. Every refusal leaves through the
|
|
474
|
+
* one serializer, so a refusal cannot lose its recovery fields by
|
|
475
|
+
* being routed through a shorter path.
|
|
476
|
+
*/
|
|
477
|
+
return writerFailureResponse(operation.clientId, result, ctx);
|
|
478
|
+
}
|
|
479
|
+
return jsonResponse(result satisfies IntegrationRestoreEnvelope, 200, req, ctx.config);
|
|
480
|
+
} catch (error) {
|
|
481
|
+
if (error instanceof IntegrationMutationBusyError) {
|
|
482
|
+
return jsonResponse({
|
|
483
|
+
error: "integration mutation busy",
|
|
484
|
+
code: "integration_mutation_busy",
|
|
485
|
+
clientId: error.clientId,
|
|
486
|
+
}, 409, req, ctx.config);
|
|
487
|
+
}
|
|
488
|
+
return internalErrorResponse(error, ctx);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (req.method !== "GET" && req.method !== "PUT") return null;
|
|
493
|
+
const requestedClient = decodeClientPath(url.pathname);
|
|
494
|
+
if (requestedClient === null) return null;
|
|
495
|
+
if (!isIntegrationClientId(requestedClient)) return invalidClientResponse(ctx);
|
|
496
|
+
|
|
497
|
+
if (req.method === "GET") {
|
|
498
|
+
try {
|
|
499
|
+
const input = await buildIntegrationWriteInput(requestedClient, ctx, integrationStore());
|
|
500
|
+
const state = readIntegrationState(input);
|
|
501
|
+
return jsonResponse(state satisfies IntegrationStateEnvelope, 200, req, ctx.config);
|
|
502
|
+
} catch (error) {
|
|
503
|
+
return internalErrorResponse(error, ctx);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const parsed = await readJsonBody(ctx);
|
|
508
|
+
if (parsed instanceof Response) return parsed;
|
|
509
|
+
if (!isPlainRecord(parsed) || typeof parsed.enabled !== "boolean") {
|
|
510
|
+
return jsonResponse({
|
|
511
|
+
error: "enabled must be a boolean",
|
|
512
|
+
code: "invalid_enabled",
|
|
513
|
+
}, 400, req, ctx.config);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
try {
|
|
517
|
+
const input = await buildIntegrationWriteInput(requestedClient, ctx, integrationStore());
|
|
518
|
+
const result = await runIntegrationMutationFlight(
|
|
519
|
+
requestedClient,
|
|
520
|
+
parsed.enabled ? "apply" : "disable",
|
|
521
|
+
input.io?.now ?? Date.now,
|
|
522
|
+
() => Promise.resolve(parsed.enabled
|
|
523
|
+
? applyIntegration(input)
|
|
524
|
+
: disableIntegration(input)),
|
|
525
|
+
);
|
|
526
|
+
if (!result.ok) return writerFailureResponse(requestedClient, result, ctx);
|
|
527
|
+
return jsonResponse(result satisfies IntegrationToggleEnvelope, 200, req, ctx.config);
|
|
528
|
+
} catch (error) {
|
|
529
|
+
if (error instanceof IntegrationMutationBusyError) {
|
|
530
|
+
return jsonResponse({
|
|
531
|
+
error: "integration mutation busy",
|
|
532
|
+
code: "integration_mutation_busy",
|
|
533
|
+
clientId: error.clientId,
|
|
534
|
+
}, 409, req, ctx.config);
|
|
535
|
+
}
|
|
536
|
+
return internalErrorResponse(error, ctx);
|
|
537
|
+
}
|
|
538
|
+
}
|