@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,150 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { chmodSync, lstatSync, mkdirSync, realpathSync } from "node:fs";
|
|
3
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { Database } from "bun:sqlite";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
normalizeTrustedDarwinSystemAlias,
|
|
9
|
+
sameLogGuardPathIdentity,
|
|
10
|
+
} from "./path-safety";
|
|
11
|
+
import { isSqliteBusy } from "./sqlite-errors";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
CodexUserIdentityRefusal,
|
|
15
|
+
resolveCodexCoordinatorDatabasePath,
|
|
16
|
+
resolveEffectiveUserIdentity,
|
|
17
|
+
} from "../user-identity";
|
|
18
|
+
|
|
19
|
+
export type CodexLogGuardLockOutcome<T> =
|
|
20
|
+
| { kind: "completed"; value: T }
|
|
21
|
+
| { kind: "unavailable"; reason: "busy" | "database" | "unsafe-path" };
|
|
22
|
+
|
|
23
|
+
export interface CodexLogGuardLockDeps {
|
|
24
|
+
/** Test seam. Production resolves a dedicated DB in the trusted user runtime root. */
|
|
25
|
+
resolveDatabasePath?: (canonicalCodexHome: string, canonicalLogsDbPath: string) => string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function lockFileIsSafe(path: string, requireRealpath: boolean): boolean {
|
|
29
|
+
const stat = lstatSync(path);
|
|
30
|
+
if (stat.isSymbolicLink() || !stat.isFile()) return false;
|
|
31
|
+
if (requireRealpath && !sameLogGuardPathIdentity(realpathSync.native(path), path)) return false;
|
|
32
|
+
if (process.platform === "win32") return true;
|
|
33
|
+
const uid = process.getuid?.();
|
|
34
|
+
return uid !== undefined && stat.uid === uid && (stat.mode & 0o777) === 0o600;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function codexLogGuardLockDigest(
|
|
38
|
+
canonicalCodexHome: string,
|
|
39
|
+
canonicalLogsDbPath: string,
|
|
40
|
+
): string {
|
|
41
|
+
const digestCodexHome = normalizeTrustedDarwinSystemAlias(canonicalCodexHome);
|
|
42
|
+
const digestLogsDbPath = normalizeTrustedDarwinSystemAlias(canonicalLogsDbPath);
|
|
43
|
+
return createHash("sha256")
|
|
44
|
+
.update(`${digestCodexHome.length}:${digestCodexHome}`)
|
|
45
|
+
.update(`${digestLogsDbPath.length}:${digestLogsDbPath}`)
|
|
46
|
+
.digest("hex");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function resolveLogGuardLockDatabase(
|
|
50
|
+
canonicalCodexHome: string,
|
|
51
|
+
canonicalLogsDbPath: string,
|
|
52
|
+
): string {
|
|
53
|
+
if (!isAbsolute(canonicalCodexHome) || !isAbsolute(canonicalLogsDbPath)) {
|
|
54
|
+
throw new CodexUserIdentityRefusal("Codex Log Guard lock keys must be absolute paths.");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const identity = resolveEffectiveUserIdentity();
|
|
58
|
+
// Use the native-coordinator resolver only to enter the already-audited,
|
|
59
|
+
// environment-independent per-user runtime root. L itself is a different
|
|
60
|
+
// database in a sibling directory and never acquires N or H.
|
|
61
|
+
const coordinatorPath = resolveCodexCoordinatorDatabasePath(identity, canonicalCodexHome);
|
|
62
|
+
const runtimeRoot = dirname(dirname(coordinatorPath));
|
|
63
|
+
const locksDir = join(runtimeRoot, "log-guard-locks");
|
|
64
|
+
mkdirSync(locksDir, { recursive: true, mode: 0o700 });
|
|
65
|
+
const dirStat = lstatSync(locksDir);
|
|
66
|
+
if (!dirStat.isDirectory() || dirStat.isSymbolicLink()
|
|
67
|
+
|| !sameLogGuardPathIdentity(realpathSync.native(locksDir), locksDir)) {
|
|
68
|
+
throw new CodexUserIdentityRefusal("Codex Log Guard lock directory is unsafe.");
|
|
69
|
+
}
|
|
70
|
+
if (process.platform !== "win32") {
|
|
71
|
+
const uid = process.getuid?.();
|
|
72
|
+
// `mkdirSync` applies `mode` only when it CREATES the directory, so a
|
|
73
|
+
// directory left by an earlier build (or a permissive umask on a older
|
|
74
|
+
// version) keeps its old permissions forever and every Log Guard mutation
|
|
75
|
+
// fails `unsafe_path` with no in-product way out. Tighten it ourselves when
|
|
76
|
+
// we own it, and only refuse if that cannot be achieved.
|
|
77
|
+
if (uid === undefined || dirStat.uid !== uid) {
|
|
78
|
+
throw new CodexUserIdentityRefusal("Codex Log Guard lock directory is not private to the effective user.");
|
|
79
|
+
}
|
|
80
|
+
if ((dirStat.mode & 0o777) !== 0o700) {
|
|
81
|
+
try {
|
|
82
|
+
chmodSync(locksDir, 0o700);
|
|
83
|
+
} catch {
|
|
84
|
+
throw new CodexUserIdentityRefusal("Codex Log Guard lock directory is not private to the effective user.");
|
|
85
|
+
}
|
|
86
|
+
// Re-read rather than trusting the chmod: a filesystem that ignores mode
|
|
87
|
+
// bits must still fail closed.
|
|
88
|
+
if ((lstatSync(locksDir).mode & 0o777) !== 0o700) {
|
|
89
|
+
throw new CodexUserIdentityRefusal("Codex Log Guard lock directory is not private to the effective user.");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return join(locksDir, `${codexLogGuardLockDigest(canonicalCodexHome, canonicalLogsDbPath)}.sqlite`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function withCodexLogGuardLock<T>(
|
|
98
|
+
canonicalCodexHome: string,
|
|
99
|
+
canonicalLogsDbPath: string,
|
|
100
|
+
work: () => T,
|
|
101
|
+
deps: CodexLogGuardLockDeps = {},
|
|
102
|
+
): CodexLogGuardLockOutcome<T> {
|
|
103
|
+
let databasePath: string;
|
|
104
|
+
try {
|
|
105
|
+
databasePath = deps.resolveDatabasePath?.(canonicalCodexHome, canonicalLogsDbPath)
|
|
106
|
+
?? resolveLogGuardLockDatabase(canonicalCodexHome, canonicalLogsDbPath);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (error instanceof CodexUserIdentityRefusal) {
|
|
109
|
+
return { kind: "unavailable", reason: "unsafe-path" };
|
|
110
|
+
}
|
|
111
|
+
return { kind: "unavailable", reason: "database" };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let database: Database | undefined;
|
|
115
|
+
let transactionOpen = false;
|
|
116
|
+
try {
|
|
117
|
+
let absent = false;
|
|
118
|
+
try {
|
|
119
|
+
if (!lockFileIsSafe(databasePath, false)) {
|
|
120
|
+
return { kind: "unavailable", reason: "unsafe-path" };
|
|
121
|
+
}
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if ((error as NodeJS.ErrnoException | undefined)?.code !== "ENOENT") throw error;
|
|
124
|
+
absent = true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
database = new Database(databasePath, { create: true });
|
|
128
|
+
if (absent) {
|
|
129
|
+
try { chmodSync(databasePath, 0o600); } catch { /* Windows permissions are enforced by the trusted runtime root. */ }
|
|
130
|
+
}
|
|
131
|
+
if (!lockFileIsSafe(databasePath, true)) {
|
|
132
|
+
return { kind: "unavailable", reason: "unsafe-path" };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
database.exec("PRAGMA busy_timeout = 0; BEGIN IMMEDIATE");
|
|
136
|
+
transactionOpen = true;
|
|
137
|
+
const value = work();
|
|
138
|
+
database.exec("COMMIT");
|
|
139
|
+
transactionOpen = false;
|
|
140
|
+
return { kind: "completed", value };
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (transactionOpen) {
|
|
143
|
+
try { database?.exec("ROLLBACK"); } catch { /* close releases it */ }
|
|
144
|
+
}
|
|
145
|
+
if (isSqliteBusy(error)) return { kind: "unavailable", reason: "busy" };
|
|
146
|
+
throw error;
|
|
147
|
+
} finally {
|
|
148
|
+
try { database?.close(); } catch { /* acquisition already settled */ }
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { lstatSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { Database, constants as sqliteConstants } from "bun:sqlite";
|
|
3
|
+
|
|
4
|
+
import { getCodexHome, resolveCodexLogsDbPath } from "../paths";
|
|
5
|
+
import { samePathIdentity } from "../user-identity";
|
|
6
|
+
import { hasCurrentLogsSchema, inspectCodexLogs } from "./inspect";
|
|
7
|
+
import { withCodexLogGuardLock, type CodexLogGuardLockOutcome } from "./lock";
|
|
8
|
+
import { sameLogGuardPathIdentity } from "./path-safety";
|
|
9
|
+
import { isSqliteBusy } from "./sqlite-errors";
|
|
10
|
+
import { listRunningCodexProcesses, type CodexWriterProcessCheck } from "./processes";
|
|
11
|
+
|
|
12
|
+
const CURRENT_LOG_COLUMNS = [
|
|
13
|
+
"id",
|
|
14
|
+
"ts",
|
|
15
|
+
"ts_nanos",
|
|
16
|
+
"level",
|
|
17
|
+
"target",
|
|
18
|
+
"feedback_log_body",
|
|
19
|
+
"module_path",
|
|
20
|
+
"file",
|
|
21
|
+
"line",
|
|
22
|
+
"thread_id",
|
|
23
|
+
"process_uuid",
|
|
24
|
+
"estimated_bytes",
|
|
25
|
+
] as const;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Budgets are expressed in BYTES and converted with the database's real page
|
|
29
|
+
* size, because that is what the guide promises: ~8 MiB per batch and ~256 MiB
|
|
30
|
+
* per run. Fixed page counts silently meant something different on every page
|
|
31
|
+
* size — at the 4 KiB pages the fixtures use, 512/8192 pages is 2 MiB/32 MiB,
|
|
32
|
+
* a quarter of the documented budget.
|
|
33
|
+
*/
|
|
34
|
+
const DEFAULT_BATCH_BYTES = 8 * 1024 * 1024;
|
|
35
|
+
const DEFAULT_MAX_BYTES_PER_RUN = 256 * 1024 * 1024;
|
|
36
|
+
const MAX_ITERATIONS = 64;
|
|
37
|
+
|
|
38
|
+
/** Convert a byte budget to whole pages, never returning zero pages. */
|
|
39
|
+
function pagesForBytes(bytes: number, pageSize: number): number {
|
|
40
|
+
if (!Number.isFinite(pageSize) || pageSize <= 0) return 1;
|
|
41
|
+
return Math.max(1, Math.floor(bytes / pageSize));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type CompactStopReason = "complete" | "page_budget" | "no_progress" | "busy";
|
|
45
|
+
|
|
46
|
+
export interface CodexLogGuardCompactionMeasure {
|
|
47
|
+
databaseBytes: number;
|
|
48
|
+
/** On-disk WAL sidecar size at measurement time; FULL checkpoint does not imply shrinkage. */
|
|
49
|
+
walBytes: number;
|
|
50
|
+
pageCount: number;
|
|
51
|
+
freelistPages: number;
|
|
52
|
+
reclaimableBytes: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface CodexLogGuardCompactionReport {
|
|
56
|
+
pageSize: number;
|
|
57
|
+
before: CodexLogGuardCompactionMeasure;
|
|
58
|
+
after: CodexLogGuardCompactionMeasure;
|
|
59
|
+
pagesReclaimed: number;
|
|
60
|
+
/**
|
|
61
|
+
* Logical space returned to the free list, in bytes (`pagesReclaimed * pageSize`).
|
|
62
|
+
* Distinct from `physicalDatabaseBytesReclaimed`: an incremental vacuum can
|
|
63
|
+
* reclaim pages without the file shrinking, so a run that reports logical
|
|
64
|
+
* progress and zero physical shrinkage is normal rather than a failure. The
|
|
65
|
+
* guide documented this field before it existed.
|
|
66
|
+
*/
|
|
67
|
+
logicalBytesReclaimed: number;
|
|
68
|
+
physicalDatabaseBytesReclaimed: number;
|
|
69
|
+
iterations: number;
|
|
70
|
+
complete: boolean;
|
|
71
|
+
stopReason: CompactStopReason;
|
|
72
|
+
integrity: { before: "ok"; after: "ok" };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type CodexLogGuardCompactionError =
|
|
76
|
+
| "unsupported_schema"
|
|
77
|
+
| "codex_running"
|
|
78
|
+
| "process_enumeration_failed"
|
|
79
|
+
| "unsafe_path"
|
|
80
|
+
| "busy"
|
|
81
|
+
| "database_error"
|
|
82
|
+
| "auto_vacuum_not_incremental"
|
|
83
|
+
| "integrity_check_failed";
|
|
84
|
+
|
|
85
|
+
export type CodexLogGuardCompactionResult =
|
|
86
|
+
| { ok: true; report: CodexLogGuardCompactionReport }
|
|
87
|
+
| {
|
|
88
|
+
ok: false;
|
|
89
|
+
error: Exclude<CodexLogGuardCompactionError, "integrity_check_failed">;
|
|
90
|
+
}
|
|
91
|
+
| { ok: false; error: "integrity_check_failed"; phase: "before" | "after" };
|
|
92
|
+
|
|
93
|
+
export interface CodexLogGuardMaintenanceDeps {
|
|
94
|
+
codexHome?: string;
|
|
95
|
+
processCheck?: () => CodexWriterProcessCheck;
|
|
96
|
+
withLock?: <T>(
|
|
97
|
+
canonicalCodexHome: string,
|
|
98
|
+
canonicalLogsDbPath: string,
|
|
99
|
+
work: () => T,
|
|
100
|
+
) => CodexLogGuardLockOutcome<T>;
|
|
101
|
+
quickCheck?: (db: Database) => string[];
|
|
102
|
+
openDatabase?: (databasePath: string, flags: number) => Database;
|
|
103
|
+
batchPages?: number;
|
|
104
|
+
maxPagesPerRun?: number;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
interface ColumnRow { name: string }
|
|
108
|
+
interface CheckpointRow {
|
|
109
|
+
busy?: number;
|
|
110
|
+
log?: number;
|
|
111
|
+
checkpointed?: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface DatabaseFileIdentity {
|
|
115
|
+
dev: number;
|
|
116
|
+
ino: number;
|
|
117
|
+
realPath: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function databasePathIdentity(databasePath: string): DatabaseFileIdentity | null {
|
|
121
|
+
try {
|
|
122
|
+
const stat = lstatSync(databasePath);
|
|
123
|
+
if (!stat.isFile() || stat.isSymbolicLink()) return null;
|
|
124
|
+
const realPath = realpathSync.native(databasePath);
|
|
125
|
+
if (!sameLogGuardPathIdentity(realPath, databasePath)) return null;
|
|
126
|
+
return { dev: stat.dev, ino: stat.ino, realPath };
|
|
127
|
+
} catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function databasePathIsSafe(databasePath: string): boolean {
|
|
133
|
+
return databasePathIdentity(databasePath) !== null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function databasePathStillMatches(
|
|
137
|
+
databasePath: string,
|
|
138
|
+
before: DatabaseFileIdentity,
|
|
139
|
+
): boolean {
|
|
140
|
+
const after = databasePathIdentity(databasePath);
|
|
141
|
+
return after !== null
|
|
142
|
+
&& after.dev === before.dev
|
|
143
|
+
&& after.ino === before.ino
|
|
144
|
+
&& samePathIdentity(after.realPath, before.realPath);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function exactCurrentSchema(db: Database): boolean {
|
|
148
|
+
// Same reasoning as protection.ts: the pre-mutation recheck must match the
|
|
149
|
+
// inspector's compatibility contract exactly, or Reclaim can vacuum a
|
|
150
|
+
// database the inspector classifies as monitor-only.
|
|
151
|
+
return hasCurrentLogsSchema(db);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function pragmaNumber(db: Database, sql: string): number {
|
|
155
|
+
const row = db.query<Record<string, unknown>, []>(sql).get();
|
|
156
|
+
if (!row) throw new Error(`missing pragma result for ${sql}`);
|
|
157
|
+
const value = Number(Object.values(row)[0]);
|
|
158
|
+
if (!Number.isFinite(value)) throw new Error(`invalid pragma result for ${sql}`);
|
|
159
|
+
return value;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function defaultQuickCheck(db: Database): string[] {
|
|
163
|
+
return db.query<Record<string, unknown>, []>("PRAGMA quick_check").all().map(row => {
|
|
164
|
+
const value = Object.values(row)[0];
|
|
165
|
+
return value === undefined ? "" : String(value);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function quickCheckIsOk(rows: string[]): boolean {
|
|
170
|
+
return rows.length === 1 && rows[0]?.trim().toLowerCase() === "ok";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function processRefusal(
|
|
174
|
+
check: CodexWriterProcessCheck,
|
|
175
|
+
): "process_enumeration_failed" | "codex_running" | null {
|
|
176
|
+
if (check.state === "unknown") return "process_enumeration_failed";
|
|
177
|
+
if (check.processes.length > 0) return "codex_running";
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function checkpointFull(db: Database): "ok" | "busy" {
|
|
182
|
+
const row = db.query<CheckpointRow, []>("PRAGMA wal_checkpoint(FULL)").get();
|
|
183
|
+
if (!row) throw new Error("missing wal_checkpoint result");
|
|
184
|
+
const values = Object.values(row).map(Number);
|
|
185
|
+
const busy = Number(row.busy ?? values[0] ?? 0);
|
|
186
|
+
const log = Number(row.log ?? values[1] ?? -1);
|
|
187
|
+
const checkpointed = Number(row.checkpointed ?? values[2] ?? -1);
|
|
188
|
+
if (busy !== 0) return "busy";
|
|
189
|
+
// SQLite returns -1/-1 when the database is not in WAL mode or there are no
|
|
190
|
+
// WAL frames to report. Otherwise FULL must have copied every frame.
|
|
191
|
+
if (log >= 0 && checkpointed >= 0 && checkpointed < log) return "busy";
|
|
192
|
+
return "ok";
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function measure(databasePath: string, db: Database, pageSize: number): CodexLogGuardCompactionMeasure {
|
|
196
|
+
const databaseBytes = (() => {
|
|
197
|
+
try {
|
|
198
|
+
const stat = statSync(databasePath);
|
|
199
|
+
return stat.isFile() ? stat.size : 0;
|
|
200
|
+
} catch {
|
|
201
|
+
return 0;
|
|
202
|
+
}
|
|
203
|
+
})();
|
|
204
|
+
const walBytes = (() => {
|
|
205
|
+
try {
|
|
206
|
+
const stat = statSync(`${databasePath}-wal`);
|
|
207
|
+
return stat.isFile() ? stat.size : 0;
|
|
208
|
+
} catch {
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
})();
|
|
212
|
+
const pageCount = pragmaNumber(db, "PRAGMA page_count");
|
|
213
|
+
const freelistPages = pragmaNumber(db, "PRAGMA freelist_count");
|
|
214
|
+
return {
|
|
215
|
+
databaseBytes,
|
|
216
|
+
walBytes,
|
|
217
|
+
pageCount,
|
|
218
|
+
freelistPages,
|
|
219
|
+
reclaimableBytes: pageSize * freelistPages,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function runCompaction(
|
|
224
|
+
databasePath: string,
|
|
225
|
+
deps: CodexLogGuardMaintenanceDeps,
|
|
226
|
+
): CodexLogGuardCompactionResult {
|
|
227
|
+
let db: Database | undefined;
|
|
228
|
+
let probeOpen = false;
|
|
229
|
+
let reportBusyPartial: (() => CodexLogGuardCompactionResult) | undefined;
|
|
230
|
+
try {
|
|
231
|
+
const beforeOpenIdentity = databasePathIdentity(databasePath);
|
|
232
|
+
if (!beforeOpenIdentity) return { ok: false, error: "unsafe_path" };
|
|
233
|
+
const openDatabase = deps.openDatabase
|
|
234
|
+
?? ((path: string, flags: number) => new Database(path, flags));
|
|
235
|
+
db = openDatabase(databasePath, sqliteConstants.SQLITE_OPEN_READWRITE);
|
|
236
|
+
// The path is user-writable foreign state. Re-check its regular-file,
|
|
237
|
+
// canonical-path and st_dev/st_ino identity immediately after SQLite opens
|
|
238
|
+
// it, before issuing any pragma or write-capable statement.
|
|
239
|
+
if (!databasePathStillMatches(databasePath, beforeOpenIdentity)) {
|
|
240
|
+
return { ok: false, error: "unsafe_path" };
|
|
241
|
+
}
|
|
242
|
+
db.exec("PRAGMA busy_timeout = 0");
|
|
243
|
+
|
|
244
|
+
if (!exactCurrentSchema(db)) return { ok: false, error: "unsupported_schema" };
|
|
245
|
+
if (pragmaNumber(db, "PRAGMA auto_vacuum") !== 2) {
|
|
246
|
+
return { ok: false, error: "auto_vacuum_not_incremental" };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const quickCheck = deps.quickCheck ?? defaultQuickCheck;
|
|
250
|
+
if (!quickCheckIsOk(quickCheck(db))) {
|
|
251
|
+
return { ok: false, error: "integrity_check_failed", phase: "before" };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Confirm no SQLite writer can acquire the file before the first checkpoint.
|
|
255
|
+
// BEGIN IMMEDIATE is intentionally released before PRAGMA wal_checkpoint,
|
|
256
|
+
// which cannot run while this same connection holds a write transaction.
|
|
257
|
+
db.exec("BEGIN IMMEDIATE");
|
|
258
|
+
probeOpen = true;
|
|
259
|
+
db.exec("ROLLBACK");
|
|
260
|
+
probeOpen = false;
|
|
261
|
+
|
|
262
|
+
if (checkpointFull(db) === "busy") return { ok: false, error: "busy" };
|
|
263
|
+
|
|
264
|
+
const pageSize = pragmaNumber(db, "PRAGMA page_size");
|
|
265
|
+
const before = measure(databasePath, db, pageSize);
|
|
266
|
+
// Derived from the byte budgets AFTER reading the real page size, so the
|
|
267
|
+
// documented ~8 MiB batch / ~256 MiB run hold at any page size. Explicit
|
|
268
|
+
// page-count overrides still win, which is what the tests use.
|
|
269
|
+
const batchPages = Math.max(1, Math.floor(deps.batchPages ?? pagesForBytes(DEFAULT_BATCH_BYTES, pageSize)));
|
|
270
|
+
const maxPages = Math.max(batchPages, Math.floor(deps.maxPagesPerRun ?? pagesForBytes(DEFAULT_MAX_BYTES_PER_RUN, pageSize)));
|
|
271
|
+
let previousFreelist = before.freelistPages;
|
|
272
|
+
let pagesReclaimed = 0;
|
|
273
|
+
let iterations = 0;
|
|
274
|
+
let stopReason: CompactStopReason = previousFreelist === 0 ? "complete" : "page_budget";
|
|
275
|
+
|
|
276
|
+
const finish = (reason: CompactStopReason): CodexLogGuardCompactionResult => {
|
|
277
|
+
const after = measure(databasePath, db!, pageSize);
|
|
278
|
+
if (!quickCheckIsOk(quickCheck(db!))) {
|
|
279
|
+
return { ok: false, error: "integrity_check_failed", phase: "after" };
|
|
280
|
+
}
|
|
281
|
+
const complete = after.freelistPages === 0;
|
|
282
|
+
// If SQLITE_BUSY is thrown after an incremental_vacuum commit but before the
|
|
283
|
+
// loop can sample freelist_count, the before/after measurements still capture
|
|
284
|
+
// that committed logical reclamation. Never under-report already-landed work.
|
|
285
|
+
const observedPagesReclaimed = Math.max(
|
|
286
|
+
pagesReclaimed,
|
|
287
|
+
Math.max(0, before.freelistPages - after.freelistPages),
|
|
288
|
+
);
|
|
289
|
+
return {
|
|
290
|
+
ok: true,
|
|
291
|
+
report: {
|
|
292
|
+
pageSize,
|
|
293
|
+
before,
|
|
294
|
+
after,
|
|
295
|
+
pagesReclaimed: observedPagesReclaimed,
|
|
296
|
+
logicalBytesReclaimed: observedPagesReclaimed * pageSize,
|
|
297
|
+
physicalDatabaseBytesReclaimed: Math.max(0, before.databaseBytes - after.databaseBytes),
|
|
298
|
+
iterations,
|
|
299
|
+
complete,
|
|
300
|
+
stopReason: reason === "busy" ? "busy" : complete ? "complete" : reason,
|
|
301
|
+
integrity: { before: "ok", after: "ok" },
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
};
|
|
305
|
+
reportBusyPartial = () => iterations > 0 ? finish("busy") : { ok: false, error: "busy" };
|
|
306
|
+
|
|
307
|
+
while (previousFreelist > 0 && pagesReclaimed < maxPages && iterations < MAX_ITERATIONS) {
|
|
308
|
+
const pageBudget = Math.min(batchPages, maxPages - pagesReclaimed, previousFreelist);
|
|
309
|
+
if (pageBudget <= 0) {
|
|
310
|
+
stopReason = "page_budget";
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
const priorFreelist = previousFreelist;
|
|
314
|
+
db.exec(`PRAGMA incremental_vacuum(${pageBudget})`);
|
|
315
|
+
iterations += 1;
|
|
316
|
+
const checkpoint = checkpointFull(db);
|
|
317
|
+
const currentFreelist = pragmaNumber(db, "PRAGMA freelist_count");
|
|
318
|
+
const reclaimed = Math.max(0, priorFreelist - currentFreelist);
|
|
319
|
+
pagesReclaimed += reclaimed;
|
|
320
|
+
previousFreelist = currentFreelist;
|
|
321
|
+
|
|
322
|
+
// incremental_vacuum has already committed by this point. A busy FULL
|
|
323
|
+
// checkpoint is therefore a partial-success stop, not an atomic refusal.
|
|
324
|
+
if (checkpoint === "busy") return finish("busy");
|
|
325
|
+
if (currentFreelist === 0) {
|
|
326
|
+
stopReason = "complete";
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
if (currentFreelist >= priorFreelist) {
|
|
330
|
+
stopReason = "no_progress";
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
stopReason = "page_budget";
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (previousFreelist > 0 && iterations >= MAX_ITERATIONS && stopReason !== "no_progress") {
|
|
337
|
+
// MAX_ITERATIONS is a bounded-work limit, not evidence that vacuum stalled.
|
|
338
|
+
stopReason = "page_budget";
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// The preceding incremental-vacuum iterations checkpoint after every batch.
|
|
342
|
+
// One final FULL checkpoint backfills any remaining WAL frames before the
|
|
343
|
+
// final main-database measurement. FULL does not reset or shrink the WAL
|
|
344
|
+
// sidecar, so `after.walBytes` is an observational size, not reclaimed WAL.
|
|
345
|
+
if (checkpointFull(db) === "busy") {
|
|
346
|
+
return iterations > 0 ? finish("busy") : { ok: false, error: "busy" };
|
|
347
|
+
}
|
|
348
|
+
return finish(stopReason);
|
|
349
|
+
} catch (error) {
|
|
350
|
+
if (probeOpen) {
|
|
351
|
+
try { db?.exec("ROLLBACK"); } catch { /* close releases it */ }
|
|
352
|
+
}
|
|
353
|
+
if (isSqliteBusy(error)) {
|
|
354
|
+
// Mirror the explicit busy exits. Once at least one vacuum batch completed,
|
|
355
|
+
// a later thrown busy is a partial-success stop rather than a pure refusal.
|
|
356
|
+
if (reportBusyPartial) {
|
|
357
|
+
try { return reportBusyPartial(); } catch { /* fall through to refusal */ }
|
|
358
|
+
}
|
|
359
|
+
return { ok: false, error: "busy" };
|
|
360
|
+
}
|
|
361
|
+
return { ok: false, error: "database_error" };
|
|
362
|
+
} finally {
|
|
363
|
+
try { db?.close(); } catch { /* maintenance already settled */ }
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function compactCodexLogs(
|
|
368
|
+
deps: CodexLogGuardMaintenanceDeps = {},
|
|
369
|
+
): CodexLogGuardCompactionResult {
|
|
370
|
+
const codexHome = deps.codexHome ?? getCodexHome();
|
|
371
|
+
const inspection = inspectCodexLogs({ codexHome });
|
|
372
|
+
const databasePath = resolveCodexLogsDbPath({ codexHome });
|
|
373
|
+
if (inspection.capabilities.reclaim.state !== "supported") {
|
|
374
|
+
return { ok: false, error: "unsupported_schema" };
|
|
375
|
+
}
|
|
376
|
+
if (!databasePathIsSafe(databasePath)) return { ok: false, error: "unsafe_path" };
|
|
377
|
+
|
|
378
|
+
const checkProcesses = deps.processCheck ?? listRunningCodexProcesses;
|
|
379
|
+
const firstRefusal = processRefusal(checkProcesses());
|
|
380
|
+
if (firstRefusal) return { ok: false, error: firstRefusal };
|
|
381
|
+
|
|
382
|
+
const withLock = deps.withLock ?? withCodexLogGuardLock;
|
|
383
|
+
let locked: CodexLogGuardLockOutcome<CodexLogGuardCompactionResult>;
|
|
384
|
+
try {
|
|
385
|
+
locked = withLock(codexHome, databasePath, () => {
|
|
386
|
+
const secondRefusal = processRefusal(checkProcesses());
|
|
387
|
+
if (secondRefusal) return { ok: false as const, error: secondRefusal };
|
|
388
|
+
return runCompaction(databasePath, deps);
|
|
389
|
+
});
|
|
390
|
+
} catch {
|
|
391
|
+
return { ok: false, error: "database_error" };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (locked.kind === "unavailable") {
|
|
395
|
+
return {
|
|
396
|
+
ok: false,
|
|
397
|
+
error: locked.reason === "busy"
|
|
398
|
+
? "busy"
|
|
399
|
+
: locked.reason === "unsafe-path" ? "unsafe_path" : "database_error",
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
return locked.value;
|
|
403
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { lstatSync, realpathSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve, sep } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { samePathIdentity } from "../user-identity";
|
|
5
|
+
|
|
6
|
+
const TRUSTED_DARWIN_SYSTEM_ALIASES = [
|
|
7
|
+
{ alias: "/var", canonical: "/private/var" },
|
|
8
|
+
{ alias: "/tmp", canonical: "/private/tmp" },
|
|
9
|
+
] as const;
|
|
10
|
+
|
|
11
|
+
export function normalizeTrustedDarwinSystemAlias(path: string): string {
|
|
12
|
+
const requested = resolve(path);
|
|
13
|
+
if (process.platform !== "darwin") return requested;
|
|
14
|
+
|
|
15
|
+
for (const entry of TRUSTED_DARWIN_SYSTEM_ALIASES) {
|
|
16
|
+
if (requested !== entry.alias && !requested.startsWith(`${entry.alias}${sep}`)) continue;
|
|
17
|
+
|
|
18
|
+
let actualAliasTarget: string;
|
|
19
|
+
try {
|
|
20
|
+
actualAliasTarget = realpathSync.native(entry.alias);
|
|
21
|
+
} catch {
|
|
22
|
+
// If the platform alias is absent or unreadable, keep the strict spelling check.
|
|
23
|
+
return requested;
|
|
24
|
+
}
|
|
25
|
+
if (!samePathIdentity(actualAliasTarget, entry.canonical, "darwin")) return requested;
|
|
26
|
+
return `${entry.canonical}${requested.slice(entry.alias.length)}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return requested;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Compare a canonical realpath with a requested Log Guard path without treating
|
|
34
|
+
* macOS's OS-owned /var and /tmp aliases as user-controlled redirections.
|
|
35
|
+
* Arbitrary ancestor symlinks remain refused.
|
|
36
|
+
*/
|
|
37
|
+
export function sameLogGuardPathIdentity(realPath: string, requestedPath: string): boolean {
|
|
38
|
+
const requested = normalizeTrustedDarwinSystemAlias(requestedPath);
|
|
39
|
+
if (samePathIdentity(realPath, requested)) return true;
|
|
40
|
+
return sameWindowsCanonicalPath(realPath, requested);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* On Windows, is the difference between these two spellings the OS canonicalizing the
|
|
45
|
+
* request rather than a redirection?
|
|
46
|
+
*
|
|
47
|
+
* `realpathSync.native` expands 8.3 short components — the `RUNNER~1` form that appears
|
|
48
|
+
* throughout `%TEMP%` — so the canonical path and the requested path can disagree as
|
|
49
|
+
* strings while naming the same file. Reading that as an ancestor-symlink redirection made
|
|
50
|
+
* every Log Guard mutation refuse with `unsafe_path` on Windows, which is what the CI shards
|
|
51
|
+
* were reporting.
|
|
52
|
+
*
|
|
53
|
+
* The first version of this re-canonicalized the requested path and compared the two
|
|
54
|
+
* canonical forms. That was wrong, and the Windows shard proved it: the caller already
|
|
55
|
+
* passes `realpathSync.native(requested)` as `realPath`, so re-resolving the request
|
|
56
|
+
* produced the same value on BOTH sides and a symlinked database compared equal. The
|
|
57
|
+
* widening let through exactly what the guard exists to refuse.
|
|
58
|
+
*
|
|
59
|
+
* The comparison is therefore link-aware. A short-name expansion rewrites the spelling of
|
|
60
|
+
* components that are all still directories on the same chain, so it is enough to require
|
|
61
|
+
* that no component of the request is a link: with none present, any remaining difference
|
|
62
|
+
* is the OS's own canonical spelling. A symlink or junction anywhere in the chain fails
|
|
63
|
+
* closed as before.
|
|
64
|
+
*/
|
|
65
|
+
function sameWindowsCanonicalPath(realPath: string, requestedPath: string): boolean {
|
|
66
|
+
if (process.platform !== "win32") return false;
|
|
67
|
+
try {
|
|
68
|
+
if (pathChainContainsLink(requestedPath)) return false;
|
|
69
|
+
return samePathIdentity(realPath, realpathSync.native(requestedPath));
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Is any component of this path a symlink or junction? Fails closed on an unreadable one. */
|
|
76
|
+
function pathChainContainsLink(path: string): boolean {
|
|
77
|
+
let current = resolve(path);
|
|
78
|
+
for (;;) {
|
|
79
|
+
try {
|
|
80
|
+
if (lstatSync(current).isSymbolicLink()) return true;
|
|
81
|
+
} catch {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
const parent = dirname(current);
|
|
85
|
+
if (parent === current) return false;
|
|
86
|
+
current = parent;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { loadConfig, saveConfigPreservingClaudeCode } from "../../config";
|
|
2
|
+
import type { OcxConfig } from "../../types";
|
|
3
|
+
|
|
4
|
+
export type CodexLogGuardMode = "off" | "compat" | "quiet";
|
|
5
|
+
|
|
6
|
+
type ConfigWithLogGuard = OcxConfig & {
|
|
7
|
+
codexLogGuard?: {
|
|
8
|
+
mode?: unknown;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export interface CodexLogGuardPolicyDeps {
|
|
14
|
+
load?: () => OcxConfig;
|
|
15
|
+
save?: (config: OcxConfig) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function readCodexLogGuardMode(deps: CodexLogGuardPolicyDeps = {}): CodexLogGuardMode {
|
|
19
|
+
const config = (deps.load ?? loadConfig)() as ConfigWithLogGuard;
|
|
20
|
+
const mode = config.codexLogGuard?.mode;
|
|
21
|
+
return mode === "compat" || mode === "quiet" ? mode : "off";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Persist user intent separately from Codex's logs database.
|
|
26
|
+
*
|
|
27
|
+
* A Codex migration may rebuild the `logs` table and thereby remove our
|
|
28
|
+
* trigger. Keeping intent in OpenCodex config makes that observable as drift
|
|
29
|
+
* rather than silently treating protection as disabled. `off` is explicit so
|
|
30
|
+
* a stale unknown value cannot reactivate protection later.
|
|
31
|
+
*/
|
|
32
|
+
export function writeCodexLogGuardMode(
|
|
33
|
+
mode: CodexLogGuardMode,
|
|
34
|
+
deps: CodexLogGuardPolicyDeps = {},
|
|
35
|
+
): void {
|
|
36
|
+
const load = deps.load ?? loadConfig;
|
|
37
|
+
const save = deps.save ?? saveConfigPreservingClaudeCode;
|
|
38
|
+
const config = load() as ConfigWithLogGuard;
|
|
39
|
+
const current = config.codexLogGuard && typeof config.codexLogGuard === "object"
|
|
40
|
+
? config.codexLogGuard
|
|
41
|
+
: {};
|
|
42
|
+
config.codexLogGuard = { ...current, mode };
|
|
43
|
+
save(config);
|
|
44
|
+
}
|