@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,205 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
listWindowsSnapshots,
|
|
6
|
+
tokenizeCommandLine,
|
|
7
|
+
type ProcessSnapshot,
|
|
8
|
+
} from "../app-server-processes";
|
|
9
|
+
|
|
10
|
+
export interface CodexWriterProcess {
|
|
11
|
+
pid: number;
|
|
12
|
+
commandLine: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type CodexWriterProcessCheck =
|
|
16
|
+
| { state: "ok"; processes: CodexWriterProcess[] }
|
|
17
|
+
| { state: "unknown"; reason: "enumeration_failed" };
|
|
18
|
+
|
|
19
|
+
export interface CodexWriterProcessIo {
|
|
20
|
+
platform?: NodeJS.Platform;
|
|
21
|
+
getuid?: () => number | undefined;
|
|
22
|
+
listSnapshots?: () => ProcessSnapshot[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const TARGET_TRIPLE = /^[a-z0-9_]+-[a-z0-9_]+-[a-z0-9_]+(?:-[a-z0-9_]+)?$/i;
|
|
26
|
+
|
|
27
|
+
function basename(token: string): string {
|
|
28
|
+
return token.replace(/\\/g, "/").split("/").pop()?.toLowerCase() ?? "";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isOfficialCodexExecutable(token: string): boolean {
|
|
32
|
+
const base = basename(token);
|
|
33
|
+
if (base === "codex" || base === "codex.exe" || base === "codex.cmd") return true;
|
|
34
|
+
const withoutSuffix = base.replace(/\.(?:exe|cmd)$/i, "");
|
|
35
|
+
if (!withoutSuffix.startsWith("codex-")) return false;
|
|
36
|
+
return TARGET_TRIPLE.test(withoutSuffix.slice("codex-".length));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isCodeModeHostExecutable(token: string): boolean {
|
|
40
|
+
const base = basename(token);
|
|
41
|
+
return base === "codex-code-mode-host" || base === "codex-code-mode-host.exe";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isInterpreterExecutable(token: string): boolean {
|
|
45
|
+
const base = basename(token);
|
|
46
|
+
return base === "node" || base === "node.exe"
|
|
47
|
+
|| base === "bun" || base === "bun.exe"
|
|
48
|
+
|| base === "deno" || base === "deno.exe";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function looksLikePathPrefix(token: string): boolean {
|
|
52
|
+
return token.startsWith("/") || token.startsWith("./") || token.startsWith("../")
|
|
53
|
+
|| token.startsWith("~") || /^[a-z]:[\\/]/i.test(token);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function flattenedPathPrefix(
|
|
57
|
+
commandLine: string,
|
|
58
|
+
predicate: (candidate: string) => boolean,
|
|
59
|
+
): { executable: string; remainder: string } | null {
|
|
60
|
+
const parts = commandLine.trim().split(/\s+/).filter(Boolean);
|
|
61
|
+
for (let end = parts.length; end >= 1; end -= 1) {
|
|
62
|
+
const executable = parts.slice(0, end).join(" ");
|
|
63
|
+
if (!predicate(executable)) continue;
|
|
64
|
+
return { executable, remainder: parts.slice(end).join(" ") };
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function interpreterHostWithFlattenedPath(commandLine: string, tokens: readonly string[]): boolean {
|
|
70
|
+
if (tokens.length < 2 || !isInterpreterExecutable(tokens[0]!)) return false;
|
|
71
|
+
const trimmed = commandLine.trim();
|
|
72
|
+
const firstWhitespace = trimmed.search(/\s/);
|
|
73
|
+
if (firstWhitespace < 0) return false;
|
|
74
|
+
const remainder = trimmed.slice(firstWhitespace).trim();
|
|
75
|
+
return flattenedPathPrefix(remainder, isCodeModeHostExecutable) !== null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Match official Codex writer executables at argv0 plus the repository's
|
|
80
|
+
* established interpreter-entrypoint form for code-mode-host.
|
|
81
|
+
*
|
|
82
|
+
* OS process listings can flatten argv into a display string. When an unquoted
|
|
83
|
+
* executable path contains spaces, recover only a leading path-shaped executable
|
|
84
|
+
* prefix; arbitrary later argv tokens still never turn a process into Codex.
|
|
85
|
+
*/
|
|
86
|
+
export function isCodexWriterCommandLine(commandLine: string, executable?: string): boolean {
|
|
87
|
+
if (executable && (isOfficialCodexExecutable(executable) || isCodeModeHostExecutable(executable))) return true;
|
|
88
|
+
const tokens = tokenizeCommandLine(commandLine.trim());
|
|
89
|
+
if (tokens.length === 0) return false;
|
|
90
|
+
if (isOfficialCodexExecutable(tokens[0]!) || isCodeModeHostExecutable(tokens[0]!)) return true;
|
|
91
|
+
return tokens.length > 1
|
|
92
|
+
&& isInterpreterExecutable(tokens[0]!)
|
|
93
|
+
&& isCodeModeHostExecutable(tokens[1]!);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function statusUid(status: string): number | undefined {
|
|
97
|
+
const match = /^Uid:\s+(\d+)/m.exec(status);
|
|
98
|
+
if (!match) return undefined;
|
|
99
|
+
const value = Number(match[1]);
|
|
100
|
+
return Number.isSafeInteger(value) ? value : undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function listLinuxSnapshots(uid: number | undefined): ProcessSnapshot[] {
|
|
104
|
+
if (!existsSync("/proc")) throw new Error("procfs_unavailable");
|
|
105
|
+
const rows: ProcessSnapshot[] = [];
|
|
106
|
+
for (const entry of readdirSync("/proc")) {
|
|
107
|
+
if (!/^\d+$/.test(entry)) continue;
|
|
108
|
+
const pid = Number(entry);
|
|
109
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) continue;
|
|
110
|
+
try {
|
|
111
|
+
const procUid = statusUid(readFileSync(`/proc/${pid}/status`, "utf8"));
|
|
112
|
+
if (uid !== undefined && procUid !== undefined && procUid !== uid) continue;
|
|
113
|
+
const argv = readFileSync(`/proc/${pid}/cmdline`)
|
|
114
|
+
.toString("utf8")
|
|
115
|
+
.split("\0")
|
|
116
|
+
.filter(Boolean);
|
|
117
|
+
const commandLine = argv.join(" ").trim();
|
|
118
|
+
if (commandLine) rows.push({ pid, commandLine, executable: argv[0], uid: procUid });
|
|
119
|
+
} catch {
|
|
120
|
+
// A process disappearing mid-enumeration is normal. A top-level procfs
|
|
121
|
+
// failure is handled before the loop and fails closed.
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return rows;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function listDarwinSnapshots(uid: number | undefined): ProcessSnapshot[] {
|
|
128
|
+
const commandOutput = uid !== undefined
|
|
129
|
+
? execFileSync("/bin/ps", ["-u", String(uid), "-o", "pid=,command="], {
|
|
130
|
+
encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5_000,
|
|
131
|
+
})
|
|
132
|
+
: execFileSync("/bin/ps", ["-axo", "pid=,uid=,command="], {
|
|
133
|
+
encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5_000,
|
|
134
|
+
});
|
|
135
|
+
const executableOutput = uid !== undefined
|
|
136
|
+
? execFileSync("/bin/ps", ["-u", String(uid), "-o", "pid=,comm="], {
|
|
137
|
+
encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5_000,
|
|
138
|
+
})
|
|
139
|
+
: execFileSync("/bin/ps", ["-axo", "pid=,comm="], {
|
|
140
|
+
encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5_000,
|
|
141
|
+
});
|
|
142
|
+
const executableByPid = new Map<number, string>();
|
|
143
|
+
for (const raw of executableOutput.split(/\r?\n/)) {
|
|
144
|
+
const match = /^\s*(\d+)\s+(.+)$/.exec(raw);
|
|
145
|
+
if (!match) continue;
|
|
146
|
+
const pid = Number(match[1]);
|
|
147
|
+
const executable = match[2]?.trim() ?? "";
|
|
148
|
+
if (Number.isSafeInteger(pid) && pid > 0 && executable) executableByPid.set(pid, executable);
|
|
149
|
+
}
|
|
150
|
+
const rows: ProcessSnapshot[] = [];
|
|
151
|
+
for (const raw of commandOutput.split(/\r?\n/)) {
|
|
152
|
+
const line = raw.trim();
|
|
153
|
+
if (!line) continue;
|
|
154
|
+
const match = uid !== undefined
|
|
155
|
+
? /^(\d+)\s+(.*)$/.exec(line)
|
|
156
|
+
: /^(\d+)\s+(\d+)\s+(.*)$/.exec(line);
|
|
157
|
+
if (!match) continue;
|
|
158
|
+
const pid = Number(match[1]);
|
|
159
|
+
const commandLine = (uid !== undefined ? match[2] : match[3])?.trim() ?? "";
|
|
160
|
+
if (!Number.isSafeInteger(pid) || pid <= 0 || !commandLine) continue;
|
|
161
|
+
rows.push({
|
|
162
|
+
pid, commandLine, executable: executableByPid.get(pid),
|
|
163
|
+
uid: uid ?? (Number.isSafeInteger(Number(match[2])) ? Number(match[2]) : undefined),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return rows;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function effectiveUid(getuid?: () => number | undefined): number | undefined {
|
|
170
|
+
try {
|
|
171
|
+
return getuid ? getuid() : process.getuid?.();
|
|
172
|
+
} catch {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function defaultSnapshots(platform: NodeJS.Platform, uid: number | undefined): ProcessSnapshot[] {
|
|
178
|
+
if (platform === "win32") return listWindowsSnapshots();
|
|
179
|
+
if (platform === "darwin") return listDarwinSnapshots(uid);
|
|
180
|
+
if (platform === "linux") return listLinuxSnapshots(uid);
|
|
181
|
+
throw new Error("unsupported_process_enumeration_platform");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function listRunningCodexProcesses(io: CodexWriterProcessIo = {}): CodexWriterProcessCheck {
|
|
185
|
+
const platform = io.platform ?? process.platform;
|
|
186
|
+
let snapshots: ProcessSnapshot[];
|
|
187
|
+
try {
|
|
188
|
+
snapshots = io.listSnapshots?.() ?? defaultSnapshots(platform, effectiveUid(io.getuid));
|
|
189
|
+
} catch {
|
|
190
|
+
return { state: "unknown", reason: "enumeration_failed" };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const byPid = new Map<number, CodexWriterProcess>();
|
|
194
|
+
for (const snapshot of snapshots) {
|
|
195
|
+
if (!Number.isSafeInteger(snapshot.pid) || snapshot.pid <= 0) continue;
|
|
196
|
+
if (!isCodexWriterCommandLine(snapshot.commandLine, snapshot.executable)) continue;
|
|
197
|
+
if (!byPid.has(snapshot.pid)) {
|
|
198
|
+
byPid.set(snapshot.pid, { pid: snapshot.pid, commandLine: snapshot.commandLine });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
state: "ok",
|
|
203
|
+
processes: [...byPid.values()].sort((a, b) => a.pid - b.pid),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import { lstatSync, realpathSync } from "node:fs";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { Database, constants as sqliteConstants } from "bun:sqlite";
|
|
4
|
+
|
|
5
|
+
import { getCodexHome, resolveCodexLogsDbPath } from "../paths";
|
|
6
|
+
import { hasCurrentLogsSchema, inspectCodexLogs, type CodexLogGuardInspection } from "./inspect";
|
|
7
|
+
import { withCodexLogGuardLock, type CodexLogGuardLockOutcome } from "./lock";
|
|
8
|
+
import { sameLogGuardPathIdentity } from "./path-safety";
|
|
9
|
+
import { isSqliteBusy } from "./sqlite-errors";
|
|
10
|
+
import {
|
|
11
|
+
readCodexLogGuardMode,
|
|
12
|
+
writeCodexLogGuardMode,
|
|
13
|
+
type CodexLogGuardMode,
|
|
14
|
+
} from "./policy";
|
|
15
|
+
import {
|
|
16
|
+
listRunningCodexProcesses,
|
|
17
|
+
type CodexWriterProcessCheck,
|
|
18
|
+
} from "./processes";
|
|
19
|
+
|
|
20
|
+
export { type CodexLogGuardMode } from "./policy";
|
|
21
|
+
|
|
22
|
+
const IMMUTABLE_READONLY_FLAGS = sqliteConstants.SQLITE_OPEN_READONLY | sqliteConstants.SQLITE_OPEN_URI;
|
|
23
|
+
const COMPAT_TRIGGER = "opencodex_log_guard_compat_v1";
|
|
24
|
+
const QUIET_TRIGGER = "opencodex_log_guard_quiet_v1";
|
|
25
|
+
const OWNED_TRIGGER_NAMES = [COMPAT_TRIGGER, QUIET_TRIGGER] as const;
|
|
26
|
+
|
|
27
|
+
const CURRENT_LOG_COLUMNS = [
|
|
28
|
+
"id",
|
|
29
|
+
"ts",
|
|
30
|
+
"ts_nanos",
|
|
31
|
+
"level",
|
|
32
|
+
"target",
|
|
33
|
+
"feedback_log_body",
|
|
34
|
+
"module_path",
|
|
35
|
+
"file",
|
|
36
|
+
"line",
|
|
37
|
+
"thread_id",
|
|
38
|
+
"process_uuid",
|
|
39
|
+
"estimated_bytes",
|
|
40
|
+
] as const;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Versioned compatibility policy pinned to the current upstream Codex persistent
|
|
44
|
+
* log filters researched for Log Guard v1. It intentionally preserves unrelated
|
|
45
|
+
* TRACE rows rather than assuming all TRACE diagnostics are disposable.
|
|
46
|
+
*/
|
|
47
|
+
/**
|
|
48
|
+
* Upstream configures these filters with `Targets::with_target`, which matches a
|
|
49
|
+
* target and every module path BENEATH it: `hyper_util` also covers
|
|
50
|
+
* `hyper_util::client::legacy::pool`, and `codex_api::sse` also covers
|
|
51
|
+
* `codex_api::sse::responses`. Exact equality reproduced only the parent, so the
|
|
52
|
+
* high-volume child targets — the ones that actually fill the database — kept
|
|
53
|
+
* writing while compat mode reported itself active.
|
|
54
|
+
*
|
|
55
|
+
* The comparison uses `substr`, not `LIKE`. SQLite's `LIKE` is ASCII
|
|
56
|
+
* case-insensitive by default, so a `LIKE` form would also suppress
|
|
57
|
+
* `HYPER_UTIL::child` — Rust target paths are case-sensitive, and silently
|
|
58
|
+
* dropping a differently-cased target is a wrong answer, not a safe default.
|
|
59
|
+
* `substr(NEW.target, 1, N) = 'X::'` is a plain case-sensitive comparison with
|
|
60
|
+
* no wildcard metacharacters to escape, which also removes the `_`/`%` hazard
|
|
61
|
+
* that `LIKE` would have required an ESCAPE clause to contain.
|
|
62
|
+
*
|
|
63
|
+
* The `'::'` boundary is deliberate and NARROWER than upstream's raw prefix
|
|
64
|
+
* rule: `Targets::with_target("hyper_util")` would also match a sibling crate
|
|
65
|
+
* named `hyper_utilities`. Suppressing an unrelated crate's logs is worse for
|
|
66
|
+
* a guard that silently discards rows, so this matches the module-descendant
|
|
67
|
+
* relation instead. The existing regression pins `hyper_utilities` as
|
|
68
|
+
* preserved.
|
|
69
|
+
*
|
|
70
|
+
* `opentelemetry_sdk` stays exact because upstream registers it with exact
|
|
71
|
+
* equality rather than a prefix filter.
|
|
72
|
+
*/
|
|
73
|
+
function targetOrDescendant(target: string): string {
|
|
74
|
+
const prefix = `${target}::`;
|
|
75
|
+
return `(NEW.target = '${target}' OR substr(NEW.target, 1, ${prefix.length}) = '${prefix}')`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function anyTargetOrDescendant(targets: readonly string[]): string {
|
|
79
|
+
return `(${targets.map(targetOrDescendant).join(" OR ")})`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const COMPAT_TRIGGER_SQL = `CREATE TRIGGER ${COMPAT_TRIGGER}
|
|
83
|
+
BEFORE INSERT ON logs
|
|
84
|
+
WHEN
|
|
85
|
+
NEW.target = 'log'
|
|
86
|
+
OR NEW.target = 'codex_otel.log_only'
|
|
87
|
+
OR NEW.target = 'codex_otel.trace_safe'
|
|
88
|
+
OR NEW.target = 'codex_api::responses_websocket_timing'
|
|
89
|
+
OR NEW.target = 'codex_core::post_sampling_token_estimate'
|
|
90
|
+
OR (${targetOrDescendant("hyper_util")} AND upper(NEW.level) IN ('TRACE', 'DEBUG', 'INFO'))
|
|
91
|
+
OR (${anyTargetOrDescendant(["codex_rmcp_client", "rmcp"])} AND upper(NEW.level) IN ('TRACE', 'DEBUG'))
|
|
92
|
+
OR (${anyTargetOrDescendant([
|
|
93
|
+
"codex_http_client::transport",
|
|
94
|
+
"codex_api::sse",
|
|
95
|
+
"codex_tui::streaming::controller",
|
|
96
|
+
"codex_tui::streaming::table_holdback",
|
|
97
|
+
])} AND upper(NEW.level) = 'TRACE')
|
|
98
|
+
OR (NEW.target = 'opentelemetry_sdk' AND upper(NEW.level) IN ('TRACE', 'DEBUG'))
|
|
99
|
+
BEGIN
|
|
100
|
+
SELECT RAISE(IGNORE);
|
|
101
|
+
END`;
|
|
102
|
+
|
|
103
|
+
const QUIET_TRIGGER_SQL = `CREATE TRIGGER ${QUIET_TRIGGER}
|
|
104
|
+
BEFORE INSERT ON logs
|
|
105
|
+
WHEN upper(NEW.level) = 'TRACE'
|
|
106
|
+
BEGIN
|
|
107
|
+
SELECT RAISE(IGNORE);
|
|
108
|
+
END`;
|
|
109
|
+
|
|
110
|
+
const SQL_BY_MODE: Record<Exclude<CodexLogGuardMode, "off">, string> = {
|
|
111
|
+
compat: COMPAT_TRIGGER_SQL,
|
|
112
|
+
quiet: QUIET_TRIGGER_SQL,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export type CodexLogGuardObservedMode = CodexLogGuardMode | "collision";
|
|
116
|
+
export type CodexLogGuardProtectionState = "off" | "active" | "drifted" | "unsupported" | "unknown";
|
|
117
|
+
|
|
118
|
+
export interface CodexLogGuardProtectionSummary {
|
|
119
|
+
desiredMode: CodexLogGuardMode;
|
|
120
|
+
observedMode: CodexLogGuardObservedMode;
|
|
121
|
+
state: CodexLogGuardProtectionState;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export type CodexLogGuardStatus = CodexLogGuardInspection & {
|
|
125
|
+
protection: CodexLogGuardProtectionSummary;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export type CodexLogGuardMutationError =
|
|
129
|
+
| "unsupported_schema"
|
|
130
|
+
| "codex_running"
|
|
131
|
+
| "process_enumeration_failed"
|
|
132
|
+
| "trigger_collision"
|
|
133
|
+
| "unsafe_path"
|
|
134
|
+
| "busy"
|
|
135
|
+
| "database_error"
|
|
136
|
+
| "config_write_failed";
|
|
137
|
+
|
|
138
|
+
export type CodexLogGuardMutationResult =
|
|
139
|
+
| { ok: true; status: CodexLogGuardStatus }
|
|
140
|
+
| { ok: false; error: CodexLogGuardMutationError };
|
|
141
|
+
|
|
142
|
+
export interface CodexLogGuardProtectionDeps {
|
|
143
|
+
codexHome?: string;
|
|
144
|
+
processCheck?: () => CodexWriterProcessCheck;
|
|
145
|
+
readDesiredMode?: () => CodexLogGuardMode;
|
|
146
|
+
writeDesiredMode?: (mode: CodexLogGuardMode) => void;
|
|
147
|
+
withLock?: <T>(
|
|
148
|
+
canonicalCodexHome: string,
|
|
149
|
+
canonicalLogsDbPath: string,
|
|
150
|
+
work: () => T,
|
|
151
|
+
) => CodexLogGuardLockOutcome<T>;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
interface TriggerRow {
|
|
155
|
+
name: string;
|
|
156
|
+
sql: string | null;
|
|
157
|
+
}
|
|
158
|
+
interface ColumnRow { name: string }
|
|
159
|
+
interface OwnedTriggerSnapshot { name: string; sql: string }
|
|
160
|
+
|
|
161
|
+
type LockedMutationResult =
|
|
162
|
+
| { ok: true }
|
|
163
|
+
| { ok: false; error: CodexLogGuardMutationError };
|
|
164
|
+
|
|
165
|
+
function normalizeSql(sql: string | null | undefined): string {
|
|
166
|
+
return (sql ?? "").trim().replace(/;\s*$/, "").replace(/\s+/g, " ");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function expectedSql(mode: Exclude<CodexLogGuardMode, "off">): string {
|
|
170
|
+
return normalizeSql(SQL_BY_MODE[mode]);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function ownedModeForRow(row: TriggerRow): Exclude<CodexLogGuardMode, "off"> | null {
|
|
174
|
+
if (row.name === COMPAT_TRIGGER && normalizeSql(row.sql) === expectedSql("compat")) return "compat";
|
|
175
|
+
if (row.name === QUIET_TRIGGER && normalizeSql(row.sql) === expectedSql("quiet")) return "quiet";
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function queryReservedTriggers(db: Database): TriggerRow[] {
|
|
180
|
+
const placeholders = OWNED_TRIGGER_NAMES.map(() => "?").join(", ");
|
|
181
|
+
return db.query<TriggerRow, string[]>(
|
|
182
|
+
`SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND name IN (${placeholders}) ORDER BY name`,
|
|
183
|
+
).all(...OWNED_TRIGGER_NAMES);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function observeTriggers(db: Database): CodexLogGuardObservedMode {
|
|
187
|
+
const rows = queryReservedTriggers(db);
|
|
188
|
+
if (rows.length === 0) return "off";
|
|
189
|
+
const modes = rows.map(ownedModeForRow);
|
|
190
|
+
if (modes.some(mode => mode === null)) return "collision";
|
|
191
|
+
const unique = new Set(modes);
|
|
192
|
+
return unique.size === 1 && rows.length === 1 ? modes[0]! : "collision";
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function exactCurrentSchema(db: Database): boolean {
|
|
196
|
+
// Delegates to the inspector's predicate so the locked recheck is exactly as
|
|
197
|
+
// strict as the compatibility report. Column names alone let a schema change
|
|
198
|
+
// between inspection and the locked write slip a mutation onto a database the
|
|
199
|
+
// inspector calls monitor-only.
|
|
200
|
+
return hasCurrentLogsSchema(db);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Read trigger metadata with the same immutable/checkpointed semantics as PR 1
|
|
205
|
+
* diagnostics. A status GET must never participate in Codex's SQLite WAL/SHM
|
|
206
|
+
* protocol or materialise sidecars merely to report protection state.
|
|
207
|
+
*/
|
|
208
|
+
function openReadOnly(databasePath: string): Database {
|
|
209
|
+
const uri = `${pathToFileURL(databasePath).href}?immutable=1`;
|
|
210
|
+
return new Database(uri, IMMUTABLE_READONLY_FLAGS);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function openReadWrite(databasePath: string): Database {
|
|
214
|
+
// READWRITE without CREATE: a missing/moved canonical DB is a refusal, not a
|
|
215
|
+
// reason for OpenCodex to materialise a new foreign database.
|
|
216
|
+
return new Database(databasePath, sqliteConstants.SQLITE_OPEN_READWRITE);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function databasePathIsSafe(databasePath: string): boolean {
|
|
220
|
+
try {
|
|
221
|
+
const stat = lstatSync(databasePath);
|
|
222
|
+
if (!stat.isFile() || stat.isSymbolicLink()) return false;
|
|
223
|
+
return sameLogGuardPathIdentity(realpathSync.native(databasePath), databasePath);
|
|
224
|
+
} catch {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function protectionSummary(
|
|
230
|
+
inspection: CodexLogGuardInspection,
|
|
231
|
+
desiredMode: CodexLogGuardMode,
|
|
232
|
+
observedMode: CodexLogGuardObservedMode,
|
|
233
|
+
): CodexLogGuardProtectionSummary {
|
|
234
|
+
if (inspection.capabilities.protection.state !== "supported") {
|
|
235
|
+
return { desiredMode, observedMode, state: "unsupported" };
|
|
236
|
+
}
|
|
237
|
+
if (observedMode === "collision") return { desiredMode, observedMode, state: "unknown" };
|
|
238
|
+
if (desiredMode === "off" && observedMode === "off") {
|
|
239
|
+
return { desiredMode, observedMode, state: "off" };
|
|
240
|
+
}
|
|
241
|
+
if (desiredMode !== "off" && desiredMode === observedMode) {
|
|
242
|
+
return { desiredMode, observedMode, state: "active" };
|
|
243
|
+
}
|
|
244
|
+
return { desiredMode, observedMode, state: "drifted" };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function inspectionDeps(deps: CodexLogGuardProtectionDeps): { codexHome?: string } {
|
|
248
|
+
return deps.codexHome ? { codexHome: deps.codexHome } : {};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function getCodexLogGuardProtectionStatus(
|
|
252
|
+
deps: CodexLogGuardProtectionDeps = {},
|
|
253
|
+
): CodexLogGuardStatus {
|
|
254
|
+
const codexHome = deps.codexHome ?? getCodexHome();
|
|
255
|
+
const inspection = inspectCodexLogs({ codexHome });
|
|
256
|
+
const databasePath = resolveCodexLogsDbPath({ codexHome });
|
|
257
|
+
const desiredMode = (deps.readDesiredMode ?? readCodexLogGuardMode)();
|
|
258
|
+
let observedMode: CodexLogGuardObservedMode = inspection.schema.state === "compatible" ? "collision" : "off";
|
|
259
|
+
|
|
260
|
+
if (inspection.schema.state === "compatible" && databasePathIsSafe(databasePath)) {
|
|
261
|
+
try {
|
|
262
|
+
const db = openReadOnly(databasePath);
|
|
263
|
+
try { observedMode = observeTriggers(db); }
|
|
264
|
+
finally { db.close(); }
|
|
265
|
+
} catch {
|
|
266
|
+
observedMode = "collision";
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
...inspection,
|
|
272
|
+
protection: protectionSummary(inspection, desiredMode, observedMode),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function successfulMutationStatus(
|
|
277
|
+
codexHome: string,
|
|
278
|
+
mode: CodexLogGuardMode,
|
|
279
|
+
): CodexLogGuardStatus {
|
|
280
|
+
const inspection = inspectCodexLogs({ codexHome });
|
|
281
|
+
const state: CodexLogGuardProtectionState = inspection.capabilities.protection.state === "supported"
|
|
282
|
+
? (mode === "off" ? "off" : "active")
|
|
283
|
+
: "unsupported";
|
|
284
|
+
return {
|
|
285
|
+
...inspection,
|
|
286
|
+
protection: { desiredMode: mode, observedMode: mode, state },
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function processRefusal(check: CodexWriterProcessCheck): CodexLogGuardMutationError | null {
|
|
291
|
+
if (check.state === "unknown") return "process_enumeration_failed";
|
|
292
|
+
if (check.processes.length > 0) return "codex_running";
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function mutateOwnedTrigger(
|
|
297
|
+
databasePath: string,
|
|
298
|
+
mode: CodexLogGuardMode,
|
|
299
|
+
): { ok: true; previousTriggers: readonly OwnedTriggerSnapshot[] } | { ok: false; error: CodexLogGuardMutationError } {
|
|
300
|
+
let db: Database | undefined;
|
|
301
|
+
let transactionOpen = false;
|
|
302
|
+
try {
|
|
303
|
+
if (!databasePathIsSafe(databasePath)) return { ok: false, error: "unsafe_path" };
|
|
304
|
+
db = openReadWrite(databasePath);
|
|
305
|
+
db.exec("PRAGMA busy_timeout = 0; BEGIN IMMEDIATE");
|
|
306
|
+
transactionOpen = true;
|
|
307
|
+
if (!exactCurrentSchema(db)) {
|
|
308
|
+
// Same reasoning as the caller's gate: installing into an unrecognized
|
|
309
|
+
// schema is refused, but removing a trigger we installed ourselves stays
|
|
310
|
+
// available so a schema upgrade cannot strand it.
|
|
311
|
+
if (mode !== "off") {
|
|
312
|
+
db.exec("ROLLBACK");
|
|
313
|
+
transactionOpen = false;
|
|
314
|
+
return { ok: false, error: "unsupported_schema" };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const rows = queryReservedTriggers(db);
|
|
319
|
+
const modes = rows.map(ownedModeForRow);
|
|
320
|
+
if (modes.some(item => item === null)) {
|
|
321
|
+
db.exec("ROLLBACK");
|
|
322
|
+
transactionOpen = false;
|
|
323
|
+
return { ok: false, error: "trigger_collision" };
|
|
324
|
+
}
|
|
325
|
+
const previousTriggers: OwnedTriggerSnapshot[] = rows.map(row => ({
|
|
326
|
+
name: row.name,
|
|
327
|
+
sql: row.sql!,
|
|
328
|
+
}));
|
|
329
|
+
|
|
330
|
+
for (const row of rows) {
|
|
331
|
+
// Name is from our fixed allow-list; never interpolate arbitrary sqlite_master data.
|
|
332
|
+
db.exec(`DROP TRIGGER ${row.name}`);
|
|
333
|
+
}
|
|
334
|
+
if (mode !== "off") db.exec(SQL_BY_MODE[mode]);
|
|
335
|
+
|
|
336
|
+
const observed = observeTriggers(db);
|
|
337
|
+
if (observed !== mode) throw new Error("log_guard_trigger_verification_failed");
|
|
338
|
+
db.exec("COMMIT");
|
|
339
|
+
transactionOpen = false;
|
|
340
|
+
return { ok: true, previousTriggers };
|
|
341
|
+
} catch (error) {
|
|
342
|
+
if (transactionOpen) {
|
|
343
|
+
try { db?.exec("ROLLBACK"); } catch { /* close releases the transaction */ }
|
|
344
|
+
}
|
|
345
|
+
if (isSqliteBusy(error)) return { ok: false, error: "busy" };
|
|
346
|
+
return { ok: false, error: "database_error" };
|
|
347
|
+
} finally {
|
|
348
|
+
try { db?.close(); } catch { /* mutation already settled */ }
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function restoreOwnedTriggers(databasePath: string, previousTriggers: readonly OwnedTriggerSnapshot[]): void {
|
|
353
|
+
// Best-effort compensation only. Failure is deliberately not hidden by
|
|
354
|
+
// claiming success; the caller returns config_write_failed and status will
|
|
355
|
+
// expose any remaining drift on the next read.
|
|
356
|
+
let db: Database | undefined;
|
|
357
|
+
let transactionOpen = false;
|
|
358
|
+
try {
|
|
359
|
+
if (!databasePathIsSafe(databasePath)) return;
|
|
360
|
+
db = openReadWrite(databasePath);
|
|
361
|
+
db.exec("PRAGMA busy_timeout = 0; BEGIN IMMEDIATE");
|
|
362
|
+
transactionOpen = true;
|
|
363
|
+
if (!exactCurrentSchema(db)) {
|
|
364
|
+
db.exec("ROLLBACK");
|
|
365
|
+
transactionOpen = false;
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const current = queryReservedTriggers(db);
|
|
370
|
+
if (current.some(row => ownedModeForRow(row) === null)) {
|
|
371
|
+
db.exec("ROLLBACK");
|
|
372
|
+
transactionOpen = false;
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
for (const row of current) db.exec(`DROP TRIGGER ${row.name}`);
|
|
376
|
+
|
|
377
|
+
for (const trigger of previousTriggers) {
|
|
378
|
+
if (ownedModeForRow(trigger) === null) throw new Error("invalid_owned_trigger_snapshot");
|
|
379
|
+
db.exec(trigger.sql);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const restored = queryReservedTriggers(db);
|
|
383
|
+
const expected = new Map(previousTriggers.map(row => [row.name, normalizeSql(row.sql)]));
|
|
384
|
+
if (restored.length !== previousTriggers.length
|
|
385
|
+
|| restored.some(row => expected.get(row.name) !== normalizeSql(row.sql))) {
|
|
386
|
+
throw new Error("log_guard_trigger_restore_verification_failed");
|
|
387
|
+
}
|
|
388
|
+
db.exec("COMMIT");
|
|
389
|
+
transactionOpen = false;
|
|
390
|
+
} catch {
|
|
391
|
+
if (transactionOpen) {
|
|
392
|
+
try { db?.exec("ROLLBACK"); } catch { /* close releases the transaction */ }
|
|
393
|
+
}
|
|
394
|
+
} finally {
|
|
395
|
+
try { db?.close(); } catch { /* compensation already settled */ }
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function performMutation(
|
|
400
|
+
requestedMode: CodexLogGuardMode | (() => CodexLogGuardMode),
|
|
401
|
+
deps: CodexLogGuardProtectionDeps,
|
|
402
|
+
): CodexLogGuardMutationResult {
|
|
403
|
+
const codexHome = deps.codexHome ?? getCodexHome();
|
|
404
|
+
const inspection = inspectCodexLogs({ codexHome });
|
|
405
|
+
const databasePath = resolveCodexLogsDbPath({ codexHome });
|
|
406
|
+
// Removal must not be gated on the schema still being recognized. A Codex
|
|
407
|
+
// upgrade that changes the logs schema would otherwise strand an installed
|
|
408
|
+
// trigger: Protect is refused (correctly), but so is Disable, leaving the
|
|
409
|
+
// user with an active OpenCodex trigger and no in-product way to remove it.
|
|
410
|
+
// Installing into an unknown schema stays refused; taking our own trigger
|
|
411
|
+
// back out is always allowed.
|
|
412
|
+
const removingProtection = typeof requestedMode !== "function" && requestedMode === "off";
|
|
413
|
+
if (!removingProtection && inspection.capabilities.protection.state !== "supported") {
|
|
414
|
+
return { ok: false, error: "unsupported_schema" };
|
|
415
|
+
}
|
|
416
|
+
if (!databasePathIsSafe(databasePath)) return { ok: false, error: "unsafe_path" };
|
|
417
|
+
|
|
418
|
+
const checkProcesses = deps.processCheck ?? listRunningCodexProcesses;
|
|
419
|
+
const firstRefusal = processRefusal(checkProcesses());
|
|
420
|
+
if (firstRefusal) return { ok: false, error: firstRefusal };
|
|
421
|
+
|
|
422
|
+
const withLock = deps.withLock ?? withCodexLogGuardLock;
|
|
423
|
+
const writeDesired = deps.writeDesiredMode ?? writeCodexLogGuardMode;
|
|
424
|
+
let locked: CodexLogGuardLockOutcome<LockedMutationResult>;
|
|
425
|
+
// Repair passes a resolver instead of a value: its target mode must be read
|
|
426
|
+
// INSIDE L. Reading it before acquiring the lock let a Disable complete in
|
|
427
|
+
// the gap, after which the stale Repair reinstalled protection and reported
|
|
428
|
+
// success — the caller saw an honest "off" and got "compat".
|
|
429
|
+
let effectiveMode: CodexLogGuardMode = typeof requestedMode === "function" ? "off" : requestedMode;
|
|
430
|
+
try {
|
|
431
|
+
locked = withLock(codexHome, databasePath, () => {
|
|
432
|
+
// Recheck after acquiring L so a Codex process that starts during lock
|
|
433
|
+
// acquisition cannot race the foreign-schema mutation.
|
|
434
|
+
const secondRefusal = processRefusal(checkProcesses());
|
|
435
|
+
if (secondRefusal) return { ok: false, error: secondRefusal };
|
|
436
|
+
|
|
437
|
+
effectiveMode = typeof requestedMode === "function" ? requestedMode() : requestedMode;
|
|
438
|
+
const mutation = mutateOwnedTrigger(databasePath, effectiveMode);
|
|
439
|
+
if (!mutation.ok) return mutation;
|
|
440
|
+
|
|
441
|
+
// Desired state belongs to the same logical transition as the trigger.
|
|
442
|
+
// Keep L held through this write so another OpenCodex process cannot
|
|
443
|
+
// interleave a different mode between the DB commit and config commit.
|
|
444
|
+
try {
|
|
445
|
+
writeDesired(effectiveMode);
|
|
446
|
+
} catch {
|
|
447
|
+
restoreOwnedTriggers(databasePath, mutation.previousTriggers);
|
|
448
|
+
return { ok: false, error: "config_write_failed" as const };
|
|
449
|
+
}
|
|
450
|
+
return { ok: true };
|
|
451
|
+
});
|
|
452
|
+
} catch {
|
|
453
|
+
return { ok: false, error: "database_error" };
|
|
454
|
+
}
|
|
455
|
+
if (locked.kind === "unavailable") {
|
|
456
|
+
return {
|
|
457
|
+
ok: false,
|
|
458
|
+
error: locked.reason === "busy"
|
|
459
|
+
? "busy"
|
|
460
|
+
: locked.reason === "unsafe-path" ? "unsafe_path" : "database_error",
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
if (!locked.value.ok) return locked.value;
|
|
464
|
+
|
|
465
|
+
// Report the mode that was actually applied under the lock, not the one the
|
|
466
|
+
// caller guessed before acquiring it.
|
|
467
|
+
return { ok: true, status: successfulMutationStatus(codexHome, effectiveMode) };
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export function protectCodexLogs(
|
|
471
|
+
mode: Exclude<CodexLogGuardMode, "off">,
|
|
472
|
+
deps: CodexLogGuardProtectionDeps = {},
|
|
473
|
+
): CodexLogGuardMutationResult {
|
|
474
|
+
return performMutation(mode, deps);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export function unprotectCodexLogs(
|
|
478
|
+
deps: CodexLogGuardProtectionDeps = {},
|
|
479
|
+
): CodexLogGuardMutationResult {
|
|
480
|
+
return performMutation("off", deps);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export function repairCodexLogGuardProtection(
|
|
484
|
+
deps: CodexLogGuardProtectionDeps = {},
|
|
485
|
+
): CodexLogGuardMutationResult {
|
|
486
|
+
// Resolve the desired mode under the lock (see performMutation): a Disable
|
|
487
|
+
// that lands between the read and the lock must win, not be silently undone.
|
|
488
|
+
return performMutation(() => (deps.readDesiredMode ?? readCodexLogGuardMode)(), deps);
|
|
489
|
+
}
|