@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
package/src/update/notify.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import { createInterface } from "node:readline/promises";
|
|
5
5
|
import { atomicWriteFile, getConfigDir } from "../config";
|
|
6
6
|
import { hasStarPromptRun } from "../cli/star-prompt";
|
|
7
|
+
import { selfLaunchArgv } from "../lib/self-launch-argv";
|
|
7
8
|
import {
|
|
8
9
|
type Channel,
|
|
9
10
|
currentVersion,
|
|
@@ -83,8 +84,12 @@ function gt(a: number[], b: number[]): boolean {
|
|
|
83
84
|
|
|
84
85
|
/**
|
|
85
86
|
* Channel-aware "is latest newer than current?".
|
|
86
|
-
* - latest channel: compare maj.min.pat only;
|
|
87
|
-
* (parity with codex-rs), so stable users are not pushed onto
|
|
87
|
+
* - latest channel: compare maj.min.pat only; prerelease TARGETS are never
|
|
88
|
+
* "newer" (parity with codex-rs), so stable users are not pushed onto
|
|
89
|
+
* previews. An installed preview CURRENT compares by its maj.min.pat core,
|
|
90
|
+
* so a stable release with a strictly higher base is offered (same base is
|
|
91
|
+
* content-lateral promotion and stays not-newer, mirroring the preview
|
|
92
|
+
* channel's O3 rule).
|
|
88
93
|
* - preview channel: preview-vs-preview compares the trailing -preview.N; a
|
|
89
94
|
* stable release with a strictly higher base counts as newer (O3), while a
|
|
90
95
|
* stable release with the same base as the current preview does not.
|
|
@@ -92,7 +97,7 @@ function gt(a: number[], b: number[]): boolean {
|
|
|
92
97
|
export function isNewer(latest: string, current: string, channel: Channel): boolean {
|
|
93
98
|
if (channel === "latest") {
|
|
94
99
|
const l = parseStable(latest);
|
|
95
|
-
const c = parseStable(current);
|
|
100
|
+
const c = parseStable(current) ?? parsePreview(current)?.slice(0, 3);
|
|
96
101
|
if (!l || !c) return false;
|
|
97
102
|
return gt(l, c);
|
|
98
103
|
}
|
|
@@ -162,9 +167,10 @@ function cacheIsStale(cache: VersionCache | null): boolean {
|
|
|
162
167
|
export function triggerBackgroundRefreshIfStale(channel: Channel, cache: VersionCache | null): void {
|
|
163
168
|
if (!cacheIsStale(cache)) return;
|
|
164
169
|
try {
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
170
|
+
const commandArgs = ["__refresh-version", channel];
|
|
171
|
+
const args = selfLaunchArgv(commandArgs);
|
|
172
|
+
if (args.length > commandArgs.length && (!args[0] || !existsSync(args[0]))) return;
|
|
173
|
+
const child = spawn(process.execPath, args, {
|
|
168
174
|
detached: true,
|
|
169
175
|
stdio: "ignore",
|
|
170
176
|
windowsHide: true,
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { spawnSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
export type NpmCachePreflightReason =
|
|
4
|
+
| "cache_accessible"
|
|
5
|
+
| "cache_entry_foreign_owner"
|
|
6
|
+
| "cache_entry_inaccessible"
|
|
7
|
+
| "cache_path_malformed"
|
|
8
|
+
| "inspection_incomplete"
|
|
9
|
+
| "npm_config_failed"
|
|
10
|
+
| "npm_unavailable"
|
|
11
|
+
| "windows_skip"
|
|
12
|
+
| "worker_failed"
|
|
13
|
+
| "worker_output_malformed"
|
|
14
|
+
| "worker_timeout";
|
|
15
|
+
|
|
16
|
+
export interface NpmCachePreflightResult {
|
|
17
|
+
ok: boolean;
|
|
18
|
+
reason: NpmCachePreflightReason;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface NpmCacheInspectionOptions {
|
|
22
|
+
expectedUid?: number;
|
|
23
|
+
maxDepth?: number;
|
|
24
|
+
maxEntries?: number;
|
|
25
|
+
nowMs?: () => number;
|
|
26
|
+
/** Test seam: resolve a symlinked cache root. Defaults to realpathSync. */
|
|
27
|
+
realpathFn?: (path: string) => string;
|
|
28
|
+
/** Test seam: resolve an entry's owner uid. Defaults to the lstat result. */
|
|
29
|
+
uidOf?: (path: string, stat: { uid: number }) => number;
|
|
30
|
+
timeoutMs?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface NpmCachePreflightOptions {
|
|
34
|
+
env?: NodeJS.ProcessEnv;
|
|
35
|
+
execPath?: string;
|
|
36
|
+
platform?: NodeJS.Platform;
|
|
37
|
+
spawnSyncFn?: typeof spawnSync;
|
|
38
|
+
timeoutMs?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function inspectNpmCacheDirectory(
|
|
42
|
+
cachePath: string,
|
|
43
|
+
options?: NpmCacheInspectionOptions,
|
|
44
|
+
): NpmCachePreflightResult;
|
|
45
|
+
|
|
46
|
+
export function runNpmCachePreflight(options?: NpmCachePreflightOptions): NpmCachePreflightResult;
|
|
47
|
+
export function npmCachePreflightFailureMessage(reason: NpmCachePreflightReason): string;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { lstatSync, readdirSync, realpathSync } from "node:fs";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { isAbsolute, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { npmInvocation } from "./npm-invocation.mjs";
|
|
6
|
+
|
|
7
|
+
const WORKER_ARG = "--ocx-npm-cache-preflight-worker";
|
|
8
|
+
const PROTOCOL_VERSION = 1;
|
|
9
|
+
const WORKER_TIMEOUT_MS = 10_000;
|
|
10
|
+
const NPM_CONFIG_TIMEOUT_MS = 5_000;
|
|
11
|
+
const INSPECTION_TIMEOUT_MS = 7_500;
|
|
12
|
+
const MAX_ENTRIES = 100_000;
|
|
13
|
+
const MAX_DEPTH = 64;
|
|
14
|
+
|
|
15
|
+
const RESULT_REASONS = new Set([
|
|
16
|
+
"cache_accessible",
|
|
17
|
+
"cache_entry_foreign_owner",
|
|
18
|
+
"cache_entry_inaccessible",
|
|
19
|
+
"cache_path_malformed",
|
|
20
|
+
"inspection_incomplete",
|
|
21
|
+
"npm_config_failed",
|
|
22
|
+
"npm_unavailable",
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
function inaccessibleByMode(stat) {
|
|
26
|
+
if (stat.isSymbolicLink()) return false;
|
|
27
|
+
const ownerBits = stat.mode & 0o700;
|
|
28
|
+
if (stat.isDirectory()) return (ownerBits & 0o700) !== 0o700;
|
|
29
|
+
return (ownerBits & 0o400) === 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Inspect an existing Unix npm cache without following symlinks. The limits are
|
|
34
|
+
* deliberately part of the result contract: an incomplete inspection cannot prove
|
|
35
|
+
* that replacing the live package will succeed.
|
|
36
|
+
*/
|
|
37
|
+
export function inspectNpmCacheDirectory(cachePath, options = {}) {
|
|
38
|
+
const expectedUid = options.expectedUid ?? process.getuid?.();
|
|
39
|
+
const deadline = (options.nowMs ?? Date.now)() + (options.timeoutMs ?? INSPECTION_TIMEOUT_MS);
|
|
40
|
+
const maxEntries = options.maxEntries ?? MAX_ENTRIES;
|
|
41
|
+
const maxDepth = options.maxDepth ?? MAX_DEPTH;
|
|
42
|
+
const nowMs = options.nowMs ?? Date.now;
|
|
43
|
+
// Injected uid seam. A test cannot create a genuinely foreign-owned file without a second
|
|
44
|
+
// account, and without this the symlink-before-ownership rule cannot be pinned: `!isDirectory`
|
|
45
|
+
// skips a link anyway, so removing the rule leaves every assertion green.
|
|
46
|
+
const uidOf = options.uidOf ?? ((_path, stat) => stat.uid);
|
|
47
|
+
const stack = [{ path: cachePath, depth: 0 }];
|
|
48
|
+
let inspected = 0;
|
|
49
|
+
let rootResolved = false;
|
|
50
|
+
|
|
51
|
+
while (stack.length > 0) {
|
|
52
|
+
// Budget exhausted is NOT a failure. A mature npm cache legitimately holds hundreds of
|
|
53
|
+
// thousands of entries — this machine's has ~256k — and treating "we ran out of time to
|
|
54
|
+
// look" as "your cache is broken" would block updates for ordinary users, which is worse
|
|
55
|
+
// than the bug this preflight exists to prevent. We looked at a bounded prefix, found
|
|
56
|
+
// nothing wrong, and let the update proceed.
|
|
57
|
+
if (inspected >= maxEntries || nowMs() > deadline) {
|
|
58
|
+
return { ok: true, reason: "inspection_incomplete" };
|
|
59
|
+
}
|
|
60
|
+
const current = stack.pop();
|
|
61
|
+
let stat;
|
|
62
|
+
try {
|
|
63
|
+
stat = lstatSync(current.path);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (current.depth === 0 && error?.code === "ENOENT") {
|
|
66
|
+
return { ok: true, reason: "cache_accessible" };
|
|
67
|
+
}
|
|
68
|
+
return { ok: false, reason: "cache_entry_inaccessible" };
|
|
69
|
+
}
|
|
70
|
+
inspected += 1;
|
|
71
|
+
|
|
72
|
+
// A symlinked cache ROOT used to be rejected outright, but pointing ~/.npm at another volume
|
|
73
|
+
// is ordinary npm configuration, and blocking those users would be the same false-positive
|
|
74
|
+
// failure this preflight exists to avoid. Resolve the root once and inspect the target;
|
|
75
|
+
// only an unresolvable root is a real problem. Nested links are still never followed.
|
|
76
|
+
if (current.depth === 0 && stat.isSymbolicLink()) {
|
|
77
|
+
// Resolve exactly once. realpath already collapses a chain, so a second pass would only
|
|
78
|
+
// happen if the target is itself reported as a link — treat that as unresolvable rather
|
|
79
|
+
// than looping.
|
|
80
|
+
if (rootResolved) return { ok: false, reason: "cache_entry_inaccessible" };
|
|
81
|
+
rootResolved = true;
|
|
82
|
+
let resolved;
|
|
83
|
+
try {
|
|
84
|
+
resolved = (options.realpathFn ?? realpathSync)(current.path);
|
|
85
|
+
} catch {
|
|
86
|
+
return { ok: false, reason: "cache_entry_inaccessible" };
|
|
87
|
+
}
|
|
88
|
+
stack.push({ path: resolved, depth: 0 });
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
// A nested symlink is not. npm creates them constantly below _npx, node_modules and .bin,
|
|
92
|
+
// and we never follow them — so its owner is irrelevant and must not abort the update.
|
|
93
|
+
// This has to come BEFORE the ownership check: a foreign-owned but never-followed link is
|
|
94
|
+
// exactly the false positive that made the previous attempt at this feature unusable.
|
|
95
|
+
if (stat.isSymbolicLink()) continue;
|
|
96
|
+
|
|
97
|
+
if (expectedUid !== undefined && uidOf(current.path, stat) !== expectedUid) {
|
|
98
|
+
return { ok: false, reason: "cache_entry_foreign_owner" };
|
|
99
|
+
}
|
|
100
|
+
if (inaccessibleByMode(stat)) {
|
|
101
|
+
return { ok: false, reason: "cache_entry_inaccessible" };
|
|
102
|
+
}
|
|
103
|
+
if (!stat.isDirectory()) continue;
|
|
104
|
+
// Same reasoning as the entry budget: too deep to finish is not evidence of a bad cache.
|
|
105
|
+
if (current.depth >= maxDepth) return { ok: true, reason: "inspection_incomplete" };
|
|
106
|
+
|
|
107
|
+
let entries;
|
|
108
|
+
try {
|
|
109
|
+
entries = readdirSync(current.path, { withFileTypes: true });
|
|
110
|
+
} catch {
|
|
111
|
+
return { ok: false, reason: "cache_entry_inaccessible" };
|
|
112
|
+
}
|
|
113
|
+
for (const entry of entries) {
|
|
114
|
+
stack.push({ path: resolve(current.path, entry.name), depth: current.depth + 1 });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { ok: true, reason: "cache_accessible" };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function workerResult() {
|
|
122
|
+
const invocation = npmInvocation(["config", "get", "cache"]);
|
|
123
|
+
if (!invocation) return { ok: false, reason: "npm_unavailable" };
|
|
124
|
+
const npm = spawnSync(invocation.file, invocation.args, {
|
|
125
|
+
encoding: "utf8",
|
|
126
|
+
timeout: NPM_CONFIG_TIMEOUT_MS,
|
|
127
|
+
windowsHide: true,
|
|
128
|
+
...invocation.options,
|
|
129
|
+
});
|
|
130
|
+
if (npm.status !== 0) return { ok: false, reason: "npm_config_failed" };
|
|
131
|
+
|
|
132
|
+
const output = typeof npm.stdout === "string" ? npm.stdout.trim() : "";
|
|
133
|
+
if (!output || output.length > 4096 || output.includes("\0") || /[\r\n]/.test(output) || !isAbsolute(output)) {
|
|
134
|
+
return { ok: false, reason: "cache_path_malformed" };
|
|
135
|
+
}
|
|
136
|
+
return inspectNpmCacheDirectory(output);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Reasons that legitimately accompany `ok: true`. The parser below cross-checks the flag against
|
|
140
|
+
// this set so a worker cannot claim success with a failure reason (or the reverse). It is a SET,
|
|
141
|
+
// not a single value: a bounded inspection that ran out of budget without finding a problem is a
|
|
142
|
+
// pass, and hardcoding `cache_accessible` here silently rejected exactly that — the pass never
|
|
143
|
+
// reached the caller and every large cache still failed, as `worker_output_malformed`.
|
|
144
|
+
const OK_REASONS = new Set([
|
|
145
|
+
"cache_accessible",
|
|
146
|
+
"inspection_incomplete",
|
|
147
|
+
"windows_skip",
|
|
148
|
+
]);
|
|
149
|
+
|
|
150
|
+
function parseWorkerOutput(stdout) {
|
|
151
|
+
if (typeof stdout !== "string" || stdout.length > 1024) return null;
|
|
152
|
+
try {
|
|
153
|
+
const parsed = JSON.parse(stdout);
|
|
154
|
+
if (!parsed || parsed.protocol !== PROTOCOL_VERSION || typeof parsed.ok !== "boolean") return null;
|
|
155
|
+
if (typeof parsed.reason !== "string" || !RESULT_REASONS.has(parsed.reason)) return null;
|
|
156
|
+
if (parsed.ok !== OK_REASONS.has(parsed.reason)) return null;
|
|
157
|
+
if (Object.keys(parsed).sort().join(",") !== "ok,protocol,reason") return null;
|
|
158
|
+
return { ok: parsed.ok, reason: parsed.reason };
|
|
159
|
+
} catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Run the bounded cache inspection in an isolated, synchronously-timeboxed worker. */
|
|
165
|
+
export function runNpmCachePreflight(options = {}) {
|
|
166
|
+
if ((options.platform ?? process.platform) === "win32") {
|
|
167
|
+
return { ok: true, reason: "windows_skip" };
|
|
168
|
+
}
|
|
169
|
+
const spawn = options.spawnSyncFn ?? spawnSync;
|
|
170
|
+
const result = spawn(
|
|
171
|
+
options.execPath ?? process.execPath,
|
|
172
|
+
[fileURLToPath(import.meta.url), WORKER_ARG],
|
|
173
|
+
{
|
|
174
|
+
encoding: "utf8",
|
|
175
|
+
timeout: options.timeoutMs ?? WORKER_TIMEOUT_MS,
|
|
176
|
+
windowsHide: true,
|
|
177
|
+
env: options.env ?? process.env,
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
if (result.status === null) return { ok: false, reason: "worker_timeout" };
|
|
181
|
+
if (result.status !== 0) return { ok: false, reason: "worker_failed" };
|
|
182
|
+
return parseWorkerOutput(result.stdout) ?? { ok: false, reason: "worker_output_malformed" };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Fixed operator guidance; worker/npm output is intentionally never interpolated. */
|
|
186
|
+
export function npmCachePreflightFailureMessage(reason) {
|
|
187
|
+
return `npm cache access pre-flight failed (${reason}); fix cache ownership and permissions, then retry`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const isWorker = process.argv[1]
|
|
191
|
+
&& resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
|
192
|
+
&& process.argv[2] === WORKER_ARG;
|
|
193
|
+
if (isWorker) {
|
|
194
|
+
let result;
|
|
195
|
+
try {
|
|
196
|
+
result = workerResult();
|
|
197
|
+
} catch {
|
|
198
|
+
result = { ok: false, reason: "cache_entry_inaccessible" };
|
|
199
|
+
}
|
|
200
|
+
process.stdout.write(JSON.stringify({ protocol: PROTOCOL_VERSION, ...result }));
|
|
201
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type InstallTreeVerification = { ok: boolean; failures: string[] };
|
|
2
|
+
export function verifyInstallTree(packageDir: string, expectedVersion?: string): InstallTreeVerification;
|
|
3
|
+
export function bootRestoreProbe(
|
|
4
|
+
packageDir: string,
|
|
5
|
+
deps?: { rename?: (from: string, to: string) => void },
|
|
6
|
+
): { action: "none" | "reaped" | "restored" | "failed"; count?: number; from?: string; error?: string };
|
|
7
|
+
export function transactionalNpmUpdate(args: {
|
|
8
|
+
packageDir: string;
|
|
9
|
+
pkgName: string;
|
|
10
|
+
targetVersion?: string;
|
|
11
|
+
tag: string;
|
|
12
|
+
runNpm: (args: string[]) => { status: number | null };
|
|
13
|
+
log?: (line: string) => void;
|
|
14
|
+
deps?: { rename?: (from: string, to: string) => void };
|
|
15
|
+
}): {
|
|
16
|
+
ok: boolean;
|
|
17
|
+
phase: "stage" | "verify" | "swap-backup" | "swap-live" | "post-verify" | "double-fault" | "done";
|
|
18
|
+
error?: string;
|
|
19
|
+
rolledBack?: boolean;
|
|
20
|
+
backup?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transactional npm self-update: stage -> verify -> swap -> rollback (#1942 / #1849).
|
|
3
|
+
*
|
|
4
|
+
* The legacy path ran `npm install -g` straight into the live global tree, so a
|
|
5
|
+
* failure after npm removed the old files left a file-less package skeleton with no
|
|
6
|
+
* recovery. This module stages the new version into a SIBLING directory of the live
|
|
7
|
+
* package (same volume, so directory renames are atomic-ish and never cross devices),
|
|
8
|
+
* verifies the staged tree with a manifest before anything live is touched, then swaps
|
|
9
|
+
* live -> backup -> stage-into-live with a reverse-rename rollback on failure and a
|
|
10
|
+
* recovery marker on double fault.
|
|
11
|
+
*
|
|
12
|
+
* Layout (siblings, never children — a child would travel WITH the live rename and the
|
|
13
|
+
* live dir cannot move into its own subtree):
|
|
14
|
+
* <scopeDir>/opencodex live package
|
|
15
|
+
* <scopeDir>/.ocx-staging-<ts>/ npm --prefix root (contains node_modules/...)
|
|
16
|
+
* <scopeDir>/.ocx-backup-<ts>/opencodex previous live tree during/after the swap
|
|
17
|
+
* <scopeDir>/.ocx-recovery.json double-fault marker with a one-line restore
|
|
18
|
+
*/
|
|
19
|
+
import { spawnSync } from "node:child_process";
|
|
20
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
21
|
+
import { dirname, join } from "node:path";
|
|
22
|
+
|
|
23
|
+
/** Verification manifest for a staged (or live) package tree. */
|
|
24
|
+
export function verifyInstallTree(packageDir, expectedVersion) {
|
|
25
|
+
const failures = [];
|
|
26
|
+
let pkg;
|
|
27
|
+
try {
|
|
28
|
+
pkg = JSON.parse(readFileSync(join(packageDir, "package.json"), "utf8"));
|
|
29
|
+
} catch (error) {
|
|
30
|
+
return { ok: false, failures: ["package.json unreadable: " + (error?.message ?? String(error))] };
|
|
31
|
+
}
|
|
32
|
+
if (expectedVersion && pkg.version !== expectedVersion) {
|
|
33
|
+
failures.push("package.json version " + pkg.version + " != expected " + expectedVersion);
|
|
34
|
+
}
|
|
35
|
+
const launcher = join(packageDir, "bin", "ocx.mjs");
|
|
36
|
+
try {
|
|
37
|
+
const st = statSync(launcher);
|
|
38
|
+
if (!st.isFile() || st.size < 1024) failures.push("bin/ocx.mjs missing or truncated");
|
|
39
|
+
} catch {
|
|
40
|
+
failures.push("bin/ocx.mjs absent");
|
|
41
|
+
}
|
|
42
|
+
// The bundled Bun binary is the load-bearing artifact: without it the launcher exits
|
|
43
|
+
// before serving anything, and a boot probe that called this tree healthy would reap
|
|
44
|
+
// the only backup (review High 3). Size-gate the real binary, not just its package.json.
|
|
45
|
+
const bunPkgDir = join(packageDir, "node_modules", "bun");
|
|
46
|
+
if (existsSync(bunPkgDir)) {
|
|
47
|
+
const bunBinary = findLargestFile(bunPkgDir);
|
|
48
|
+
if (!bunBinary || bunBinary.size < 10 * 1024 * 1024) {
|
|
49
|
+
failures.push("bundled Bun binary missing or truncated (< 10MB)");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Sentinel direct deps: each must have an intact package.json. The bundled Bun dep is
|
|
53
|
+
// the load-bearing one — without it the launcher cannot start the proxy at all.
|
|
54
|
+
const deps = Object.keys(pkg.dependencies ?? {});
|
|
55
|
+
const sentinels = deps.filter(name => name === "bun" || name === "zod").length > 0
|
|
56
|
+
? deps.filter(name => name === "bun" || name === "zod")
|
|
57
|
+
: deps.slice(0, 2);
|
|
58
|
+
for (const name of sentinels) {
|
|
59
|
+
const depPkg = join(packageDir, "node_modules", ...name.split("/"), "package.json");
|
|
60
|
+
if (!existsSync(depPkg)) failures.push("sentinel dependency missing: " + name);
|
|
61
|
+
}
|
|
62
|
+
return failures.length === 0 ? { ok: true, failures: [] } : { ok: false, failures };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function stampedName(prefix) {
|
|
66
|
+
return prefix + "-" + new Date().toISOString().replace(/[:.]/g, "-");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Largest regular file under a directory tree (bounded depth) — locates the Bun binary. */
|
|
70
|
+
function findLargestFile(root, depth = 3) {
|
|
71
|
+
let best;
|
|
72
|
+
let names = [];
|
|
73
|
+
try { names = readdirSync(root); } catch { return undefined; }
|
|
74
|
+
for (const name of names) {
|
|
75
|
+
const full = join(root, name);
|
|
76
|
+
let st;
|
|
77
|
+
try { st = statSync(full); } catch { continue; }
|
|
78
|
+
if (st.isFile()) {
|
|
79
|
+
if (!best || st.size > best.size) best = { path: full, size: st.size };
|
|
80
|
+
} else if (st.isDirectory() && depth > 0) {
|
|
81
|
+
const sub = findLargestFile(full, depth - 1);
|
|
82
|
+
if (sub && (!best || sub.size > best.size)) best = sub;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return best;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Bounded Windows-class rename retry: EPERM/EBUSY/EACCES from AV/indexers clears in ms. */
|
|
89
|
+
function renameWithRetry(rename, from, to, attempts = 5) {
|
|
90
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
91
|
+
try {
|
|
92
|
+
rename(from, to);
|
|
93
|
+
return;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
const code = error?.code;
|
|
96
|
+
const retryable = code === "EPERM" || code === "EBUSY" || code === "EACCES";
|
|
97
|
+
if (!retryable || attempt >= attempts - 1) throw error;
|
|
98
|
+
// Synchronous bounded backoff (launcher context has no async loop here).
|
|
99
|
+
const until = Date.now() + 100 * (attempt + 1);
|
|
100
|
+
while (Date.now() < until) { /* spin briefly; total worst case ~1.5s */ }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function recoveryMarkerPath(scopeDir) {
|
|
106
|
+
return join(scopeDir, ".ocx-recovery.json");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Startup probe: restore a backup when the live tree is broken (D4 power-loss rows). */
|
|
110
|
+
export function bootRestoreProbe(packageDir, deps = {}) {
|
|
111
|
+
const rename = deps.rename ?? renameSync;
|
|
112
|
+
const scopeDir = dirname(packageDir);
|
|
113
|
+
let backups = [];
|
|
114
|
+
try {
|
|
115
|
+
backups = readdirSync(scopeDir).filter(name => name.startsWith(".ocx-backup-")).sort();
|
|
116
|
+
} catch {
|
|
117
|
+
return { action: "none" };
|
|
118
|
+
}
|
|
119
|
+
if (backups.length === 0) return { action: "none" };
|
|
120
|
+
const liveOk = existsSync(join(packageDir, "package.json"))
|
|
121
|
+
&& verifyInstallTree(packageDir).ok;
|
|
122
|
+
const newestBackup = join(scopeDir, backups[backups.length - 1], "opencodex");
|
|
123
|
+
if (liveOk) {
|
|
124
|
+
// Live is healthy: the backups are leftovers from a completed swap. Reap them.
|
|
125
|
+
for (const name of backups) {
|
|
126
|
+
try { rmSync(join(scopeDir, name), { recursive: true, force: true }); } catch { /* keep */ }
|
|
127
|
+
}
|
|
128
|
+
try { rmSync(recoveryMarkerPath(scopeDir), { force: true }); } catch { /* keep */ }
|
|
129
|
+
return { action: "reaped", count: backups.length };
|
|
130
|
+
}
|
|
131
|
+
if (!existsSync(join(newestBackup, "package.json"))) return { action: "none" };
|
|
132
|
+
try {
|
|
133
|
+
try { rmSync(packageDir, { recursive: true, force: true }); } catch { /* may not exist */ }
|
|
134
|
+
rename(newestBackup, packageDir);
|
|
135
|
+
return { action: "restored", from: newestBackup };
|
|
136
|
+
} catch (error) {
|
|
137
|
+
return { action: "failed", error: error?.message ?? String(error) };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Run the transactional update. `runNpm(args, opts)` is injected so the caller keeps
|
|
143
|
+
* its hardened npm resolution (npm-invocation.mjs) and logging.
|
|
144
|
+
*/
|
|
145
|
+
export function transactionalNpmUpdate({
|
|
146
|
+
packageDir,
|
|
147
|
+
pkgName,
|
|
148
|
+
targetVersion,
|
|
149
|
+
tag,
|
|
150
|
+
runNpm,
|
|
151
|
+
log = () => {},
|
|
152
|
+
deps = {},
|
|
153
|
+
}) {
|
|
154
|
+
const rename = deps.rename ?? renameSync;
|
|
155
|
+
const scopeDir = dirname(packageDir);
|
|
156
|
+
const stageRoot = join(scopeDir, stampedName(".ocx-staging"));
|
|
157
|
+
// GLOBAL-style staging (-g --prefix): npm nests the package's dependencies INSIDE the
|
|
158
|
+
// package dir, exactly like the live global tree this stage will replace. A local-style
|
|
159
|
+
// install would hoist bun/zod to stageRoot/node_modules — siblings that the swap would
|
|
160
|
+
// leave behind, shipping a dependency-less live tree (release-audit blocker).
|
|
161
|
+
// Layout: <stageRoot>/lib/node_modules/<pkg> on POSIX, <stageRoot>/node_modules/<pkg>
|
|
162
|
+
// on Windows.
|
|
163
|
+
const stagedCandidates = [
|
|
164
|
+
join(stageRoot, "lib", "node_modules", ...pkgName.split("/")),
|
|
165
|
+
join(stageRoot, "node_modules", ...pkgName.split("/")),
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
// D1: stage to the side. --prefix keeps npm entirely inside stageRoot; the live tree
|
|
169
|
+
// and the npm bin shims are untouched until the swap. A failure HERE (mkdir EACCES,
|
|
170
|
+
// ENOSPC) must NOT fall back to the destructive legacy install (review High 4): the
|
|
171
|
+
// caller sees a normal phase failure with live untouched.
|
|
172
|
+
try {
|
|
173
|
+
mkdirSync(stageRoot, { recursive: true });
|
|
174
|
+
} catch (error) {
|
|
175
|
+
return { ok: false, phase: "stage", error: "could not create staging directory: " + (error?.message ?? String(error)) };
|
|
176
|
+
}
|
|
177
|
+
const spec = pkgName + "@" + (targetVersion || tag);
|
|
178
|
+
log("Staging " + spec + " into " + stageRoot);
|
|
179
|
+
const install = runNpm(["install", "-g", "--prefix", stageRoot, "--no-audit", "--no-fund", spec]);
|
|
180
|
+
if (install.status !== 0) {
|
|
181
|
+
try { rmSync(stageRoot, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
182
|
+
return { ok: false, phase: "stage", error: "npm staging install failed (" + (install.status ?? "?") + ")" };
|
|
183
|
+
}
|
|
184
|
+
const stagedPackage = stagedCandidates.find(dir => existsSync(join(dir, "package.json")));
|
|
185
|
+
if (!stagedPackage) {
|
|
186
|
+
try { rmSync(stageRoot, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
187
|
+
return { ok: false, phase: "verify", error: "staged package directory not found under " + stageRoot };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// D2: verify INSIDE the stage. Live is still untouched on any failure here.
|
|
191
|
+
const staged = verifyInstallTree(stagedPackage, targetVersion || undefined);
|
|
192
|
+
if (!staged.ok) {
|
|
193
|
+
try { rmSync(stageRoot, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
194
|
+
return { ok: false, phase: "verify", error: "staged tree failed verification: " + staged.failures.join("; ") };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// D3: swap. live -> backup, stage -> live, re-verify live, rollback on failure.
|
|
198
|
+
const backupRoot = join(scopeDir, stampedName(".ocx-backup"));
|
|
199
|
+
const backupPackage = join(backupRoot, "opencodex");
|
|
200
|
+
try {
|
|
201
|
+
mkdirSync(backupRoot, { recursive: true });
|
|
202
|
+
} catch (error) {
|
|
203
|
+
try { rmSync(stageRoot, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
204
|
+
return { ok: false, phase: "swap-backup", error: "could not create backup directory: " + (error?.message ?? String(error)) };
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
renameWithRetry(rename, packageDir, backupPackage);
|
|
208
|
+
} catch (error) {
|
|
209
|
+
try { rmSync(stageRoot, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
210
|
+
try { rmSync(backupRoot, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
211
|
+
return { ok: false, phase: "swap-backup", error: "could not move live tree aside: " + (error?.message ?? String(error)) };
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
renameWithRetry(rename, stagedPackage, packageDir);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
// Rollback: reverse the first rename. Double fault leaves the recovery marker.
|
|
217
|
+
try {
|
|
218
|
+
renameWithRetry(rename, backupPackage, packageDir);
|
|
219
|
+
try { rmSync(stageRoot, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
220
|
+
try { rmSync(backupRoot, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
221
|
+
return { ok: false, phase: "swap-live", rolledBack: true, error: "could not place staged tree: " + (error?.message ?? String(error)) };
|
|
222
|
+
} catch (rollbackError) {
|
|
223
|
+
writeFileSync(recoveryMarkerPath(scopeDir), JSON.stringify({
|
|
224
|
+
at: new Date().toISOString(),
|
|
225
|
+
backup: backupPackage,
|
|
226
|
+
live: packageDir,
|
|
227
|
+
restore: 'move "' + backupPackage + '" back to "' + packageDir + '"',
|
|
228
|
+
error: String(error?.message ?? error),
|
|
229
|
+
rollbackError: String(rollbackError?.message ?? rollbackError),
|
|
230
|
+
}, null, 2));
|
|
231
|
+
return { ok: false, phase: "double-fault", rolledBack: false, error: "swap and rollback both failed; recovery marker written at " + recoveryMarkerPath(scopeDir) };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const liveCheck = verifyInstallTree(packageDir, targetVersion || undefined);
|
|
235
|
+
if (!liveCheck.ok) {
|
|
236
|
+
try {
|
|
237
|
+
rmSync(packageDir, { recursive: true, force: true });
|
|
238
|
+
rename(backupPackage, packageDir);
|
|
239
|
+
try { rmSync(stageRoot, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
240
|
+
try { rmSync(backupRoot, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
241
|
+
return { ok: false, phase: "post-verify", rolledBack: true, error: "live tree failed post-swap verification: " + liveCheck.failures.join("; ") };
|
|
242
|
+
} catch (rollbackError) {
|
|
243
|
+
writeFileSync(recoveryMarkerPath(scopeDir), JSON.stringify({
|
|
244
|
+
at: new Date().toISOString(),
|
|
245
|
+
backup: backupPackage,
|
|
246
|
+
live: packageDir,
|
|
247
|
+
restore: 'move "' + backupPackage + '" back to "' + packageDir + '"',
|
|
248
|
+
error: "post-swap verification failed: " + liveCheck.failures.join("; "),
|
|
249
|
+
rollbackError: String(rollbackError?.message ?? rollbackError),
|
|
250
|
+
}, null, 2));
|
|
251
|
+
return { ok: false, phase: "double-fault", rolledBack: false, error: "post-verify rollback failed; recovery marker written" };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Success: stage scaffolding is disposable now; the backup stays until the next
|
|
255
|
+
// healthy boot reaps it (bootRestoreProbe) — the process that spawned this update may
|
|
256
|
+
// still hold the old cwd.
|
|
257
|
+
try { rmSync(stageRoot, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
258
|
+
return { ok: true, phase: "done", backup: backupPackage };
|
|
259
|
+
}
|
package/src/usage/cost.ts
CHANGED
|
Binary file
|