@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,492 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apply, disable and restore an opencodex provider block in a client's config.
|
|
3
|
+
*
|
|
4
|
+
* Everything here exists to keep one promise: a toggle can always be undone.
|
|
5
|
+
* That means every mutation snapshots first, writes atomically, and journals
|
|
6
|
+
* what it did — and that a mutation refuses rather than guesses whenever the
|
|
7
|
+
* file is not in a state we can reason about. A disable that deletes work the
|
|
8
|
+
* user did after us would be worse than never shipping the feature.
|
|
9
|
+
*
|
|
10
|
+
* Design of record: devlog/_fin/260802_client_toggle_api/030 and 031.
|
|
11
|
+
*/
|
|
12
|
+
import { dirname } from "node:path";
|
|
13
|
+
import { EXPORT_CLIENTS, type ExportModel } from "../clients/config-export";
|
|
14
|
+
import { isLoopbackHostname } from "../codex/inject";
|
|
15
|
+
import type { OcxConfig } from "../types";
|
|
16
|
+
import { PARSE_FAILED, defaultIntegrationIO, loadTarget, parseConfig, type IntegrationIO } from "./config-io";
|
|
17
|
+
import { fingerprint, canonicalContribution, fragmentPathsOf, type OwnershipRecord } from "./ownership";
|
|
18
|
+
import { createdContainerPaths, mergeContribution, removeFragments } from "./merge";
|
|
19
|
+
import { INTEGRATION_CLIENTS, isLoopbackOnly, type IntegrationClientId } from "./registry";
|
|
20
|
+
import { classifyIntegration, exportContextOf } from "./state";
|
|
21
|
+
import type { IntegrationState } from "./state";
|
|
22
|
+
import { serializeDocument, UnserializableValueError } from "./serialize";
|
|
23
|
+
import { ClientPathError } from "../clients/config-export";
|
|
24
|
+
import { matchesOperationResult, newOpId, type JournalEntry } from "./journal";
|
|
25
|
+
import { createIntegrationStateStore, type IntegrationStateStore } from "./store";
|
|
26
|
+
|
|
27
|
+
export type RefusalReason =
|
|
28
|
+
| "not_installed"
|
|
29
|
+
| "conflict"
|
|
30
|
+
| "unsafe"
|
|
31
|
+
| "non_loopback"
|
|
32
|
+
| "drift_requires_confirm"
|
|
33
|
+
| "snapshot_expired"
|
|
34
|
+
| "write_failed";
|
|
35
|
+
|
|
36
|
+
export interface WriteOk {
|
|
37
|
+
ok: true;
|
|
38
|
+
changed: boolean;
|
|
39
|
+
state: IntegrationState;
|
|
40
|
+
clientId: IntegrationClientId;
|
|
41
|
+
opId?: string;
|
|
42
|
+
message: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface WriteRefused {
|
|
46
|
+
ok: false;
|
|
47
|
+
reason: RefusalReason;
|
|
48
|
+
state: IntegrationState;
|
|
49
|
+
clientId: IntegrationClientId;
|
|
50
|
+
message: string;
|
|
51
|
+
/** Absolute path of a recoverable snapshot, when one exists. */
|
|
52
|
+
snapshotPath?: string;
|
|
53
|
+
/** True when compensation itself failed and the file is intermediate. */
|
|
54
|
+
residual?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type WriteOutcome = WriteOk | WriteRefused;
|
|
58
|
+
|
|
59
|
+
export interface IntegrationWriteInput {
|
|
60
|
+
clientId: IntegrationClientId;
|
|
61
|
+
models: readonly ExportModel[];
|
|
62
|
+
config: OcxConfig;
|
|
63
|
+
port: number;
|
|
64
|
+
env?: NodeJS.ProcessEnv;
|
|
65
|
+
home?: string;
|
|
66
|
+
store?: IntegrationStateStore;
|
|
67
|
+
io?: IntegrationIO;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface IntegrationRestoreInput extends IntegrationWriteInput {
|
|
71
|
+
opId: string;
|
|
72
|
+
confirmDrift?: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function refuse(
|
|
76
|
+
clientId: IntegrationClientId,
|
|
77
|
+
reason: RefusalReason,
|
|
78
|
+
state: IntegrationState,
|
|
79
|
+
message: string,
|
|
80
|
+
snapshotPath?: string,
|
|
81
|
+
): WriteRefused {
|
|
82
|
+
return { ok: false, reason, state, clientId, message, ...(snapshotPath ? { snapshotPath } : {}) };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function messageOf(error: unknown): string {
|
|
86
|
+
return error instanceof Error ? error.message : String(error);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface CommitArgs {
|
|
90
|
+
io: IntegrationIO;
|
|
91
|
+
store: IntegrationStateStore;
|
|
92
|
+
clientId: IntegrationClientId;
|
|
93
|
+
configPath: string;
|
|
94
|
+
before: string | null;
|
|
95
|
+
nextText: string | null;
|
|
96
|
+
record: OwnershipRecord | null;
|
|
97
|
+
priorRecord: OwnershipRecord | null;
|
|
98
|
+
entry: JournalEntry;
|
|
99
|
+
state: IntegrationState;
|
|
100
|
+
snapshotPath?: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Client file, then ownership record, then journal row.
|
|
105
|
+
*
|
|
106
|
+
* The row is last on purpose: a record without a row is a thin history, while
|
|
107
|
+
* a row without a record would advertise an operation the classifier cannot
|
|
108
|
+
* corroborate. If either bookkeeping step fails we put the file back AND
|
|
109
|
+
* restore the ownership the operation replaced.
|
|
110
|
+
*/
|
|
111
|
+
function commit(args: CommitArgs): WriteOutcome {
|
|
112
|
+
const { io, clientId, configPath } = args;
|
|
113
|
+
try {
|
|
114
|
+
if (args.nextText === null) io.removeFile(configPath);
|
|
115
|
+
else {
|
|
116
|
+
io.mkdirp(dirname(configPath));
|
|
117
|
+
io.writeText(configPath, args.nextText);
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
return refuse(clientId, "write_failed", args.state, messageOf(error), args.snapshotPath);
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
if (args.record) io.putRecord(args.record);
|
|
124
|
+
else io.dropRecord(clientId);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
return compensate(args, error, "could not record ownership");
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
io.appendJournal(args.entry);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
return compensate(args, error, "could not append the journal row");
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
ok: true,
|
|
135
|
+
changed: true,
|
|
136
|
+
state: args.state,
|
|
137
|
+
clientId,
|
|
138
|
+
opId: args.entry.opId,
|
|
139
|
+
message: "ok",
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function compensate(args: CommitArgs, cause: unknown, what: string): WriteRefused {
|
|
144
|
+
const { io, clientId, configPath } = args;
|
|
145
|
+
try {
|
|
146
|
+
if (args.before === null) io.removeFile(configPath);
|
|
147
|
+
else io.writeText(configPath, args.before);
|
|
148
|
+
if (args.priorRecord) io.putRecord(args.priorRecord);
|
|
149
|
+
else io.dropRecord(clientId);
|
|
150
|
+
} catch {
|
|
151
|
+
// Say so. A false "rolled back" is worse than the original error, because
|
|
152
|
+
// the user would stop looking for the file we left half-written.
|
|
153
|
+
return {
|
|
154
|
+
ok: false,
|
|
155
|
+
reason: "write_failed",
|
|
156
|
+
state: args.state,
|
|
157
|
+
clientId,
|
|
158
|
+
residual: true,
|
|
159
|
+
...(args.snapshotPath ? { snapshotPath: args.snapshotPath } : {}),
|
|
160
|
+
message: `${what}, and the change could not be rolled back. The file or its ownership record is in an intermediate state; the backup is at ${args.snapshotPath ?? "(none)"}.`,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return refuse(clientId, "write_failed", args.state, `${what}; the change was rolled back. Cause: ${messageOf(cause)}`, args.snapshotPath);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function snapshotAbsPath(store: IntegrationStateStore, entry: JournalEntry): string | undefined {
|
|
167
|
+
const snapshot = store.readSnapshot(entry);
|
|
168
|
+
return snapshot.kind === "stored" ? snapshot.path : undefined;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Shared preflight: detect, gate, read, parse and classify. */
|
|
172
|
+
function preflight(input: IntegrationWriteInput) {
|
|
173
|
+
const store = input.store ?? createIntegrationStateStore();
|
|
174
|
+
const io = input.io ?? defaultIntegrationIO(store);
|
|
175
|
+
const clientId = input.clientId;
|
|
176
|
+
const spec = INTEGRATION_CLIENTS[clientId];
|
|
177
|
+
const exportSpec = EXPORT_CLIENTS[clientId];
|
|
178
|
+
/*
|
|
179
|
+
* Resolution itself can refuse: a relative OPENCLAW_* selector is rejected
|
|
180
|
+
* because we cannot know the gateway's working directory. That is a refusal
|
|
181
|
+
* about the user's configuration, not an internal fault, so it must not
|
|
182
|
+
* escape as an exception — the collection route would answer 500 for the
|
|
183
|
+
* whole Integrations page because one client is misconfigured.
|
|
184
|
+
*/
|
|
185
|
+
let configPath: string;
|
|
186
|
+
try {
|
|
187
|
+
configPath = spec.configPath(input.env, input.home);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
if (!(error instanceof ClientPathError)) throw error;
|
|
190
|
+
return { failed: refuse(clientId, "unsafe", "unsafe", error.message) } as const;
|
|
191
|
+
}
|
|
192
|
+
store.retryPendingPrunes();
|
|
193
|
+
|
|
194
|
+
const target = loadTarget(io, configPath);
|
|
195
|
+
if (!target.ok) {
|
|
196
|
+
return {
|
|
197
|
+
failed: refuse(clientId, "unsafe", "unsafe",
|
|
198
|
+
target.why === "read-failed"
|
|
199
|
+
? `${configPath} exists but could not be read`
|
|
200
|
+
: `${configPath} is not a regular file`),
|
|
201
|
+
} as const;
|
|
202
|
+
}
|
|
203
|
+
const before = target.before;
|
|
204
|
+
const parsed = parseConfig(before, exportSpec.format);
|
|
205
|
+
if (parsed === PARSE_FAILED) {
|
|
206
|
+
return { failed: refuse(clientId, "unsafe", "unsafe", `${configPath} could not be parsed`) } as const;
|
|
207
|
+
}
|
|
208
|
+
const contribution = exportSpec.buildContribution(exportContextOf(input));
|
|
209
|
+
// A record proves ownership of the file it was written FOR. Matching only by
|
|
210
|
+
// client id let a record for one home authorize a write to another whose
|
|
211
|
+
// bytes happened to hash the same — which deleted a config we never touched.
|
|
212
|
+
const stored = store.readRecords()[clientId] ?? null;
|
|
213
|
+
const record = stored && stored.clientId === clientId && stored.configPath === configPath
|
|
214
|
+
? stored
|
|
215
|
+
: null;
|
|
216
|
+
// `configPath`/`clientId` are load-bearing, not decoration: a record proves
|
|
217
|
+
// ownership of ONE file, and the writer mutates whatever path resolves NOW.
|
|
218
|
+
// Without them a record written for another home directory would grant
|
|
219
|
+
// ownership here and disable would delete fragments it never wrote.
|
|
220
|
+
const classified = classifyIntegration({
|
|
221
|
+
fileText: before, fileIsRegular: true, parsed, record, contribution, configPath, clientId,
|
|
222
|
+
});
|
|
223
|
+
return { failed: undefined, store, io, clientId, spec, exportSpec, configPath, before, parsed, contribution, record, classified } as const;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function applyIntegration(input: IntegrationWriteInput): WriteOutcome {
|
|
227
|
+
const pre = preflight(input);
|
|
228
|
+
if (pre.failed) return pre.failed;
|
|
229
|
+
const { store, io, clientId, spec, exportSpec, configPath, before, parsed, contribution, record, classified } = pre;
|
|
230
|
+
|
|
231
|
+
if (io.statKind(spec.detectDir(input.env, input.home)) !== "dir") {
|
|
232
|
+
return refuse(clientId, "not_installed", "absent", `${clientId} is not installed`);
|
|
233
|
+
}
|
|
234
|
+
if (isLoopbackOnly(clientId) && !isLoopbackHostname(input.config.hostname)) {
|
|
235
|
+
return refuse(clientId, "non_loopback", classified.state,
|
|
236
|
+
`The generated ${clientId} integration is loopback-only and does not emit the admission header a non-loopback bind requires. Give it loopback access instead, through a tunnel or a local forwarder.`);
|
|
237
|
+
}
|
|
238
|
+
if (classified.state === "conflict") {
|
|
239
|
+
return refuse(clientId, "conflict", "conflict",
|
|
240
|
+
classified.reason === "foreign-edit"
|
|
241
|
+
? `${configPath} changed after Remodex wrote it`
|
|
242
|
+
: `${configPath} already contains a Remodex block we did not write`);
|
|
243
|
+
}
|
|
244
|
+
/*
|
|
245
|
+
* `unsafe` from the classifier means the document is not one we may write
|
|
246
|
+
* through — today that is a container on our fragment path holding a
|
|
247
|
+
* non-object value the merge would replace with `{}`. Unreadable and
|
|
248
|
+
* unparseable files are caught earlier in preflight; this branch exists
|
|
249
|
+
* because the classifier can also refuse a file it CAN read.
|
|
250
|
+
*/
|
|
251
|
+
if (classified.state === "unsafe") {
|
|
252
|
+
return refuse(clientId, "unsafe", "unsafe",
|
|
253
|
+
classified.reason === "blocked-container"
|
|
254
|
+
? `${configPath} holds a value where Remodex would have to write a section, so applying would replace it`
|
|
255
|
+
: `${configPath} cannot be changed safely`);
|
|
256
|
+
}
|
|
257
|
+
if (classified.state === "current") {
|
|
258
|
+
return { ok: true, changed: false, state: "current", clientId, message: "already applied" };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// A stale refresh drops what the PREVIOUS record owned before merging: a
|
|
262
|
+
// model that left the catalog would otherwise stay behind as an orphan the
|
|
263
|
+
// new record no longer covers, and disable could never remove it.
|
|
264
|
+
/*
|
|
265
|
+
* The previous record's `createdContainers` has to travel with this removal.
|
|
266
|
+
* Without it the refresh leaves our own empty scaffolding behind in `base`,
|
|
267
|
+
* `createdContainerPaths` then sees the container already present and
|
|
268
|
+
* concludes the user owns it, and the replacement record forgets we made it
|
|
269
|
+
* — so a later disable strands it forever.
|
|
270
|
+
*/
|
|
271
|
+
const base = classified.state === "stale" && record
|
|
272
|
+
? removeFragments(parsed, record.fragmentPaths, new Set(record.createdContainers ?? [])).doc
|
|
273
|
+
: parsed;
|
|
274
|
+
// Computed against the document as it stands BEFORE the merge: afterwards
|
|
275
|
+
// every container exists and "did we create this?" is unanswerable.
|
|
276
|
+
const created = createdContainerPaths(base, contribution);
|
|
277
|
+
/*
|
|
278
|
+
* A document can hold a value its own format cannot round-trip through our
|
|
279
|
+
* renderers. That used to throw straight out of the writer and reach the
|
|
280
|
+
* user as a 500 with no path and no advice; it is a refusal like any other,
|
|
281
|
+
* and the file is untouched because this happens before any write.
|
|
282
|
+
*/
|
|
283
|
+
let text: string;
|
|
284
|
+
try {
|
|
285
|
+
text = serializeDocument(mergeContribution(base, contribution), exportSpec.format);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
if (!(error instanceof UnserializableValueError)) throw error;
|
|
288
|
+
return refuse(clientId, "unsafe", "unsafe",
|
|
289
|
+
`${configPath} contains something Remodex cannot rewrite safely (${error.message}), so it was left alone`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Compare-before-commit: someone may have written between classify and now.
|
|
293
|
+
const recheck = io.readText(configPath);
|
|
294
|
+
const rechecked = recheck.kind === "text" ? recheck.text : recheck.kind === "missing" ? null : undefined;
|
|
295
|
+
if (rechecked === undefined || rechecked !== before) {
|
|
296
|
+
return refuse(clientId, "conflict", "conflict", `${configPath} changed while applying`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const opId = newOpId();
|
|
300
|
+
const snapshot = store.captureSnapshot(clientId, opId, before);
|
|
301
|
+
const at = new Date(io.now()).toISOString();
|
|
302
|
+
const entry: JournalEntry = {
|
|
303
|
+
opId, clientId, kind: classified.state === "stale" ? "refresh" : "apply", at, configPath,
|
|
304
|
+
snapshot, resultFingerprint: fingerprint(text), resultAbsent: false, priorRecord: record,
|
|
305
|
+
};
|
|
306
|
+
return commit({
|
|
307
|
+
io, store, clientId, configPath, before, nextText: text, state: "current",
|
|
308
|
+
priorRecord: record,
|
|
309
|
+
record: {
|
|
310
|
+
clientId, configPath, fileFingerprint: fingerprint(text),
|
|
311
|
+
blockFingerprint: fingerprint(canonicalContribution(contribution)),
|
|
312
|
+
fragmentPaths: fragmentPathsOf(contribution), createdContainers: created,
|
|
313
|
+
appliedAt: at, opId,
|
|
314
|
+
},
|
|
315
|
+
entry,
|
|
316
|
+
snapshotPath: snapshotAbsPath(store, entry),
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function disableIntegration(input: IntegrationWriteInput): WriteOutcome {
|
|
321
|
+
const pre = preflight(input);
|
|
322
|
+
if (pre.failed) return pre.failed;
|
|
323
|
+
const { store, io, clientId, exportSpec, configPath, before, parsed, record, classified } = pre;
|
|
324
|
+
|
|
325
|
+
if (classified.state === "absent") {
|
|
326
|
+
return { ok: true, changed: false, state: "absent", clientId, message: "not applied" };
|
|
327
|
+
}
|
|
328
|
+
if (classified.state === "conflict") {
|
|
329
|
+
return refuse(clientId, "conflict", "conflict",
|
|
330
|
+
classified.reason === "foreign-edit"
|
|
331
|
+
? `${configPath} changed after Remodex wrote it; disabling would discard that edit`
|
|
332
|
+
: `${configPath} contains a Remodex block we did not write`);
|
|
333
|
+
}
|
|
334
|
+
/*
|
|
335
|
+
* `unsafe` reaches here the same way it reaches apply, and the code below
|
|
336
|
+
* dereferences `record` on the assumption that anything past this point is
|
|
337
|
+
* `current` or `stale`. A blocked container has no record, so disable threw
|
|
338
|
+
* a TypeError and the route answered 500 — the GUI locks the switch, but the
|
|
339
|
+
* CLI and direct API callers do not.
|
|
340
|
+
*/
|
|
341
|
+
if (classified.state === "unsafe") {
|
|
342
|
+
return refuse(clientId, "unsafe", "unsafe",
|
|
343
|
+
classified.reason === "blocked-container"
|
|
344
|
+
? `${configPath} holds a value where Remodex would have to read a section, so nothing can be removed safely`
|
|
345
|
+
: `${configPath} cannot be changed safely`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// current | stale only: the file fingerprint still matches our record, so the
|
|
349
|
+
// recorded paths are exactly what we put there.
|
|
350
|
+
const { doc, removed } = removeFragments(
|
|
351
|
+
parsed,
|
|
352
|
+
record!.fragmentPaths,
|
|
353
|
+
new Set(record!.createdContainers ?? []),
|
|
354
|
+
);
|
|
355
|
+
if (!removed) {
|
|
356
|
+
return { ok: true, changed: false, state: "absent", clientId, message: "nothing to remove" };
|
|
357
|
+
}
|
|
358
|
+
let text: string;
|
|
359
|
+
try {
|
|
360
|
+
text = serializeDocument(doc, exportSpec.format);
|
|
361
|
+
} catch (error) {
|
|
362
|
+
if (!(error instanceof UnserializableValueError)) throw error;
|
|
363
|
+
return refuse(clientId, "unsafe", "unsafe",
|
|
364
|
+
`${configPath} contains something Remodex cannot rewrite safely (${error.message}), so nothing was removed`);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const recheck = io.readText(configPath);
|
|
368
|
+
const rechecked = recheck.kind === "text" ? recheck.text : recheck.kind === "missing" ? null : undefined;
|
|
369
|
+
if (rechecked === undefined || rechecked !== before) {
|
|
370
|
+
return refuse(clientId, "conflict", "conflict", `${configPath} changed while disabling`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const opId = newOpId();
|
|
374
|
+
const snapshot = store.captureSnapshot(clientId, opId, before);
|
|
375
|
+
const at = new Date(io.now()).toISOString();
|
|
376
|
+
const entry: JournalEntry = {
|
|
377
|
+
opId, clientId, kind: "disable", at, configPath, snapshot,
|
|
378
|
+
resultFingerprint: fingerprint(text), resultAbsent: false, priorRecord: record,
|
|
379
|
+
};
|
|
380
|
+
return commit({
|
|
381
|
+
io, store, clientId, configPath, before, nextText: text, state: "absent",
|
|
382
|
+
record: null, priorRecord: record, entry,
|
|
383
|
+
snapshotPath: snapshotAbsPath(store, entry),
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function restoreIntegration(input: IntegrationRestoreInput): WriteOutcome {
|
|
388
|
+
const store = input.store ?? createIntegrationStateStore();
|
|
389
|
+
const io = input.io ?? defaultIntegrationIO(store);
|
|
390
|
+
const entry = store.findOperation(input.opId);
|
|
391
|
+
if (!entry) throw new Error(`unknown operation ${input.opId}`);
|
|
392
|
+
if (entry.clientId !== input.clientId) throw new Error("restore input names a different client than the operation");
|
|
393
|
+
|
|
394
|
+
const clientId = entry.clientId;
|
|
395
|
+
const resolvedPath = INTEGRATION_CLIENTS[clientId].configPath(input.env, input.home);
|
|
396
|
+
// Restore acts on the path the operation was journaled against. Resolving a
|
|
397
|
+
// different path here would let an operation recorded for one home delete a
|
|
398
|
+
// file in another.
|
|
399
|
+
const configPath = entry.configPath;
|
|
400
|
+
if (resolvedPath !== configPath) {
|
|
401
|
+
return refuse(clientId, "conflict", "conflict",
|
|
402
|
+
`that operation was recorded for ${configPath}, but this client now resolves to ${resolvedPath}`);
|
|
403
|
+
}
|
|
404
|
+
const snapshot = store.readSnapshot(entry);
|
|
405
|
+
if (snapshot.kind === "expired") {
|
|
406
|
+
return refuse(clientId, "snapshot_expired", "absent", "that backup has expired");
|
|
407
|
+
}
|
|
408
|
+
const backupHint = snapshot.kind === "stored" ? snapshot.path : undefined;
|
|
409
|
+
|
|
410
|
+
const target = loadTarget(io, configPath);
|
|
411
|
+
if (!target.ok) {
|
|
412
|
+
return refuse(clientId, "unsafe", "unsafe",
|
|
413
|
+
target.why === "read-failed"
|
|
414
|
+
? `${configPath} exists but could not be read; the backup is at ${backupHint ?? "(none)"}`
|
|
415
|
+
: `${configPath} is not a regular file; the backup is at ${backupHint ?? "(none)"}`,
|
|
416
|
+
backupHint);
|
|
417
|
+
}
|
|
418
|
+
const current = target.before;
|
|
419
|
+
|
|
420
|
+
/*
|
|
421
|
+
* Drift: the file changed after the operation we are undoing.
|
|
422
|
+
*
|
|
423
|
+
* Through the shared matcher, because `fingerprint(current ?? "")` treated a
|
|
424
|
+
* MISSING file as an empty one — so restoring an operation whose result was
|
|
425
|
+
* absence, with the file still absent, read as drift and demanded a
|
|
426
|
+
* confirmation for edits nobody had made. The journal meanwhile offered the
|
|
427
|
+
* same row as Undo.
|
|
428
|
+
*/
|
|
429
|
+
if (!matchesOperationResult(entry, current) && !input.confirmDrift) {
|
|
430
|
+
return refuse(clientId, "drift_requires_confirm", "conflict",
|
|
431
|
+
"this file changed after that operation; confirm to replace it (the current version is backed up first)");
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Restore is itself journaled and itself undoable: snapshot the CURRENT file
|
|
435
|
+
// first, so a confirmed drift-restore never destroys the newer edits.
|
|
436
|
+
const opId = newOpId();
|
|
437
|
+
const preSnapshot = store.captureSnapshot(clientId, opId, current);
|
|
438
|
+
const restoredText = snapshot.kind === "none" ? null : snapshot.text;
|
|
439
|
+
|
|
440
|
+
// Provenance is RESTORED, never re-derived: `priorRecord` described these
|
|
441
|
+
// exact bytes when the snapshot was taken. Re-deriving it from the file would
|
|
442
|
+
// mean guessing which entries are ours, and a wrong guess deletes a user's.
|
|
443
|
+
const restoredRecord = entry.priorRecord;
|
|
444
|
+
const fresh = EXPORT_CLIENTS[clientId].buildContribution(exportContextOf(input));
|
|
445
|
+
/*
|
|
446
|
+
* Does the restored record actually describe the restored bytes?
|
|
447
|
+
*
|
|
448
|
+
* It usually does — `priorRecord` was written for exactly this snapshot. But
|
|
449
|
+
* a CONFIRMED drift-restore snapshots the user's edited file first, and
|
|
450
|
+
* undoing that restore puts those edited bytes back while carrying a record
|
|
451
|
+
* that describes what Remodex had written. Overwriting the record's
|
|
452
|
+
* fingerprint with the restored bytes then laundered a foreign edit into
|
|
453
|
+
* owned content: the state read `current`, and a later disable deleted
|
|
454
|
+
* fields the user had added by hand.
|
|
455
|
+
*/
|
|
456
|
+
const restoredFingerprint = restoredText === null ? "" : fingerprint(restoredText);
|
|
457
|
+
const recordDescribesBytes = restoredRecord !== null
|
|
458
|
+
&& restoredRecord.fileFingerprint === restoredFingerprint;
|
|
459
|
+
const state: IntegrationState = restoredRecord === null
|
|
460
|
+
? (restoredText === null ? "absent" : "conflict")
|
|
461
|
+
: !recordDescribesBytes
|
|
462
|
+
? "conflict"
|
|
463
|
+
: restoredRecord.blockFingerprint === fingerprint(canonicalContribution(fresh))
|
|
464
|
+
? "current"
|
|
465
|
+
: "stale";
|
|
466
|
+
|
|
467
|
+
const at = new Date(io.now()).toISOString();
|
|
468
|
+
const priorRecord = store.readRecords()[clientId] ?? null;
|
|
469
|
+
const restoreEntry: JournalEntry = {
|
|
470
|
+
opId, clientId, kind: "restore", at, configPath, snapshot: preSnapshot,
|
|
471
|
+
resultFingerprint: restoredText === null ? "" : fingerprint(restoredText),
|
|
472
|
+
resultAbsent: restoredText === null,
|
|
473
|
+
priorRecord,
|
|
474
|
+
};
|
|
475
|
+
return commit({
|
|
476
|
+
io, store, clientId, configPath, before: current, nextText: restoredText, state,
|
|
477
|
+
priorRecord,
|
|
478
|
+
/*
|
|
479
|
+
* Keep the record's ORIGINAL `fileFingerprint`. Restoring bytes it does not
|
|
480
|
+
* describe leaves the classifier reading `conflict`, which is the honest
|
|
481
|
+
* answer — we are no longer sure the block on disk is ours, and refusing
|
|
482
|
+
* is what stops a disable from deleting the user's edit.
|
|
483
|
+
*/
|
|
484
|
+
record: restoredRecord === null ? null : {
|
|
485
|
+
...restoredRecord,
|
|
486
|
+
appliedAt: at,
|
|
487
|
+
opId,
|
|
488
|
+
},
|
|
489
|
+
entry: restoreEntry,
|
|
490
|
+
snapshotPath: snapshotAbsPath(store, restoreEntry),
|
|
491
|
+
});
|
|
492
|
+
}
|
package/src/lib/abort.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
export interface LinkedAbortSignal {
|
|
2
|
+
signal: AbortSignal;
|
|
3
|
+
cleanup: () => void;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface ClearableDeadline {
|
|
7
|
+
/** Parent-linked signal passed to fetch; remains parent-linked after clear(). */
|
|
8
|
+
signal: AbortSignal;
|
|
9
|
+
/** Stable reason object used when this deadline wins the abort race. */
|
|
10
|
+
timeoutReason: DOMException;
|
|
11
|
+
/** True only when this deadline, rather than the parent, fired first. */
|
|
12
|
+
didExpire: () => boolean;
|
|
13
|
+
/** Clear only the timer. Never aborts the deadline controller or detaches the parent. */
|
|
14
|
+
clear: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface IdleDeadline {
|
|
18
|
+
/** (Re-)arm the timer for one idle window. Call when a wait for progress BEGINS. No-op after fire/cancel. */
|
|
19
|
+
reset: () => void;
|
|
20
|
+
/** Disarm the timer WITHOUT retiring the deadline (call when the awaited progress arrives). No-op after fire/cancel. */
|
|
21
|
+
pause: () => void;
|
|
22
|
+
/** Retire permanently (success/teardown paths). Idempotent. */
|
|
23
|
+
cancel: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Resettable inactivity deadline (devlog 260716_passthrough_followups/010).
|
|
28
|
+
*
|
|
29
|
+
* Fires `onIdle` at most ONCE after `idleMs` elapses with no `reset()`/`pause()`.
|
|
30
|
+
* Contract:
|
|
31
|
+
* - `idleMs <= 0` returns an inert no-op deadline — the 0-disable responsibility
|
|
32
|
+
* lives here so callers cannot mis-handle it.
|
|
33
|
+
* - The timer starts DISARMED: callers arm it with `reset()` when a wait begins and
|
|
34
|
+
* `pause()` it when the wait settles, so pull-based relays never count downstream
|
|
35
|
+
* backpressure (no pending read) as upstream inactivity.
|
|
36
|
+
* - First terminal wins: after `onIdle` runs or `cancel()` is called, every method
|
|
37
|
+
* is a no-op and `onIdle` never runs again.
|
|
38
|
+
* - Never linked to fetch signals — the consumer decides how to kill its stream
|
|
39
|
+
* (e.g. reader.cancel), keeping body-lifetime semantics unchanged.
|
|
40
|
+
*/
|
|
41
|
+
export function idleDeadline(idleMs: number, onIdle: () => void): IdleDeadline {
|
|
42
|
+
if (idleMs <= 0) return { reset: () => {}, pause: () => {}, cancel: () => {} };
|
|
43
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
44
|
+
let done = false;
|
|
45
|
+
const disarm = () => {
|
|
46
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
47
|
+
timer = undefined;
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
reset: () => {
|
|
51
|
+
if (done) return;
|
|
52
|
+
disarm();
|
|
53
|
+
timer = setTimeout(() => {
|
|
54
|
+
if (done) return;
|
|
55
|
+
done = true;
|
|
56
|
+
timer = undefined;
|
|
57
|
+
onIdle();
|
|
58
|
+
}, idleMs);
|
|
59
|
+
},
|
|
60
|
+
pause: () => {
|
|
61
|
+
if (done) return;
|
|
62
|
+
disarm();
|
|
63
|
+
},
|
|
64
|
+
cancel: () => {
|
|
65
|
+
if (done) return;
|
|
66
|
+
done = true;
|
|
67
|
+
disarm();
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Response-header deadline whose timer can be cleared without severing body-lifetime cancellation.
|
|
74
|
+
*
|
|
75
|
+
* `signalWithTimeout().cleanup()` intentionally removes its parent listener and is therefore suited
|
|
76
|
+
* to operations that are completely finished at cleanup. A fetch response body is different: once
|
|
77
|
+
* headers arrive the deadline ends, but the original parent/client signal must remain attached to
|
|
78
|
+
* the body. `AbortSignal.any()` supplies that direct lifetime link while `clear()` owns only the
|
|
79
|
+
* timer.
|
|
80
|
+
*/
|
|
81
|
+
export function clearableDeadline(timeoutMs: number, parent?: AbortSignal): ClearableDeadline {
|
|
82
|
+
const deadline = new AbortController();
|
|
83
|
+
const timeoutReason = new DOMException("Timeout elapsed", "TimeoutError");
|
|
84
|
+
let timer: ReturnType<typeof setTimeout> | undefined = setTimeout(() => {
|
|
85
|
+
timer = undefined;
|
|
86
|
+
if (!deadline.signal.aborted) deadline.abort(timeoutReason);
|
|
87
|
+
}, timeoutMs);
|
|
88
|
+
const signal = parent ? AbortSignal.any([parent, deadline.signal]) : deadline.signal;
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
signal,
|
|
92
|
+
timeoutReason,
|
|
93
|
+
didExpire: () => signal.aborted && signal.reason === timeoutReason,
|
|
94
|
+
clear: () => {
|
|
95
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
96
|
+
timer = undefined;
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function signalWithTimeout(timeoutMs: number, parent?: AbortSignal): LinkedAbortSignal {
|
|
102
|
+
const controller = new AbortController();
|
|
103
|
+
const timeout = setTimeout(() => {
|
|
104
|
+
if (!controller.signal.aborted) controller.abort(new DOMException("Timeout elapsed", "TimeoutError"));
|
|
105
|
+
}, timeoutMs);
|
|
106
|
+
|
|
107
|
+
const abortFromParent = () => {
|
|
108
|
+
if (!controller.signal.aborted) controller.abort(parent?.reason);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
if (parent?.aborted) {
|
|
112
|
+
abortFromParent();
|
|
113
|
+
} else {
|
|
114
|
+
parent?.addEventListener("abort", abortFromParent, { once: true });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
signal: controller.signal,
|
|
119
|
+
cleanup: () => {
|
|
120
|
+
clearTimeout(timeout);
|
|
121
|
+
parent?.removeEventListener("abort", abortFromParent);
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Bind a response body's lifetime to an abort signal.
|
|
128
|
+
*
|
|
129
|
+
* Bun's HTTP client, when a `fetch(..., { signal })` is aborted AFTER the response resolved, tears
|
|
130
|
+
* down the response body stream and rejects any in-flight internal read. If our code hasn't attached
|
|
131
|
+
* a reader yet (e.g. the abort lands between `await fetch()` and the decoder's first read), that
|
|
132
|
+
* rejection is orphaned off the awaited path and Bun reports it as
|
|
133
|
+
* `unhandledRejection: TypeError: null is not an object` (native-only stack) — uncatchable by any
|
|
134
|
+
* caller try/catch. Proactively cancelling the body on abort makes US the consumer that settles it,
|
|
135
|
+
* so the rejection is absorbed. Returns a cleanup to detach the listener on the normal path.
|
|
136
|
+
*/
|
|
137
|
+
export function cancelBodyOnAbort(body: ReadableStream<Uint8Array> | null, signal?: AbortSignal): () => void {
|
|
138
|
+
if (!body || !signal) return () => {};
|
|
139
|
+
const onAbort = () => { void body.cancel().catch(() => {}); };
|
|
140
|
+
if (signal.aborted) {
|
|
141
|
+
onAbort();
|
|
142
|
+
return () => {};
|
|
143
|
+
}
|
|
144
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
145
|
+
return () => signal.removeEventListener("abort", onAbort);
|
|
146
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { lstatSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getConfigDir } from "../config";
|
|
4
|
+
|
|
5
|
+
export const ADMIN_TOKEN_FILE = "admin-api-token";
|
|
6
|
+
|
|
7
|
+
export function adminApiTokenFilePath(configDir = getConfigDir()): string {
|
|
8
|
+
return join(configDir, ADMIN_TOKEN_FILE);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function loadAdminTokenFromFile(configDir = getConfigDir()): string | null {
|
|
12
|
+
const path = adminApiTokenFilePath(configDir);
|
|
13
|
+
try {
|
|
14
|
+
const stat = lstatSync(path);
|
|
15
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 512) return null;
|
|
16
|
+
const token = readFileSync(path, "utf8").trim();
|
|
17
|
+
return /^ocx_admin_[A-Za-z0-9_-]{43}$/.test(token) ? token : null;
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function configuredAdminToken(configDir = getConfigDir(), env: NodeJS.ProcessEnv = process.env): string | null {
|
|
24
|
+
return env.OPENCODEX_ADMIN_AUTH_TOKEN?.trim() || loadAdminTokenFromFile(configDir);
|
|
25
|
+
}
|