@iislee/opencodex 2.11.0 → 2.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS_INSTALL.md +109 -0
- package/README.md +114 -19
- package/bin/ocx.mjs +164 -36
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-BF38heuV.js +104 -0
- package/gui/dist/assets/index-DMiI18Kv.css +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +19 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +376 -52
- package/src/adapters/base.ts +54 -7
- package/src/adapters/client-fingerprint.ts +18 -12
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +601 -0
- package/src/adapters/cursor/checkpoint-store.ts +303 -0
- package/src/adapters/cursor/cursor-errors.ts +129 -6
- package/src/adapters/cursor/discovery.ts +61 -4
- package/src/adapters/cursor/effort-map.ts +27 -3
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/h2-pool.ts +123 -0
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/images.ts +704 -0
- package/src/adapters/cursor/live-models.ts +174 -58
- package/src/adapters/cursor/live-transport.ts +609 -170
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-fs.ts +10 -7
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +0 -1
- package/src/adapters/cursor/native-exec.ts +101 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +383 -65
- package/src/adapters/cursor/request-builder.ts +237 -31
- package/src/adapters/cursor/tool-definitions.ts +242 -12
- package/src/adapters/cursor/tool-result-normalize.ts +92 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +28 -1
- package/src/adapters/cursor.ts +133 -7
- package/src/adapters/google-antigravity-replay.ts +558 -34
- package/src/adapters/google-antigravity-wire.ts +43 -10
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +618 -74
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +18 -2
- package/src/adapters/kiro-tools.ts +10 -1
- package/src/adapters/kiro.ts +92 -54
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1311 -336
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +830 -56
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +104 -21
- package/src/adapters/xai-web-search.ts +185 -0
- package/src/bridge.ts +428 -61
- package/src/chat/inbound.ts +43 -19
- package/src/chat/outbound.ts +82 -26
- package/src/claude/agents-inject.ts +32 -9
- package/src/claude/context-windows.ts +21 -5
- package/src/claude/desktop-3p.ts +243 -9
- package/src/claude/gateway-cache.ts +41 -4
- package/src/claude/inbound.ts +72 -3
- package/src/claude/model-info.ts +38 -15
- package/src/claude/outbound.ts +70 -16
- package/src/cli/account-api.ts +35 -1
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +389 -2
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +30 -6
- package/src/cli/agent.ts +45 -1
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +29 -4
- package/src/cli/claude.ts +103 -34
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +76 -0
- package/src/cli/combo.ts +8 -0
- package/src/cli/config-command.ts +74 -10
- package/src/cli/dispatch.ts +593 -0
- package/src/cli/doctor.ts +315 -43
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +20 -271
- package/src/cli/index.ts +378 -519
- package/src/cli/init.ts +4 -17
- package/src/cli/integrations.ts +120 -2
- package/src/cli/lab.ts +607 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/minimax.ts +497 -0
- package/src/cli/models-runtime.ts +35 -2
- package/src/cli/models.ts +100 -14
- package/src/cli/observe.ts +92 -3
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +18 -1
- package/src/cli/provider.ts +24 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +437 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +6 -3
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +10 -3
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/v2.ts +105 -10
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1358 -21
- package/src/codex/account-label.ts +14 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/account-store.ts +29 -2
- package/src/codex/account-usability.ts +25 -2
- package/src/codex/admission.ts +256 -0
- package/src/codex/affinity-debug.ts +162 -0
- package/src/codex/app-server-processes.ts +493 -106
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +849 -242
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +345 -32
- package/src/codex/autostart-health.ts +8 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +68 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +121 -30
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +529 -45
- package/src/codex/catalog/native-models.ts +72 -0
- package/src/codex/catalog/parsing.ts +224 -30
- package/src/codex/catalog/provider-fetch.ts +1460 -134
- package/src/codex/catalog/sync.ts +1449 -186
- package/src/codex/catalog-admission.ts +199 -0
- package/src/codex/catalog-refresh-status.ts +105 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +6 -3
- package/src/codex/codex-write-lock.ts +384 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +651 -0
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +407 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-migration-guardian.ts +26 -20
- package/src/codex/history-provider.ts +231 -28
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +220 -0
- package/src/codex/inject-coordination.ts +290 -0
- package/src/codex/inject.ts +1073 -152
- package/src/codex/injected-marker.ts +37 -3
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +80 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +506 -0
- package/src/codex/log-guard/lock.ts +150 -0
- package/src/codex/log-guard/maintenance.ts +403 -0
- package/src/codex/log-guard/path-safety.ts +88 -0
- package/src/codex/log-guard/policy.ts +44 -0
- package/src/codex/log-guard/processes.ts +205 -0
- package/src/codex/log-guard/protection.ts +489 -0
- package/src/codex/log-guard/sqlite-errors.ts +9 -0
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/main-account.ts +29 -1
- package/src/codex/management-convergence.ts +167 -0
- package/src/codex/model-cache.ts +56 -10
- package/src/codex/model-entitlements.ts +353 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +178 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +492 -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 +682 -0
- package/src/codex/paths.ts +80 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +40 -0
- package/src/codex/plugins-doctor.ts +1 -1
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/project-config-warnings.ts +20 -6
- package/src/codex/prompt-journal.ts +352 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +175 -13
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +505 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1009 -28
- package/src/codex/subagent-model-fallback.ts +350 -35
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +612 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +557 -0
- package/src/codex/warmup.ts +187 -81
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +20 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +81 -9
- package/src/config/provider-name.ts +24 -0
- package/src/config.ts +1762 -140
- package/src/generated/compatibility-version.json +3164 -0
- package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
- package/src/grok/inject.ts +16 -5
- package/src/grok/inspect.ts +45 -0
- package/src/grok/sync.ts +2 -2
- package/src/images/loop.ts +152 -29
- package/src/images/plan.ts +23 -13
- package/src/integrations/config-io.ts +269 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/mutation-flight.ts +71 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/omp-yaml-source.ts +358 -0
- package/src/integrations/owned-refresh.ts +74 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +159 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +361 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +691 -0
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +499 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +64 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +164 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +68 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +214 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +229 -0
- package/src/lab/events/validate.ts +781 -0
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +489 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +54 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +310 -0
- package/src/lab/ledger/store.ts +532 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +336 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +118 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +301 -0
- package/src/lab/observe/from-live.ts +117 -0
- package/src/lab/paths.ts +153 -0
- package/src/lab/projection/rebuild.ts +495 -0
- package/src/lab/projection/schema.ts +135 -0
- package/src/lab/projection/verdicts.ts +474 -0
- package/src/lab/projection/verification.ts +412 -0
- package/src/lab/public/bundle.ts +217 -0
- package/src/lab/public/community-authority.ts +175 -0
- package/src/lab/public/community-files.ts +29 -0
- package/src/lab/public/community.ts +479 -0
- package/src/lab/public/file-safety.ts +155 -0
- package/src/lab/public/ids.ts +26 -0
- package/src/lab/public/index.ts +16 -0
- package/src/lab/public/mutation-lock.ts +424 -0
- package/src/lab/public/operator.ts +353 -0
- package/src/lab/public/origin-purge.ts +79 -0
- package/src/lab/public/origin.ts +203 -0
- package/src/lab/public/privacy.ts +143 -0
- package/src/lab/public/private-file.ts +261 -0
- package/src/lab/public/project.ts +124 -0
- package/src/lab/public/purge-test-fault.ts +21 -0
- package/src/lab/public/purge.ts +223 -0
- package/src/lab/public/registry.ts +44 -0
- package/src/lab/public/revocation.ts +252 -0
- package/src/lab/public/signature.ts +243 -0
- package/src/lab/public/storage.ts +105 -0
- package/src/lab/public/strict-json.ts +206 -0
- package/src/lab/public/time.ts +26 -0
- package/src/lab/public/types.ts +172 -0
- package/src/lab/public/validate.ts +391 -0
- package/src/lab/query/catalog.ts +101 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +45 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +112 -0
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +74 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +153 -9
- package/src/lib/bun-runtime.ts +125 -12
- package/src/lib/bun-stream-caps.ts +13 -9
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/config-ownership.ts +6 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +44 -2
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/lab-activation.ts +223 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +56 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/lab-passive-linker-registration.ts +26 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/local-provider-reload-contract.ts +100 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +4 -1
- package/src/lib/provider-outbound.ts +49 -9
- package/src/lib/redact.ts +419 -3
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/shadow-call.ts +35 -4
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/state-store-registrations.ts +10 -2
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/token-estimate.ts +19 -2
- package/src/lib/tool-argument-integers.ts +202 -0
- package/src/lib/translator-budget.ts +44 -0
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-reachability.ts +95 -0
- package/src/lib/upstream-retry.ts +156 -3
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-elevation.ts +70 -2
- package/src/lib/windows-secret-acl.ts +409 -69
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/lib/windows-text.ts +106 -0
- package/src/lib/windows-user-principal.ts +341 -0
- package/src/lib/winsw.ts +33 -5
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic.ts +12 -1
- package/src/oauth/callback-server.ts +8 -2
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +398 -66
- package/src/oauth/key-providers.ts +16 -0
- package/src/oauth/kimi.ts +16 -2
- package/src/oauth/kiro.ts +50 -6
- package/src/oauth/local-token-detect.ts +11 -2
- package/src/oauth/log.ts +3 -1
- package/src/oauth/login-cli.ts +88 -28
- package/src/oauth/nous.ts +798 -0
- package/src/oauth/store.ts +119 -21
- package/src/oauth/token-guardian.ts +9 -3
- package/src/pi/models.ts +2 -2
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/antigravity-models.ts +521 -31
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +144 -0
- package/src/providers/context-cap.ts +22 -5
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/derive.ts +253 -6
- package/src/providers/fastwire.ts +501 -0
- package/src/providers/free-directory.ts +10 -7
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/label.ts +1 -1
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/openai-sidecar.ts +72 -4
- package/src/providers/openai-tier-startup.ts +31 -2
- package/src/providers/openai-tiers.ts +119 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +29 -0
- package/src/providers/quota.ts +1319 -38
- package/src/providers/registry.ts +1429 -111
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +42 -6
- package/src/providers/static-model-discovery.ts +86 -0
- package/src/providers/xai-responses-opt-in.ts +15 -0
- package/src/providers/xai-transport.ts +11 -4
- package/src/reasoning-effort.ts +49 -1
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +266 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +355 -0
- package/src/responses/parser.ts +220 -38
- package/src/responses/provider-continuation.ts +98 -0
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +426 -0
- package/src/responses/schema.ts +7 -1
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +565 -27
- package/src/responses/thought-signature-replay.ts +347 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/router.ts +366 -30
- package/src/routing/analytics.ts +378 -0
- package/src/routing/capability.ts +244 -0
- package/src/routing/compatibility/assemble.ts +73 -0
- package/src/routing/compatibility/behavior.ts +278 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/provider-slot.ts +56 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +495 -0
- package/src/routing/health.ts +412 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +547 -0
- package/src/routing/quota.ts +145 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +776 -0
- package/src/server/adapter-resolve.ts +2 -29
- package/src/server/auth-cors.ts +267 -46
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +130 -56
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +426 -0
- package/src/server/claude-messages.ts +159 -43
- package/src/server/direct-local-http.ts +347 -0
- package/src/server/effort-policy.ts +18 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +94 -12
- package/src/server/index.ts +865 -181
- package/src/server/lifecycle.ts +284 -13
- package/src/server/live.ts +136 -17
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/local-provider-reload-client.ts +137 -0
- package/src/server/management/agent-settings-routes.ts +398 -103
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/combo-routes.ts +62 -24
- package/src/server/management/config-routes.ts +464 -51
- package/src/server/management/context.ts +80 -2
- package/src/server/management/integration-routes.ts +498 -0
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +563 -0
- package/src/server/management/logs-usage-routes.ts +101 -32
- package/src/server/management/model-routes.ts +189 -131
- package/src/server/management/model-rows.ts +163 -0
- package/src/server/management/native-integration-routes.ts +769 -0
- package/src/server/management/oauth-account-routes.ts +80 -4
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +764 -157
- 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 +380 -0
- package/src/server/management/shared.ts +27 -11
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/storage-log-guard-routes.ts +186 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +288 -32
- package/src/server/management/system-routes.ts +77 -0
- package/src/server/management/usage-summary-cache.ts +9 -1
- package/src/server/management/vision-sidecar-options.ts +167 -0
- package/src/server/management/web-search-sidecar-options.ts +120 -0
- package/src/server/management-api.ts +115 -14
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +132 -5
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +82 -42
- package/src/server/relay.ts +236 -76
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log.ts +235 -22
- package/src/server/responses/agent-task-recovery-cache.ts +143 -0
- package/src/server/responses/agent-task-recovery.ts +465 -0
- package/src/server/responses/collaboration.ts +204 -35
- package/src/server/responses/compact.ts +442 -55
- package/src/server/responses/core.ts +2872 -331
- package/src/server/responses/empty-completion-guard.ts +276 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +79 -4
- package/src/server/responses/input-admission.ts +185 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +251 -0
- package/src/server/responses/terminal-guard.ts +26 -5
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses/ws-upstream.ts +308 -0
- package/src/server/responses-custom-tool-repair.ts +282 -0
- package/src/server/responses-item-id-repair.ts +54 -6
- package/src/server/responses-json-events.ts +90 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses-terminal-repair.ts +342 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +153 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +110 -22
- package/src/server/startup-action-control.ts +8 -1
- package/src/server/startup-health-cache.ts +19 -1
- package/src/server/system-env.ts +80 -9
- package/src/server/ws-bridge.ts +39 -38
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1111 -90
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/scanner.ts +1 -1
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +83 -9
- package/src/tray/windows.ts +43 -16
- package/src/types/accounts.ts +37 -0
- package/src/types/config.ts +845 -0
- package/src/types/provider.ts +545 -0
- package/src/types/request.ts +384 -0
- package/src/types/tools.ts +131 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +104 -1236
- package/src/update/index.ts +32 -19
- package/src/update/job.ts +442 -67
- package/src/update/notify.ts +12 -6
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +259 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +268 -16
- package/src/usage/log.ts +606 -41
- package/src/usage/summary.ts +177 -9
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/vision/backends.ts +97 -0
- package/src/vision/describe.ts +9 -3
- package/src/vision/eligibility.ts +250 -0
- package/src/vision/index.ts +238 -24
- package/src/vision/reasoning.ts +55 -0
- package/src/vision/routed-describe.ts +175 -0
- package/src/vision/timeout-bounds.ts +9 -0
- package/src/web-search/anthropic-executor.ts +13 -7
- package/src/web-search/backends.ts +108 -0
- package/src/web-search/exa-executor.ts +88 -0
- package/src/web-search/executor.ts +11 -3
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +279 -50
- package/src/web-search/parse.ts +125 -30
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/xai-executor.ts +219 -0
- package/gui/dist/assets/index-DTpMHS4F.js +0 -67
- package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
- package/src/cli/internal-dispatch.ts +0 -20
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The filesystem seam shared by the reader and the writer.
|
|
3
|
+
*
|
|
4
|
+
* It lives in WP2 rather than beside the writer because `readIntegrationState`
|
|
5
|
+
* needs it too, and a reader that disagreed with the writer about what counts
|
|
6
|
+
* as an absent file is exactly how an unreadable config gets overwritten.
|
|
7
|
+
*
|
|
8
|
+
* Design of record: devlog/_fin/260802_client_toggle_api/021 §5-6.
|
|
9
|
+
*/
|
|
10
|
+
import { mkdirSync, readFileSync, rmSync, statSync } from "node:fs";
|
|
11
|
+
import type { ConfigFormat } from "../clients/config-export";
|
|
12
|
+
import { MAX_JSON_NESTING } from "./serialize";
|
|
13
|
+
import { atomicWriteFile } from "../config";
|
|
14
|
+
import type { JournalEntry } from "./journal";
|
|
15
|
+
import type { OwnershipRecord } from "./ownership";
|
|
16
|
+
import type { IntegrationClientId } from "./registry";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A parse failure sentinel. This is deliberately ONE symbol exported from ONE
|
|
20
|
+
* module: two `Symbol("parse-failed")` calls produce different values, so a
|
|
21
|
+
* second declaration would not duplicate code — it would make every comparison
|
|
22
|
+
* against it fail, and an unparseable config would be classified `absent` and
|
|
23
|
+
* then overwritten.
|
|
24
|
+
*/
|
|
25
|
+
export const PARSE_FAILED = Symbol("parse-failed");
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* What `JSON.parse` has already discarded by the time we hold the parsed value,
|
|
29
|
+
* and a rewrite would therefore silently change. Both classes are invisible in
|
|
30
|
+
* the parsed object, which is why this scans the RAW text — same reasoning as
|
|
31
|
+
* the TOML inf/nan guard below.
|
|
32
|
+
*
|
|
33
|
+
* Numbers: `1e999` overflows to Infinity (a rewrite bakes in `null` — the merge
|
|
34
|
+
* layer's JSON clone does it even before the serializer could refuse), `1e-9999`
|
|
35
|
+
* underflows to `+0`, an integer literal may have been rounded (a rewrite then
|
|
36
|
+
* hands consumers that read JSON integers exactly — python, jq, BigInt revivers
|
|
37
|
+
* — a different value), and `-0` re-serializes as `0`. Only literals whose value
|
|
38
|
+
* actually changed are refused: `1e21`, `1e-320` or 2^54 round-trip exactly and
|
|
39
|
+
* stay usable.
|
|
40
|
+
*
|
|
41
|
+
* Duplicate members: `{"a":1,"a":2}` parses to a single `a`, so rewriting the
|
|
42
|
+
* document DELETES the earlier member. That is content loss, not the formatting
|
|
43
|
+
* normalization we promise, and this classifier is what makes the rewrite of a
|
|
44
|
+
* user-edited file reachable at all — so it fails closed here.
|
|
45
|
+
*
|
|
46
|
+
* Depth: the same pass counts container nesting against MAX_JSON_NESTING
|
|
47
|
+
* (shared with the serializer, see serialize.ts). JSON.parse handles hundreds
|
|
48
|
+
* of thousands of levels iteratively, but the downstream merge and
|
|
49
|
+
* JSON.stringify recurse — a 100KB file nested 50k deep sailed through parse
|
|
50
|
+
* and guard, then blew up serialization with a raw RangeError after a
|
|
51
|
+
* multi-GB allocation spike. A hand-written scan rather than a JSON.parse
|
|
52
|
+
* source-access reviver on purpose: the reviver walk recurses internally, so
|
|
53
|
+
* its depth limit would be an unpredictable stack size instead of this
|
|
54
|
+
* deterministic ceiling.
|
|
55
|
+
*/
|
|
56
|
+
function jsonTextSafeToRewrite(text: string): boolean {
|
|
57
|
+
/**
|
|
58
|
+
* One frame per open container; a Set of member names for objects, null for
|
|
59
|
+
* arrays. Its length is the current nesting depth.
|
|
60
|
+
*/
|
|
61
|
+
const containers: Array<Set<string> | null> = [];
|
|
62
|
+
/** The most recent string literal — the member name if a `:` follows. */
|
|
63
|
+
let lastString: string | null = null;
|
|
64
|
+
let inString = false;
|
|
65
|
+
let escaped = false;
|
|
66
|
+
let stringStart = 0;
|
|
67
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
68
|
+
const ch = text[i]!;
|
|
69
|
+
if (inString) {
|
|
70
|
+
if (escaped) escaped = false;
|
|
71
|
+
else if (ch === "\\") escaped = true;
|
|
72
|
+
else if (ch === "\"") {
|
|
73
|
+
inString = false;
|
|
74
|
+
lastString = text.slice(stringStart, i + 1);
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (ch === "\"") { inString = true; stringStart = i; continue; }
|
|
79
|
+
if (ch === "{" || ch === "[") {
|
|
80
|
+
containers.push(ch === "{" ? new Set<string>() : null);
|
|
81
|
+
if (containers.length > MAX_JSON_NESTING) return false;
|
|
82
|
+
lastString = null;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (ch === "}" || ch === "]") { containers.pop(); lastString = null; continue; }
|
|
86
|
+
if (ch === ":") {
|
|
87
|
+
const members = containers[containers.length - 1];
|
|
88
|
+
if (members && lastString !== null) {
|
|
89
|
+
/*
|
|
90
|
+
* Decoded, not raw: `"a"` and `"a"` are spellings of ONE member,
|
|
91
|
+
* and JSON.parse keeps only the last of them.
|
|
92
|
+
*/
|
|
93
|
+
let name: string;
|
|
94
|
+
try { name = JSON.parse(lastString) as string; } catch { return false; }
|
|
95
|
+
if (members.has(name)) return false;
|
|
96
|
+
members.add(name);
|
|
97
|
+
}
|
|
98
|
+
lastString = null;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (ch !== "-" && (ch < "0" || ch > "9")) continue;
|
|
102
|
+
let end = i + 1;
|
|
103
|
+
while (end < text.length && /[0-9+\-.eE]/.test(text[end]!)) end += 1;
|
|
104
|
+
const literal = text.slice(i, end);
|
|
105
|
+
i = end - 1;
|
|
106
|
+
const value = Number(literal);
|
|
107
|
+
if (!Number.isFinite(value)) return false;
|
|
108
|
+
if (value === 0) {
|
|
109
|
+
/*
|
|
110
|
+
* `-0` (re-serializes as `0`) and underflow: `1e-9999` is a nonzero
|
|
111
|
+
* value the parse already flattened to `+0`. The significand alone
|
|
112
|
+
* decides, so genuine zero spellings (`0`, `0.0`, `0e10`) stay usable.
|
|
113
|
+
*/
|
|
114
|
+
if (literal.startsWith("-") || /[1-9]/.test(literal.split(/[eE]/)[0]!)) return false;
|
|
115
|
+
}
|
|
116
|
+
/*
|
|
117
|
+
* Deliberately plain digit runs only. They are the one spelling real
|
|
118
|
+
* consumers read with exact integer semantics (python's json yields an
|
|
119
|
+
* arbitrary-precision int, jq preserves big integer literals), so baking
|
|
120
|
+
* in the rounded double changes what those consumers extract. Decimal or
|
|
121
|
+
* exponent spellings of the same value (`9007199254740993e0`, `…3.0`) are
|
|
122
|
+
* float semantics for every consumer — they round identically before and
|
|
123
|
+
* after a rewrite, and shortest-round-trip stringify preserves what any
|
|
124
|
+
* reader can observe, so refusing them would only manufacture dead ends
|
|
125
|
+
* (`1e308` is not exactly representable either, yet rewrites losslessly
|
|
126
|
+
* for every possible reader).
|
|
127
|
+
*/
|
|
128
|
+
const digits = literal[0] === "-" ? literal.slice(1) : literal;
|
|
129
|
+
if (/^[0-9]{16,}$/.test(digits) && BigInt(literal) !== BigInt(value)) return false;
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Parse a client config, tolerating absence. PARSE_FAILED on garbage. */
|
|
135
|
+
export function parseConfig(text: string | null, format: ConfigFormat): unknown | typeof PARSE_FAILED {
|
|
136
|
+
if (text === null || text.trim().length === 0) return {};
|
|
137
|
+
try {
|
|
138
|
+
switch (format) {
|
|
139
|
+
case "json": {
|
|
140
|
+
/*
|
|
141
|
+
* Scanned BEFORE parsing: the guard is text-only, and refusing first
|
|
142
|
+
* means a hostile document is never materialized — the depth ceiling
|
|
143
|
+
* would otherwise cap the rewrite only after JSON.parse had already
|
|
144
|
+
* built the 50k-deep object graph. The outcome is unchanged: invalid
|
|
145
|
+
* JSON still returns PARSE_FAILED, from the catch below.
|
|
146
|
+
*/
|
|
147
|
+
if (!jsonTextSafeToRewrite(text)) return PARSE_FAILED;
|
|
148
|
+
return JSON.parse(text);
|
|
149
|
+
}
|
|
150
|
+
case "json5": return Bun.JSON5.parse(text);
|
|
151
|
+
case "yaml": return Bun.YAML.parse(text);
|
|
152
|
+
case "toml": {
|
|
153
|
+
/*
|
|
154
|
+
* Bun's TOML parser mangles the special floats before we ever see the
|
|
155
|
+
* document: `inf` comes back as the STRING "inf", `-inf` as the number
|
|
156
|
+
* 0, and `nan` as "nan". Re-serializing that would write those
|
|
157
|
+
* corruptions back to the user's file while reporting success — a
|
|
158
|
+
* silent value change is worse than a refusal, so a document
|
|
159
|
+
* containing them is treated as one we cannot safely rewrite.
|
|
160
|
+
*
|
|
161
|
+
* Detected on the raw text, because by the time it is parsed the
|
|
162
|
+
* evidence is gone.
|
|
163
|
+
*/
|
|
164
|
+
if (/(^|[\s,[=])[-+]?(?:inf|nan)(?=[\s,\]]|$)/mi.test(text)) return PARSE_FAILED;
|
|
165
|
+
return Bun.TOML.parse(text);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
} catch {
|
|
169
|
+
return PARSE_FAILED;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export type ReadResult =
|
|
174
|
+
| { kind: "text"; text: string }
|
|
175
|
+
| { kind: "missing" }
|
|
176
|
+
| { kind: "failed"; code?: string };
|
|
177
|
+
|
|
178
|
+
export type StatKind = "file" | "dir" | "other" | "missing" | "failed";
|
|
179
|
+
|
|
180
|
+
export interface IntegrationIO {
|
|
181
|
+
/**
|
|
182
|
+
* ONLY a missing file yields `missing`. Every other failure (EACCES, EPERM,
|
|
183
|
+
* EISDIR, …) yields `failed`, which callers must treat as unsafe.
|
|
184
|
+
*/
|
|
185
|
+
readText: (path: string) => ReadResult;
|
|
186
|
+
/** `failed` is distinct from `missing` for the same reason. */
|
|
187
|
+
statKind: (path: string) => StatKind;
|
|
188
|
+
writeText: (path: string, text: string) => void;
|
|
189
|
+
removeFile: (path: string) => void;
|
|
190
|
+
mkdirp: (path: string) => void;
|
|
191
|
+
now: () => number;
|
|
192
|
+
/** Bookkeeping seams, bound to one store so a test can redirect them. */
|
|
193
|
+
appendJournal: (entry: JournalEntry) => void;
|
|
194
|
+
putRecord: (record: OwnershipRecord) => void;
|
|
195
|
+
dropRecord: (clientId: IntegrationClientId) => void;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export type TargetState =
|
|
199
|
+
| { ok: true; before: string | null }
|
|
200
|
+
| { ok: false; why: "not-regular-file" | "read-failed" };
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Read the target and classify the three failure shapes correctly.
|
|
204
|
+
*
|
|
205
|
+
* The subtle case is a `stat` that succeeds as a file and a `read` that then
|
|
206
|
+
* fails: that is a real file we cannot see, and treating it as absence is how
|
|
207
|
+
* an unreadable config gets clobbered.
|
|
208
|
+
*/
|
|
209
|
+
export function loadTarget(io: IntegrationIO, configPath: string): TargetState {
|
|
210
|
+
const kind = io.statKind(configPath);
|
|
211
|
+
if (kind === "missing") return { ok: true, before: null };
|
|
212
|
+
if (kind === "failed") return { ok: false, why: "read-failed" };
|
|
213
|
+
if (kind !== "file") return { ok: false, why: "not-regular-file" };
|
|
214
|
+
const read = io.readText(configPath);
|
|
215
|
+
if (read.kind === "text") return { ok: true, before: read.text };
|
|
216
|
+
if (read.kind === "failed") return { ok: false, why: "read-failed" };
|
|
217
|
+
// Raced deletion between the stat and the read.
|
|
218
|
+
return { ok: true, before: null };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Filesystem half of the seam, split from the store-bound half so there is one
|
|
223
|
+
* place that decides what a failed read or stat MEANS, and one place that
|
|
224
|
+
* decides WHERE bookkeeping goes.
|
|
225
|
+
*/
|
|
226
|
+
export function fileIO(): Omit<IntegrationIO, "appendJournal" | "putRecord" | "dropRecord"> {
|
|
227
|
+
return {
|
|
228
|
+
readText: path => {
|
|
229
|
+
try {
|
|
230
|
+
return { kind: "text", text: readFileSync(path, "utf8") };
|
|
231
|
+
} catch (error) {
|
|
232
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
233
|
+
return code === "ENOENT" ? { kind: "missing" } : { kind: "failed", ...(code ? { code } : {}) };
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
statKind: path => {
|
|
237
|
+
try {
|
|
238
|
+
const stats = statSync(path);
|
|
239
|
+
return stats.isFile() ? "file" : stats.isDirectory() ? "dir" : "other";
|
|
240
|
+
} catch (error) {
|
|
241
|
+
return (error as NodeJS.ErrnoException).code === "ENOENT" ? "missing" : "failed";
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
writeText: (path, text) => atomicWriteFile(path, text),
|
|
245
|
+
removeFile: path => rmSync(path, { force: true }),
|
|
246
|
+
mkdirp: path => mkdirSync(path, { recursive: true, mode: 0o700 }),
|
|
247
|
+
now: () => Date.now(),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* The full seam: filesystem behavior plus bookkeeping bound to ONE store.
|
|
253
|
+
*
|
|
254
|
+
* Kept together so the IO a writer uses and the store it journals into can
|
|
255
|
+
* never point at different roots — that split is what let an "isolated"
|
|
256
|
+
* operation mutate the real state while a test believed otherwise.
|
|
257
|
+
*/
|
|
258
|
+
export function defaultIntegrationIO(store: {
|
|
259
|
+
appendJournal: IntegrationIO["appendJournal"];
|
|
260
|
+
putRecord: IntegrationIO["putRecord"];
|
|
261
|
+
dropRecord: IntegrationIO["dropRecord"];
|
|
262
|
+
}): IntegrationIO {
|
|
263
|
+
return {
|
|
264
|
+
...fileIO(),
|
|
265
|
+
appendJournal: entry => store.appendJournal(entry),
|
|
266
|
+
putRecord: record => store.putRecord(record),
|
|
267
|
+
dropRecord: clientId => store.dropRecord(clientId),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The operation log, its snapshots, and the retention bookkeeping around them.
|
|
3
|
+
*
|
|
4
|
+
* Two ordering rules carry the safety here:
|
|
5
|
+
*
|
|
6
|
+
* 1. `appendOperation` commits the row and NOTHING else. Pruning runs after,
|
|
7
|
+
* best-effort, because a GC failure that looked like an append failure would
|
|
8
|
+
* make the writer compensate for an operation that already succeeded.
|
|
9
|
+
* 2. Snapshots can hold the user's own credentials — we copy their file
|
|
10
|
+
* verbatim — so they go through `atomicWriteFile`, which applies 0600 plus
|
|
11
|
+
* Windows ACL hardening.
|
|
12
|
+
*
|
|
13
|
+
* Design of record: devlog/_fin/260802_client_toggle_api/021 §4.
|
|
14
|
+
*/
|
|
15
|
+
import { randomUUID } from "node:crypto";
|
|
16
|
+
import { appendFileSync, existsSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
17
|
+
import { join, resolve, sep } from "node:path";
|
|
18
|
+
import { atomicWriteFile } from "../config";
|
|
19
|
+
import { ensureDir, fingerprint, integrationsDir, type OwnershipRecord } from "./ownership";
|
|
20
|
+
import { isIntegrationClientId, type IntegrationClientId } from "./registry";
|
|
21
|
+
|
|
22
|
+
export type OperationKind = "apply" | "disable" | "refresh" | "restore";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Tagged so "the file did not exist" and "the snapshot was collected" stay
|
|
26
|
+
* distinguishable. `null` conflated them, and only one of the two is
|
|
27
|
+
* recoverable — restoring an operation that created a file means deleting it.
|
|
28
|
+
*/
|
|
29
|
+
export type SnapshotRef =
|
|
30
|
+
| { kind: "none" }
|
|
31
|
+
| { kind: "stored"; relPath: string }
|
|
32
|
+
| { kind: "expired" };
|
|
33
|
+
|
|
34
|
+
export interface JournalEntry {
|
|
35
|
+
opId: string;
|
|
36
|
+
clientId: IntegrationClientId;
|
|
37
|
+
kind: OperationKind;
|
|
38
|
+
at: string;
|
|
39
|
+
configPath: string;
|
|
40
|
+
snapshot: SnapshotRef;
|
|
41
|
+
/** Fingerprint of the file AFTER this op; "" when the op left no file. */
|
|
42
|
+
resultFingerprint: string;
|
|
43
|
+
/** True when the op's result was file absence — restore then means "delete". */
|
|
44
|
+
resultAbsent: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Ownership as it stood BEFORE this operation. Restore puts this back
|
|
47
|
+
* alongside the bytes, so provenance always describes the file it came with
|
|
48
|
+
* and is never re-derived from a provider-id prefix — which would silently
|
|
49
|
+
* adopt a user's own `opencodex/...` entry.
|
|
50
|
+
*/
|
|
51
|
+
priorRecord: OwnershipRecord | null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const SNAPSHOT_RETENTION = 10;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Does the file on disk still hold what this operation left behind?
|
|
58
|
+
*
|
|
59
|
+
* The one place that answers it, because two places answered differently. An
|
|
60
|
+
* operation whose result was ABSENCE records `resultAbsent: true` and an empty
|
|
61
|
+
* `resultFingerprint`; the route compared a missing file to `""` and called it
|
|
62
|
+
* a match, while restore hashed `""` into a real digest and called the same
|
|
63
|
+
* unchanged absence a drift. So the journal offered Undo and the restore
|
|
64
|
+
* route then demanded a confirmation for edits nobody had made.
|
|
65
|
+
*
|
|
66
|
+
* `currentText` is `null` for a missing file — not `""`, which is a file that
|
|
67
|
+
* exists and is empty. The distinction is the whole point.
|
|
68
|
+
*/
|
|
69
|
+
export function matchesOperationResult(entry: JournalEntry, currentText: string | null): boolean {
|
|
70
|
+
if (entry.resultAbsent) return currentText === null;
|
|
71
|
+
if (currentText === null) return false;
|
|
72
|
+
return fingerprint(currentText) === entry.resultFingerprint;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function newOpId(): string {
|
|
76
|
+
return randomUUID();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function journalPath(dir: string): string {
|
|
80
|
+
return join(dir, "journal.jsonl");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* A snapshot file name must be ONE path component we produced, never a value
|
|
85
|
+
* that walks anywhere. `opId` comes from `randomUUID()` in normal operation,
|
|
86
|
+
* but it also arrives from a persisted journal row, and `relPath` is read
|
|
87
|
+
* straight off disk — a row carrying `../../escaped` would otherwise let
|
|
88
|
+
* capture write outside the store and read-back read an arbitrary file.
|
|
89
|
+
*/
|
|
90
|
+
function assertSafeComponent(value: string, what: string): string {
|
|
91
|
+
if (value.length === 0 || value === "." || value === ".."
|
|
92
|
+
|| value.includes("/") || value.includes("\\") || value.includes("\0")) {
|
|
93
|
+
throw new Error(`unsafe ${what}: ${JSON.stringify(value)}`);
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Resolve `relative` under `root`, refusing anything that escapes it. */
|
|
99
|
+
function containedPath(root: string, ...segments: string[]): string {
|
|
100
|
+
const target = resolve(root, ...segments);
|
|
101
|
+
const base = resolve(root);
|
|
102
|
+
if (target !== base && !target.startsWith(base + sep)) {
|
|
103
|
+
throw new Error(`path escapes the integration store: ${target}`);
|
|
104
|
+
}
|
|
105
|
+
return target;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function snapshotDir(clientId: IntegrationClientId, dir: string): string {
|
|
109
|
+
return join(dir, "snapshots", clientId);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function captureSnapshot(
|
|
113
|
+
clientId: IntegrationClientId,
|
|
114
|
+
opId: string,
|
|
115
|
+
text: string | null,
|
|
116
|
+
dir: string = integrationsDir(),
|
|
117
|
+
): SnapshotRef {
|
|
118
|
+
if (text === null) return { kind: "none" };
|
|
119
|
+
const target = containedPath(dir, "snapshots", assertSafeComponent(clientId, "clientId"), assertSafeComponent(opId, "opId"));
|
|
120
|
+
ensureDir(target);
|
|
121
|
+
atomicWriteFile(target, text);
|
|
122
|
+
return { kind: "stored", relPath: join("snapshots", clientId, opId) };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Commit the row. Pruning is post-commit and can never fail the append. */
|
|
126
|
+
export function appendOperation(entry: JournalEntry, dir: string = integrationsDir()): void {
|
|
127
|
+
ensureDir(journalPath(dir));
|
|
128
|
+
appendFileSync(journalPath(dir), `${JSON.stringify(entry)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
129
|
+
try {
|
|
130
|
+
const pruned = pruneSnapshots(entry.clientId, dir);
|
|
131
|
+
if (pruned.ok) clearPruneFailure(entry.clientId, dir);
|
|
132
|
+
else markPruneFailure(entry.clientId, pruned.error, dir);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.error(`[integrations] post-commit maintenance failed: ${String(error)}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Newest first. A torn final line (crash mid-append) is skipped, not thrown. */
|
|
139
|
+
export function listOperations(
|
|
140
|
+
clientId?: IntegrationClientId,
|
|
141
|
+
limit = 50,
|
|
142
|
+
dir: string = integrationsDir(),
|
|
143
|
+
): JournalEntry[] {
|
|
144
|
+
let raw: string;
|
|
145
|
+
try {
|
|
146
|
+
raw = readFileSync(journalPath(dir), "utf8");
|
|
147
|
+
} catch {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
const rows: JournalEntry[] = [];
|
|
151
|
+
for (const line of raw.split("\n")) {
|
|
152
|
+
if (!line.trim()) continue;
|
|
153
|
+
try {
|
|
154
|
+
const parsed = JSON.parse(line) as JournalEntry;
|
|
155
|
+
if (!clientId || parsed.clientId === clientId) rows.push(parsed);
|
|
156
|
+
} catch {
|
|
157
|
+
// Torn line from an interrupted append; the rest of the log is still good.
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return rows.reverse().slice(0, limit);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function findOperation(opId: string, dir: string = integrationsDir()): JournalEntry | null {
|
|
164
|
+
return listOperations(undefined, Number.MAX_SAFE_INTEGER, dir).find(row => row.opId === opId) ?? null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Resolves the tag against what is actually on disk now. */
|
|
168
|
+
export function readSnapshot(
|
|
169
|
+
entry: JournalEntry,
|
|
170
|
+
dir: string = integrationsDir(),
|
|
171
|
+
): { kind: "none" } | { kind: "stored"; text: string; path: string } | { kind: "expired" } {
|
|
172
|
+
if (entry.snapshot.kind === "none") return { kind: "none" };
|
|
173
|
+
if (entry.snapshot.kind === "expired") return { kind: "expired" };
|
|
174
|
+
// Derive the path from validated identifiers rather than trusting the
|
|
175
|
+
// persisted relPath, then verify containment either way.
|
|
176
|
+
const abs = containedPath(dir, entry.snapshot.relPath);
|
|
177
|
+
if (!existsSync(abs)) return { kind: "expired" };
|
|
178
|
+
return { kind: "stored", text: readFileSync(abs, "utf8"), path: abs };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Snapshot files retained right now — the witness for `retentionDegraded`.
|
|
183
|
+
*
|
|
184
|
+
* `null` means "cannot inspect", which is NOT the same as zero: reporting an
|
|
185
|
+
* unreadable snapshot directory as a healthy empty one would hide the
|
|
186
|
+
* unbounded, credential-bearing pile this field exists to disclose.
|
|
187
|
+
*/
|
|
188
|
+
export function countSnapshots(
|
|
189
|
+
clientId: IntegrationClientId,
|
|
190
|
+
dir: string = integrationsDir(),
|
|
191
|
+
): number | null {
|
|
192
|
+
try {
|
|
193
|
+
return readdirSync(snapshotDir(clientId, dir)).length;
|
|
194
|
+
} catch (error) {
|
|
195
|
+
return (error as NodeJS.ErrnoException).code === "ENOENT" ? 0 : null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Keep the newest N snapshot files per client; journal rows always survive.
|
|
201
|
+
* Structured rather than throwing or swallowing: a swallowed failure would let
|
|
202
|
+
* credential-bearing snapshots pile up while every operation reported success.
|
|
203
|
+
*/
|
|
204
|
+
export function pruneSnapshots(
|
|
205
|
+
clientId: IntegrationClientId,
|
|
206
|
+
dir: string = integrationsDir(),
|
|
207
|
+
): { ok: true } | { ok: false; error: string } {
|
|
208
|
+
/*
|
|
209
|
+
* Filter to rows that HAVE a snapshot first, then take the newest N.
|
|
210
|
+
*
|
|
211
|
+
* Taking the newest N operations and filtering afterwards counted rows that
|
|
212
|
+
* never stored anything: an apply-to-absent records `snapshot: none`, so a
|
|
213
|
+
* client whose history alternates stored and none kept only half the backups
|
|
214
|
+
* the contract promises. The docs say ten backups per client, and this is
|
|
215
|
+
* the code that has to make that true.
|
|
216
|
+
*/
|
|
217
|
+
const keep = new Set(
|
|
218
|
+
listOperations(clientId, Number.MAX_SAFE_INTEGER, dir)
|
|
219
|
+
.filter(row => row.snapshot.kind === "stored")
|
|
220
|
+
.slice(0, SNAPSHOT_RETENTION)
|
|
221
|
+
.map(row => row.opId),
|
|
222
|
+
);
|
|
223
|
+
let names: string[];
|
|
224
|
+
try {
|
|
225
|
+
names = readdirSync(snapshotDir(clientId, dir));
|
|
226
|
+
} catch (error) {
|
|
227
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
228
|
+
if (code === "ENOENT") return { ok: true };
|
|
229
|
+
return { ok: false, error: String(error) };
|
|
230
|
+
}
|
|
231
|
+
for (const name of names) {
|
|
232
|
+
if (keep.has(name)) continue;
|
|
233
|
+
try {
|
|
234
|
+
rmSync(join(snapshotDir(clientId, dir), name), { force: true });
|
|
235
|
+
} catch (error) {
|
|
236
|
+
return { ok: false, error: String(error) };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return { ok: true };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export interface MaintenanceState {
|
|
243
|
+
pruneFailures: Partial<Record<IntegrationClientId, { at: string; error: string }>>;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function maintenancePath(dir: string): string {
|
|
247
|
+
return join(dir, "maintenance.json");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Validates rather than casts: `{}` parses fine and would leave `pruneFailures`
|
|
252
|
+
* undefined, so the next mark/clear would throw AFTER the journal row was
|
|
253
|
+
* committed — producing the phantom row the write ordering exists to prevent.
|
|
254
|
+
*/
|
|
255
|
+
export function readMaintenance(dir: string = integrationsDir()): MaintenanceState {
|
|
256
|
+
try {
|
|
257
|
+
const parsed: unknown = JSON.parse(readFileSync(maintenancePath(dir), "utf8"));
|
|
258
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
259
|
+
const raw = (parsed as { pruneFailures?: unknown }).pruneFailures;
|
|
260
|
+
const failures: MaintenanceState["pruneFailures"] = {};
|
|
261
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
262
|
+
for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {
|
|
263
|
+
if (!isIntegrationClientId(key)) continue;
|
|
264
|
+
if (!value || typeof value !== "object") continue;
|
|
265
|
+
const { at, error } = value as { at?: unknown; error?: unknown };
|
|
266
|
+
if (typeof at !== "string" || typeof error !== "string") continue;
|
|
267
|
+
failures[key] = { at, error };
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return { pruneFailures: failures };
|
|
271
|
+
}
|
|
272
|
+
} catch {
|
|
273
|
+
// Absent or corrupt: no pending retries known.
|
|
274
|
+
}
|
|
275
|
+
return { pruneFailures: {} };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function writeMaintenance(state: MaintenanceState, dir: string): void {
|
|
279
|
+
try {
|
|
280
|
+
ensureDir(maintenancePath(dir));
|
|
281
|
+
atomicWriteFile(maintenancePath(dir), `${JSON.stringify(state, null, 2)}\n`);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
// The marker is an optimization for scheduling retries. `retentionDegraded`
|
|
284
|
+
// is derived from the snapshot count, so losing it costs a retry, not the
|
|
285
|
+
// claim — a durable promise must not depend on a write that can fail.
|
|
286
|
+
console.error(`[integrations] could not record maintenance state: ${String(error)}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function markPruneFailure(
|
|
291
|
+
clientId: IntegrationClientId,
|
|
292
|
+
error: string,
|
|
293
|
+
dir: string = integrationsDir(),
|
|
294
|
+
): void {
|
|
295
|
+
const state = readMaintenance(dir);
|
|
296
|
+
state.pruneFailures[clientId] = { at: new Date().toISOString(), error };
|
|
297
|
+
writeMaintenance(state, dir);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function clearPruneFailure(
|
|
301
|
+
clientId: IntegrationClientId,
|
|
302
|
+
dir: string = integrationsDir(),
|
|
303
|
+
): void {
|
|
304
|
+
const state = readMaintenance(dir);
|
|
305
|
+
if (!(clientId in state.pruneFailures)) return;
|
|
306
|
+
delete state.pruneFailures[clientId];
|
|
307
|
+
writeMaintenance(state, dir);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Re-attempt every marked client. */
|
|
311
|
+
export function retryPendingPrunes(dir: string = integrationsDir()): void {
|
|
312
|
+
for (const clientId of Object.keys(readMaintenance(dir).pruneFailures) as IntegrationClientId[]) {
|
|
313
|
+
if (pruneSnapshots(clientId, dir).ok) clearPruneFailure(clientId, dir);
|
|
314
|
+
}
|
|
315
|
+
}
|