@iislee/opencodex 2.11.0 → 2.35.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 +117 -22
- package/bin/ocx.mjs +245 -88
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -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 +21 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +475 -62
- package/src/adapters/base.ts +82 -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 +637 -0
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +308 -0
- package/src/adapters/cursor/cursor-errors.ts +144 -6
- package/src/adapters/cursor/discovery.ts +122 -14
- package/src/adapters/cursor/effort-map.ts +106 -4
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- 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 +180 -59
- package/src/adapters/cursor/live-transport.ts +623 -170
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- 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 +5 -3
- package/src/adapters/cursor/native-exec.ts +121 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +447 -75
- package/src/adapters/cursor/request-builder.ts +271 -35
- package/src/adapters/cursor/tool-definitions.ts +252 -12
- package/src/adapters/cursor/tool-result-normalize.ts +115 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +48 -1
- package/src/adapters/cursor.ts +336 -39
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +713 -41
- package/src/adapters/google-antigravity-wire.ts +48 -10
- package/src/adapters/google-errors.ts +44 -12
- 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 +742 -86
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- 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 +65 -17
- package/src/adapters/kiro.ts +270 -73
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1377 -344
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +1164 -64
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +105 -21
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +186 -0
- package/src/bridge.ts +512 -92
- package/src/chat/inbound.ts +57 -20
- package/src/chat/outbound.ts +97 -33
- package/src/claude/agents-inject.ts +40 -10
- package/src/claude/context-windows.ts +37 -14
- 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 +88 -21
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +150 -19
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +648 -28
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +97 -19
- package/src/cli/agent.ts +53 -2
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +60 -15
- package/src/cli/claude.ts +129 -35
- 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 +821 -0
- package/src/cli/doctor.ts +440 -56
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +34 -274
- package/src/cli/index.ts +417 -528
- package/src/cli/init.ts +5 -17
- package/src/cli/inspect.ts +230 -0
- 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 +130 -2
- package/src/cli/models.ts +118 -16
- package/src/cli/observe.ts +123 -11
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +34 -3
- package/src/cli/provider.ts +25 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +476 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +57 -10
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +51 -4
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +137 -18
- package/src/cli/version-skew.ts +46 -0
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1442 -23
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +35 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +84 -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 +570 -107
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +893 -246
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +348 -32
- package/src/codex/autostart-health.ts +24 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +86 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +138 -32
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +555 -45
- package/src/codex/catalog/native-models.ts +75 -0
- package/src/codex/catalog/parsing.ts +269 -30
- package/src/codex/catalog/provider-fetch.ts +1626 -144
- package/src/codex/catalog/sync.ts +1576 -187
- 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 +386 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +676 -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/desktop-app-restart.ts +355 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +436 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +30 -24
- package/src/codex/history-provider.ts +1016 -235
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +223 -0
- package/src/codex/inject-coordination.ts +373 -0
- package/src/codex/inject.ts +1114 -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 +81 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +524 -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 +360 -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 +622 -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 +675 -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 +1313 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +264 -21
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +514 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1065 -31
- package/src/codex/subagent-model-fallback.ts +437 -43
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +720 -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 +47 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +91 -9
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-name.ts +24 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +1713 -709
- package/src/generated/compatibility-version.json +3324 -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 +157 -33
- package/src/images/plan.ts +24 -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-policy.ts +160 -0
- package/src/integrations/ownership.ts +155 -0
- package/src/integrations/registry.ts +166 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +433 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +715 -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/admin-secrets.ts +24 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +167 -11
- 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 +26 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +80 -5
- 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/package-tree-integrity.ts +101 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +6 -2
- 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/service-secrets.ts +15 -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 +253 -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 +30 -4
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +462 -71
- package/src/oauth/key-providers.ts +25 -0
- package/src/oauth/kimi.ts +25 -3
- package/src/oauth/kiro.ts +95 -6
- package/src/oauth/local-token-detect.ts +13 -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/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +133 -26
- package/src/oauth/token-guardian.ts +9 -3
- package/src/oauth/types.ts +15 -0
- 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/auto-compact-budget.ts +65 -0
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +176 -0
- package/src/providers/context-cap.ts +26 -8
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +281 -6
- package/src/providers/fastwire.ts +507 -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 +35 -2
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/new-model-policy.ts +146 -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 +30 -0
- package/src/providers/quota.ts +1379 -40
- package/src/providers/registry.ts +1545 -112
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +94 -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 +32 -4
- package/src/reasoning-effort.ts +68 -3
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +384 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +435 -0
- package/src/responses/parser.ts +225 -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 +504 -0
- package/src/responses/schema.ts +12 -2
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +659 -31
- package/src/responses/thought-signature-replay.ts +364 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +405 -31
- package/src/routing/analytics.ts +379 -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 +155 -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 +289 -48
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +154 -72
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +445 -0
- package/src/server/claude-messages.ts +160 -44
- 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 +1021 -190
- package/src/server/lifecycle.ts +336 -14
- 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 +433 -116
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +64 -25
- package/src/server/management/config-routes.ts +511 -51
- package/src/server/management/context.ts +89 -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 +127 -52
- package/src/server/management/model-routes.ts +461 -133
- package/src/server/management/model-rows.ts +167 -0
- package/src/server/management/native-integration-routes.ts +770 -0
- package/src/server/management/oauth-account-routes.ts +90 -8
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +836 -159
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/route-registry.ts +332 -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 +289 -33
- 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 +126 -23
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +161 -10
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +221 -54
- package/src/server/relay.ts +479 -87
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +298 -31
- 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/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +239 -36
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +444 -72
- package/src/server/responses/core.ts +3579 -436
- package/src/server/responses/empty-completion-guard.ts +333 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +89 -99
- package/src/server/responses/input-admission.ts +187 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +343 -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 +381 -0
- package/src/server/responses-custom-tool-repair.ts +363 -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 +363 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +376 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +319 -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 +53 -40
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1153 -94
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- 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 +911 -0
- package/src/types/provider.ts +614 -0
- package/src/types/request.ts +397 -0
- package/src/types/tools.ts +236 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +107 -1237
- package/src/update/index.ts +37 -23
- package/src/update/job.ts +445 -68
- 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 +266 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +287 -16
- package/src/usage/log.ts +666 -50
- package/src/usage/summary.ts +388 -17
- 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 +27 -16
- 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 +119 -0
- package/src/web-search/executor.ts +21 -6
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +284 -54
- package/src/web-search/parse.ts +125 -30
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/xai-executor.ts +219 -0
- package/gui/dist/assets/index-DTpMHS4F.js +0 -67
- package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
- package/src/cli/internal-dispatch.ts +0 -20
package/src/codex/features.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* features.ts — codex feature-flag view for $CODEX_HOME/config.toml.
|
|
3
3
|
*
|
|
4
|
-
* Scope boundary: this module mirrors
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Scope boundary: this module mirrors only the flags opencodex has to READ
|
|
5
|
+
* directly from config.toml:
|
|
6
|
+
* - `multi_agent_v2`, because opencodex migrates its concurrency value across
|
|
7
|
+
* the v1/v2 boundary and exposes the multi-agent config surface;
|
|
8
|
+
* - `default_mode_request_user_input` (Codex Auth page toggle), because the
|
|
9
|
+
* management API needs a live reader for the flag it manages.
|
|
10
|
+
* Every other upstream feature flag is delegated to the native `codex features`
|
|
11
|
+
* command (see src/cli/v2.ts) and must not be hardcoded here.
|
|
9
12
|
*
|
|
10
13
|
* Upstream reshapes flags freely: in the 1f0566d3f..5a1097ed2 range alone,
|
|
11
14
|
* `code_mode_host` changed from a boolean to a table (it is Stage::Stable and
|
|
@@ -27,11 +30,17 @@
|
|
|
27
30
|
* catalog.ts:40-54) so tests can point fixtures via env or the explicit
|
|
28
31
|
* `configPath` parameter without fighting the module-load-time const in paths.ts.
|
|
29
32
|
*/
|
|
30
|
-
import { existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
31
|
-
import { join, resolve } from "node:path";
|
|
33
|
+
import { existsSync, readFileSync, statSync, unlinkSync } from "node:fs";
|
|
34
|
+
import { delimiter, dirname, join, resolve } from "node:path";
|
|
35
|
+
import { createRequire } from "node:module";
|
|
32
36
|
import { realpathSync } from "node:fs";
|
|
33
|
-
import { atomicWriteFile, expandUserPath } from "../config";
|
|
37
|
+
import { AtomicWriteResidualTempError, AtomicWriteSecretResidualError, atomicWriteFile, expandUserPath, getConfigDir } from "../config";
|
|
38
|
+
import { forgetEphemeralSecretPath } from "../lib/windows-secret-acl";
|
|
34
39
|
import { CODEX_CONFIG_PATH } from "./paths";
|
|
40
|
+
import { resolveAndPersistCodexRuntime } from "./runtime";
|
|
41
|
+
|
|
42
|
+
/** Upstream codex-rs feature key: allow `request_user_input` in Default mode. */
|
|
43
|
+
export const DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY = "default_mode_request_user_input";
|
|
35
44
|
|
|
36
45
|
// EOL preservation, local copies of inject.ts dominantEol/applyEol: importing
|
|
37
46
|
// inject here would close a module cycle (features -> inject -> catalog -> features).
|
|
@@ -55,7 +64,7 @@ function mergeTrailingComments(existing?: string, migrated?: string): string {
|
|
|
55
64
|
return `${existing}; ${migratedText}`;
|
|
56
65
|
}
|
|
57
66
|
|
|
58
|
-
function activeCodexConfigPath(): string {
|
|
67
|
+
export function activeCodexConfigPath(): string {
|
|
59
68
|
const raw = process.env.CODEX_HOME?.trim();
|
|
60
69
|
if (!raw) return CODEX_CONFIG_PATH;
|
|
61
70
|
const path = resolve(expandUserPath(raw));
|
|
@@ -76,7 +85,23 @@ function readConfigText(configPath?: string): string | null {
|
|
|
76
85
|
}
|
|
77
86
|
}
|
|
78
87
|
|
|
79
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* Body lines of a TOML table `[header]` up to (not including) the next table header.
|
|
90
|
+
*
|
|
91
|
+
* The implementation body is deliberately unchanged by #1295 — only this comment
|
|
92
|
+
* is new. The scanner is line-based and string-unaware, so it ends the table at
|
|
93
|
+
* the first line matching `/^\s*\[/` even inside a multi-line value. Twenty call
|
|
94
|
+
* sites in this file consume its output, most of them by matching a regex
|
|
95
|
+
* against the returned text, so widening that text changes what they match. An
|
|
96
|
+
* earlier attempt at #1295 made this scanner string-aware and thereby gave
|
|
97
|
+
* `getAgentsEnabled`, `getAgentsMaxDepth`, and `getMaxConcurrentThreads` three
|
|
98
|
+
* new wrong answers.
|
|
99
|
+
*
|
|
100
|
+
* The readers that matter for #1295 use a real TOML parse instead (see
|
|
101
|
+
* `parsedTomlTable`). This stays as the fallback for documents that do not
|
|
102
|
+
* parse, and as the reader for the remaining call sites until they are migrated
|
|
103
|
+
* the same way.
|
|
104
|
+
*/
|
|
80
105
|
function tomlTableBody(content: string, header: string): string | null {
|
|
81
106
|
const lines = content.split("\n");
|
|
82
107
|
const escaped = header.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -102,8 +127,57 @@ function tomlBoolInBody(body: string, key: string): boolean | null {
|
|
|
102
127
|
*/
|
|
103
128
|
export function isMultiAgentV2Enabled(configPath?: string): boolean {
|
|
104
129
|
const content = readConfigText(configPath);
|
|
130
|
+
return multiAgentV2EnabledFromConfigText(content);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Parse `multi_agent_v2` from caller-owned config.toml text without consulting disk. */
|
|
134
|
+
export function multiAgentV2EnabledFromConfigText(content: string | null): boolean {
|
|
105
135
|
if (content === null) return false;
|
|
106
136
|
|
|
137
|
+
// Prefer a real parse. The hand-written table scanner below cannot distinguish
|
|
138
|
+
// an assignment from prose that looks like one — a `"""` value containing the
|
|
139
|
+
// line `enabled = true` reads as the key itself — and TOML has enough value
|
|
140
|
+
// shapes (multi-line arrays opening on the next line, escapes, comments) that
|
|
141
|
+
// each near-miss costs another special case (#1295).
|
|
142
|
+
//
|
|
143
|
+
// The scanner remains only for a document `Bun.TOML.parse` rejects. That is a
|
|
144
|
+
// statement about Bun's parser, not about Codex's — the two are separate
|
|
145
|
+
// implementations and no compatibility evidence is claimed here, so a document
|
|
146
|
+
// Bun rejects may still be one Codex loads. The fallback is therefore
|
|
147
|
+
// best-effort and inherits the ambiguity above. It exists because reporting a
|
|
148
|
+
// feature as disabled on account of an unreadable file presents a failure as
|
|
149
|
+
// a state.
|
|
150
|
+
const parsed = parsedTomlTable(content, "features");
|
|
151
|
+
if (parsed !== null) {
|
|
152
|
+
const table = plainTomlRecord(parsed.multi_agent_v2);
|
|
153
|
+
if (table !== null) return table.enabled === true;
|
|
154
|
+
if (typeof parsed.multi_agent_v2 === "boolean") return parsed.multi_agent_v2;
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Bun 1.4 enforces TOML's "value must begin on the assignment line" rule that
|
|
159
|
+
// 1.3.14 did not, so `hint =` followed by `[` on the next line now fails the
|
|
160
|
+
// real parse and reaches the line-based fallback below — which reads that `[`
|
|
161
|
+
// as a table header and truncates the table before `enabled`. Codex's own
|
|
162
|
+
// parser accepts the document, so answering "disabled" would report a parser
|
|
163
|
+
// disagreement as a feature state (#1295, #1691).
|
|
164
|
+
//
|
|
165
|
+
// Joining a dangling `=` to the line that follows is the smallest repair that
|
|
166
|
+
// keeps the scanner untouched: widening `tomlTableBody` to be string-aware is
|
|
167
|
+
// what previously broke `getAgentsEnabled`, `getAgentsMaxDepth`, and
|
|
168
|
+
// `getMaxConcurrentThreads` (see its comment). If the joined document parses,
|
|
169
|
+
// that answer is authoritative; if it does not, nothing is lost.
|
|
170
|
+
const joined = joinDanglingTomlAssignments(content);
|
|
171
|
+
if (joined !== content) {
|
|
172
|
+
const reparsed = parsedTomlTable(joined, "features");
|
|
173
|
+
if (reparsed !== null) {
|
|
174
|
+
const table = plainTomlRecord(reparsed.multi_agent_v2);
|
|
175
|
+
if (table !== null) return table.enabled === true;
|
|
176
|
+
if (typeof reparsed.multi_agent_v2 === "boolean") return reparsed.multi_agent_v2;
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
107
181
|
const table = tomlTableBody(content, "features.multi_agent_v2");
|
|
108
182
|
if (table !== null) {
|
|
109
183
|
const enabled = tomlBoolInBody(table, "enabled");
|
|
@@ -127,6 +201,86 @@ export function isMultiAgentV2Enabled(configPath?: string): boolean {
|
|
|
127
201
|
return false;
|
|
128
202
|
}
|
|
129
203
|
|
|
204
|
+
function plainTomlRecord(value: unknown): Record<string, unknown> | null {
|
|
205
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
206
|
+
? value as Record<string, unknown>
|
|
207
|
+
: null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Join `key =` to the following line when the value was written on the next
|
|
212
|
+
* line, so a parser enforcing TOML's same-line rule can read the document.
|
|
213
|
+
*
|
|
214
|
+
* Bun 1.3.14 accepted this shape; Bun 1.4 rejects it, correctly — TOML requires
|
|
215
|
+
* the value to begin on the assignment line. Codex's parser still accepts it, so
|
|
216
|
+
* this exists to keep the two readers agreeing rather than to endorse the shape.
|
|
217
|
+
*
|
|
218
|
+
* Deliberately narrow: it only acts on a line whose LAST non-comment character
|
|
219
|
+
* is `=`, which cannot occur in a valid assignment. Lines inside multi-line
|
|
220
|
+
* strings are left alone — a `"""` body line ending in `=` would be rewritten,
|
|
221
|
+
* but the result is only used when it PARSES, and the unmodified document is
|
|
222
|
+
* always tried first, so a wrong join cannot displace a correct read.
|
|
223
|
+
*/
|
|
224
|
+
function joinDanglingTomlAssignments(content: string): string {
|
|
225
|
+
const lines = content.split("\n");
|
|
226
|
+
const out: string[] = [];
|
|
227
|
+
for (let i = 0; i < lines.length; i++) {
|
|
228
|
+
const line = lines[i]!;
|
|
229
|
+
// A dangling assignment: trailing `=` with nothing after it on this line.
|
|
230
|
+
if (/^[^#]*[^=!<>]=\s*$/.test(line) && i + 1 < lines.length) {
|
|
231
|
+
let j = i + 1;
|
|
232
|
+
// Skip blank and comment-only lines between the `=` and its value.
|
|
233
|
+
while (j < lines.length && /^\s*(?:#.*)?$/.test(lines[j]!)) j++;
|
|
234
|
+
if (j < lines.length) {
|
|
235
|
+
out.push(`${line.replace(/\s*$/, "")} ${lines[j]!.replace(/^\s*/, "")}`);
|
|
236
|
+
i = j;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
out.push(line);
|
|
241
|
+
}
|
|
242
|
+
return out.join("\n");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* A top-level table from a full TOML parse, or null when the document does not
|
|
247
|
+
* parse. A parsed document with no such table yields `{}` rather than null: that
|
|
248
|
+
* is a real answer ("no keys"), while null means "could not read, fall back".
|
|
249
|
+
*/
|
|
250
|
+
function parsedTomlTable(content: string, name: string): Record<string, unknown> | null {
|
|
251
|
+
const toml = (globalThis as { Bun?: { TOML?: { parse(input: string): unknown } } }).Bun?.TOML;
|
|
252
|
+
if (!toml) return null;
|
|
253
|
+
try {
|
|
254
|
+
const root = plainTomlRecord(toml.parse(content));
|
|
255
|
+
if (root === null) return null;
|
|
256
|
+
return plainTomlRecord(root[name]) ?? {};
|
|
257
|
+
} catch {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* TRUE when the codex `default_mode_request_user_input` feature is enabled in
|
|
265
|
+
* config.toml — lets a Default-mode session pause and ask the user questions
|
|
266
|
+
* through `request_user_input` (upstream FeatureSpec: under development,
|
|
267
|
+
* default_enabled = false). Recognizes the shipped boolean form
|
|
268
|
+
* `[features] default_mode_request_user_input = true`.
|
|
269
|
+
* Missing file/key -> false.
|
|
270
|
+
*/
|
|
271
|
+
export function isDefaultModeRequestUserInputEnabled(configPath?: string): boolean {
|
|
272
|
+
const content = readConfigText(configPath);
|
|
273
|
+
if (content === null) return false;
|
|
274
|
+
// Same reason as the v2 reader: a `"""` value whose prose contains
|
|
275
|
+
// `default_mode_request_user_input = true` is not an assignment, and a raw
|
|
276
|
+
// regex over the table body cannot tell the difference (#1295).
|
|
277
|
+
const parsed = parsedTomlTable(content, "features");
|
|
278
|
+
if (parsed !== null) return parsed[DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY] === true;
|
|
279
|
+
const features = tomlTableBody(content, "features");
|
|
280
|
+
if (features === null) return false;
|
|
281
|
+
return tomlBoolInBody(features, DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY) === true;
|
|
282
|
+
}
|
|
283
|
+
|
|
130
284
|
/**
|
|
131
285
|
* TRUE when config.toml still carries `[agents] max_threads` — codex-rs REFUSES to
|
|
132
286
|
* boot with that key while multi_agent_v2 is enabled ("agents.max_threads cannot be
|
|
@@ -136,6 +290,8 @@ export function isMultiAgentV2Enabled(configPath?: string): boolean {
|
|
|
136
290
|
export function hasAgentsMaxThreads(configPath?: string): boolean {
|
|
137
291
|
const content = readConfigText(configPath);
|
|
138
292
|
if (content === null) return false;
|
|
293
|
+
const parsed = parsedTomlTable(content, "agents");
|
|
294
|
+
if (parsed !== null) return Object.hasOwn(parsed, "max_threads");
|
|
139
295
|
const agents = tomlTableBody(content, "agents");
|
|
140
296
|
if (agents === null) return false;
|
|
141
297
|
return /^\s*max_threads\s*=/m.test(agents);
|
|
@@ -145,6 +301,11 @@ export function hasAgentsMaxThreads(configPath?: string): boolean {
|
|
|
145
301
|
export function getAgentsMaxThreads(configPath?: string): number | null {
|
|
146
302
|
const content = readConfigText(configPath);
|
|
147
303
|
if (content === null) return null;
|
|
304
|
+
const parsed = parsedTomlTable(content, "agents");
|
|
305
|
+
if (parsed !== null) {
|
|
306
|
+
const value = parsed.max_threads;
|
|
307
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1 ? value : null;
|
|
308
|
+
}
|
|
148
309
|
const agents = tomlTableBody(content, "agents");
|
|
149
310
|
if (agents === null) return null;
|
|
150
311
|
const m = agents.match(/^\s*max_threads\s*=\s*(\d+)\s*(?:#.*)?$/m);
|
|
@@ -350,16 +511,46 @@ function encodeTomlBasicString(value: string): string {
|
|
|
350
511
|
*/
|
|
351
512
|
function decodeTomlStringToken(token: string): string | null {
|
|
352
513
|
if (token.length < 2) return null;
|
|
514
|
+
// Multi-line literal string: `'''...'''` verbatim (backslashes not special).
|
|
515
|
+
if (token.startsWith("'''")) {
|
|
516
|
+
return token.endsWith("'''") && token.length >= 6
|
|
517
|
+
? normalizeTomlMultilineBody(token.slice(3, -3))
|
|
518
|
+
: null;
|
|
519
|
+
}
|
|
520
|
+
// Multi-line basic string: `"""..."""` with escapes.
|
|
521
|
+
if (token.startsWith('"""')) {
|
|
522
|
+
if (!token.endsWith('"""') || token.length < 6) return null;
|
|
523
|
+
return decodeBasicStringBody(normalizeTomlMultilineBody(token.slice(3, -3)), true);
|
|
524
|
+
}
|
|
353
525
|
if (token.startsWith("'")) {
|
|
354
526
|
return token.endsWith("'") ? token.slice(1, -1) : null;
|
|
355
527
|
}
|
|
356
528
|
if (!token.startsWith('"') || !token.endsWith('"')) return null;
|
|
357
|
-
|
|
529
|
+
return decodeBasicStringBody(token.slice(1, -1));
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/** Apply TOML's multi-line newline rules before string-body decoding. */
|
|
533
|
+
function normalizeTomlMultilineBody(body: string): string {
|
|
534
|
+
const normalized = body.replace(/\r\n/g, "\n");
|
|
535
|
+
return normalized.startsWith("\n") ? normalized.slice(1) : normalized;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/** Unescape the body of a TOML basic string (single- or multi-line). */
|
|
539
|
+
function decodeBasicStringBody(body: string, multiline = false): string | null {
|
|
358
540
|
let out = "";
|
|
359
541
|
for (let i = 0; i < body.length; i++) {
|
|
360
542
|
const ch = body[i];
|
|
361
543
|
if (ch !== "\\") { out += ch; continue; }
|
|
362
544
|
const esc = body[++i];
|
|
545
|
+
if (multiline) {
|
|
546
|
+
let newline = i;
|
|
547
|
+
while (body[newline] === " " || body[newline] === "\t") newline++;
|
|
548
|
+
if (body[newline] === "\n") {
|
|
549
|
+
i = newline;
|
|
550
|
+
while (body[i + 1] === " " || body[i + 1] === "\t" || body[i + 1] === "\n") i++;
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
363
554
|
switch (esc) {
|
|
364
555
|
case "\\": out += "\\"; break;
|
|
365
556
|
case '"': out += '"'; break;
|
|
@@ -398,6 +589,28 @@ function scanTomlValueEnd(text: string, start: number): number {
|
|
|
398
589
|
while (i < text.length && (text[i] === " " || text[i] === "\t")) i++;
|
|
399
590
|
const first = text[i];
|
|
400
591
|
if (first === '"') {
|
|
592
|
+
// Multi-line basic string: `"""..."""`. The closing delimiter is a triple
|
|
593
|
+
// quote; a single or double quote inside the body does not end the token.
|
|
594
|
+
if (text[i + 1] === '"' && text[i + 2] === '"') {
|
|
595
|
+
i += 3;
|
|
596
|
+
while (i < text.length) {
|
|
597
|
+
if (text[i] === "\\") { i += 2; continue; }
|
|
598
|
+
if (text[i] === '"' && text[i + 1] === '"' && text[i + 2] === '"') {
|
|
599
|
+
// TOML permits up to two quotes immediately inside the closing
|
|
600
|
+
// delimiter, so a 4- or 5-quote run means the extra one or two
|
|
601
|
+
// quotes are part of the value. Consume the full valid run and
|
|
602
|
+
// keep the surplus quotes in the body.
|
|
603
|
+
let end = i + 3;
|
|
604
|
+
if (text[end] === '"') {
|
|
605
|
+
end++;
|
|
606
|
+
if (text[end] === '"') end++;
|
|
607
|
+
}
|
|
608
|
+
return end;
|
|
609
|
+
}
|
|
610
|
+
i++;
|
|
611
|
+
}
|
|
612
|
+
return text.length;
|
|
613
|
+
}
|
|
401
614
|
i++;
|
|
402
615
|
while (i < text.length) {
|
|
403
616
|
if (text[i] === "\\") { i += 2; continue; }
|
|
@@ -407,6 +620,24 @@ function scanTomlValueEnd(text: string, start: number): number {
|
|
|
407
620
|
return text.length;
|
|
408
621
|
}
|
|
409
622
|
if (first === "'") {
|
|
623
|
+
// Multi-line literal string: `'''...'''`.
|
|
624
|
+
if (text[i + 1] === "'" && text[i + 2] === "'") {
|
|
625
|
+
i += 3;
|
|
626
|
+
while (i < text.length) {
|
|
627
|
+
if (text[i] === "'" && text[i + 1] === "'" && text[i + 2] === "'") {
|
|
628
|
+
// Same as multi-line basic: up to two surplus single quotes can
|
|
629
|
+
// precede the closing delimiter and remain part of the value.
|
|
630
|
+
let end = i + 3;
|
|
631
|
+
if (text[end] === "'") {
|
|
632
|
+
end++;
|
|
633
|
+
if (text[end] === "'") end++;
|
|
634
|
+
}
|
|
635
|
+
return end;
|
|
636
|
+
}
|
|
637
|
+
i++;
|
|
638
|
+
}
|
|
639
|
+
return text.length;
|
|
640
|
+
}
|
|
410
641
|
const close = text.indexOf("'", i + 1);
|
|
411
642
|
return close === -1 ? text.length : close + 1;
|
|
412
643
|
}
|
|
@@ -445,6 +676,40 @@ function findInlineTableEnd(text: string, openIdx: number): number {
|
|
|
445
676
|
|
|
446
677
|
interface InlineEntry { keyStart: number; valueStart: number; valueEnd: number }
|
|
447
678
|
|
|
679
|
+
/** Locate a top-level assignment while skipping complete (possibly multiline) values. */
|
|
680
|
+
function findTomlAssignment(text: string, key: string): InlineEntry | null {
|
|
681
|
+
let lineStart = 0;
|
|
682
|
+
while (lineStart < text.length) {
|
|
683
|
+
let i = lineStart;
|
|
684
|
+
while (text[i] === " " || text[i] === "\t") i++;
|
|
685
|
+
const keyStart = i;
|
|
686
|
+
let keyText = "";
|
|
687
|
+
if (text[i] === '"' || text[i] === "'") {
|
|
688
|
+
const keyEnd = scanTomlValueEnd(text, i);
|
|
689
|
+
keyText = decodeTomlStringToken(text.slice(i, keyEnd)) ?? "";
|
|
690
|
+
i = keyEnd;
|
|
691
|
+
} else {
|
|
692
|
+
const match = /^[A-Za-z0-9_-]+/.exec(text.slice(i));
|
|
693
|
+
if (match) {
|
|
694
|
+
keyText = match[0];
|
|
695
|
+
i += match[0].length;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
while (text[i] === " " || text[i] === "\t") i++;
|
|
699
|
+
if (keyText && text[i] === "=") {
|
|
700
|
+
const valueStart = i + 1;
|
|
701
|
+
const valueEnd = scanTomlValueEnd(text, valueStart);
|
|
702
|
+
if (keyText === key) return { keyStart, valueStart, valueEnd };
|
|
703
|
+
const nextLine = text.indexOf("\n", valueEnd);
|
|
704
|
+
lineStart = nextLine === -1 ? text.length : nextLine + 1;
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
const nextLine = text.indexOf("\n", lineStart);
|
|
708
|
+
lineStart = nextLine === -1 ? text.length : nextLine + 1;
|
|
709
|
+
}
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
|
|
448
713
|
/**
|
|
449
714
|
* Locate `key = value` inside the inline-table body spanning [bodyStart, bodyEnd)
|
|
450
715
|
* (exclusive of the braces), string-aware on both keys and values, or null.
|
|
@@ -478,6 +743,24 @@ function findInlineEntry(text: string, bodyStart: number, bodyEnd: number, key:
|
|
|
478
743
|
return null;
|
|
479
744
|
}
|
|
480
745
|
|
|
746
|
+
/** Whether a TOML assignment's value starts with a triple-quoted string. */
|
|
747
|
+
function isMultilineTomlString(text: string, valueStart: number): boolean {
|
|
748
|
+
let start = valueStart;
|
|
749
|
+
while (text[start] === " " || text[start] === "\t") start++;
|
|
750
|
+
return text.startsWith('"""', start) || text.startsWith("'''", start);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/** Whether one field in a supported inline `multi_agent_v2` table is triple-quoted. */
|
|
754
|
+
function hasInlineMultilineTomlString(text: string, assignment: InlineEntry, key: string): boolean {
|
|
755
|
+
let openIdx = assignment.valueStart;
|
|
756
|
+
while (text[openIdx] === " " || text[openIdx] === "\t") openIdx++;
|
|
757
|
+
if (text[openIdx] !== "{") return false;
|
|
758
|
+
const closeIdx = findInlineTableEnd(text, openIdx);
|
|
759
|
+
if (closeIdx === -1) return false;
|
|
760
|
+
const entry = findInlineEntry(text, openIdx + 1, closeIdx, key);
|
|
761
|
+
return entry !== null && isMultilineTomlString(text, entry.valueStart);
|
|
762
|
+
}
|
|
763
|
+
|
|
481
764
|
/**
|
|
482
765
|
* Set or remove one scalar key inside a top-level TOML table, preserving every
|
|
483
766
|
* other line byte-for-byte — including the existing value's trailing comment,
|
|
@@ -501,22 +784,19 @@ function editScalarInTable(content: string, table: string, key: string, encoded:
|
|
|
501
784
|
for (let i = headerIdx + 1; i < lines.length; i++) {
|
|
502
785
|
if (/^\s*\[/.test(lines[i])) { end = i; break; }
|
|
503
786
|
}
|
|
504
|
-
const keyRe = new RegExp(`^(\\s*)${key}\\s*=\\s*`);
|
|
505
787
|
for (let i = headerIdx + 1; i < end; i++) {
|
|
506
|
-
const
|
|
507
|
-
if (!
|
|
788
|
+
const entry = findTomlAssignment(lines[i], key);
|
|
789
|
+
if (!entry) continue;
|
|
508
790
|
// The existing value may itself contain '#', so scan the value token
|
|
509
791
|
// string-aware instead of splitting on '#'.
|
|
510
792
|
const line = lines[i];
|
|
511
|
-
const
|
|
512
|
-
const valueEnd = scanTomlValueEnd(line, valueStart);
|
|
513
|
-
const trailing = line.slice(valueEnd);
|
|
793
|
+
const trailing = line.slice(entry.valueEnd);
|
|
514
794
|
if (encoded === null) {
|
|
515
795
|
lines.splice(i, 1);
|
|
516
796
|
return applyEol(lines.join("\n"), eol);
|
|
517
797
|
}
|
|
518
|
-
if (line.slice(valueStart, valueEnd).trim() === encoded) return content;
|
|
519
|
-
lines[i] = `${
|
|
798
|
+
if (line.slice(entry.valueStart, entry.valueEnd).trim() === encoded) return content;
|
|
799
|
+
lines[i] = `${line.slice(0, entry.keyStart)}${key} = ${encoded}${trailing}`;
|
|
520
800
|
return applyEol(lines.join("\n"), eol);
|
|
521
801
|
}
|
|
522
802
|
if (encoded === null) return content;
|
|
@@ -568,31 +848,95 @@ export function setAgentsMaxDepth(value: number | null, configPath?: string): Co
|
|
|
568
848
|
* Reads both the dedicated-table and inline forms; dedicated wins, mirroring
|
|
569
849
|
* `getMaxConcurrentThreads` precedence.
|
|
570
850
|
*/
|
|
571
|
-
|
|
851
|
+
/**
|
|
852
|
+
* Read a string-valued `features.multi_agent_v2` scalar (dedicated table or inline
|
|
853
|
+
* form; dedicated wins, mirroring `getMaxConcurrentThreads` precedence). Returns
|
|
854
|
+
* `null` when the key is absent or the config is unreadable. A present empty string
|
|
855
|
+
* round-trips faithfully — some upstream keys treat `""` and `null` differently.
|
|
856
|
+
*/
|
|
857
|
+
function getV2StringField(key: string, configPath?: string): string | null {
|
|
572
858
|
const content = readConfigText(configPath);
|
|
573
859
|
if (content === null) return null;
|
|
574
|
-
const table =
|
|
860
|
+
const table = tomlTableBodyForStringFields(content, "features.multi_agent_v2");
|
|
575
861
|
if (table !== null) {
|
|
576
|
-
const
|
|
577
|
-
if (
|
|
578
|
-
const
|
|
579
|
-
const token = table.slice(valueStart, scanTomlValueEnd(table, valueStart)).trim();
|
|
862
|
+
const entry = findTomlAssignment(table, key);
|
|
863
|
+
if (entry) {
|
|
864
|
+
const token = table.slice(entry.valueStart, entry.valueEnd).trim();
|
|
580
865
|
return decodeTomlStringToken(token);
|
|
581
866
|
}
|
|
582
867
|
return null;
|
|
583
868
|
}
|
|
584
|
-
const features =
|
|
869
|
+
const features = tomlTableBodyForStringFields(content, "features");
|
|
585
870
|
if (features === null) return null;
|
|
586
|
-
const
|
|
587
|
-
if (!
|
|
588
|
-
|
|
871
|
+
const v2Entry = findTomlAssignment(features, "multi_agent_v2");
|
|
872
|
+
if (!v2Entry) return null;
|
|
873
|
+
let openIdx = v2Entry.valueStart;
|
|
874
|
+
while (features[openIdx] === " " || features[openIdx] === "\t") openIdx++;
|
|
875
|
+
if (features[openIdx] !== "{") return null;
|
|
589
876
|
const closeIdx = findInlineTableEnd(features, openIdx);
|
|
590
877
|
if (closeIdx === -1) return null;
|
|
591
|
-
const entry = findInlineEntry(features, openIdx + 1, closeIdx,
|
|
878
|
+
const entry = findInlineEntry(features, openIdx + 1, closeIdx, key);
|
|
592
879
|
if (!entry) return null;
|
|
593
880
|
return decodeTomlStringToken(features.slice(entry.valueStart, entry.valueEnd).trim());
|
|
594
881
|
}
|
|
595
882
|
|
|
883
|
+
/**
|
|
884
|
+
* A table body scanner that skips complete TOML values before recognizing the
|
|
885
|
+
* next header. Unlike the legacy line scanner, bracket-shaped prose inside a
|
|
886
|
+
* multi-line string cannot truncate the table.
|
|
887
|
+
*/
|
|
888
|
+
function tomlTableBodyForStringFields(content: string, header: string): string | null {
|
|
889
|
+
const escaped = escapeRegExp(header);
|
|
890
|
+
const match = new RegExp(`^\\s*\\[${escaped}\\]\\s*(?:#.*)?$`, "m").exec(content);
|
|
891
|
+
if (!match) return null;
|
|
892
|
+
const newline = content.indexOf("\n", match.index + match[0].length);
|
|
893
|
+
if (newline === -1) return "";
|
|
894
|
+
const bodyStart = newline + 1;
|
|
895
|
+
let lineStart = bodyStart;
|
|
896
|
+
while (lineStart < content.length) {
|
|
897
|
+
let cursor = lineStart;
|
|
898
|
+
while (content[cursor] === " " || content[cursor] === "\t") cursor++;
|
|
899
|
+
if (content[cursor] === "[") return content.slice(bodyStart, lineStart);
|
|
900
|
+
const lineEnd = content.indexOf("\n", cursor);
|
|
901
|
+
const boundedEnd = lineEnd === -1 ? content.length : lineEnd;
|
|
902
|
+
let keyEnd = cursor;
|
|
903
|
+
if (content[keyEnd] === '"' || content[keyEnd] === "'") {
|
|
904
|
+
keyEnd = scanTomlValueEnd(content, keyEnd);
|
|
905
|
+
} else {
|
|
906
|
+
const keyMatch = /^[A-Za-z0-9_.-]+/.exec(content.slice(keyEnd, boundedEnd));
|
|
907
|
+
if (keyMatch) keyEnd += keyMatch[0].length;
|
|
908
|
+
}
|
|
909
|
+
while (content[keyEnd] === " " || content[keyEnd] === "\t") keyEnd++;
|
|
910
|
+
if (content[keyEnd] === "=" && keyEnd < boundedEnd) {
|
|
911
|
+
const valueEnd = scanTomlValueEnd(content, keyEnd + 1);
|
|
912
|
+
const nextLine = content.indexOf("\n", valueEnd);
|
|
913
|
+
lineStart = nextLine === -1 ? content.length : nextLine + 1;
|
|
914
|
+
} else {
|
|
915
|
+
lineStart = lineEnd === -1 ? content.length : lineEnd + 1;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
return content.slice(bodyStart);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
function escapeRegExp(value: string): string {
|
|
922
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/** Bare and quoted TOML forms of one known-safe key. */
|
|
926
|
+
function tomlKeyPattern(key: string): string {
|
|
927
|
+
const escaped = escapeRegExp(key);
|
|
928
|
+
return `(?:${escaped}|"${escaped}"|'${escaped}')`;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
export function getSubagentDeveloperInstructions(configPath?: string): string | null {
|
|
932
|
+
return getV2StringField("subagent_developer_instructions", configPath);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/** Current `features.multi_agent_v2.multi_agent_mode_hint_text`, or null when unset. */
|
|
936
|
+
export function getMultiAgentModeHintText(configPath?: string): string | null {
|
|
937
|
+
return getV2StringField("multi_agent_mode_hint_text", configPath);
|
|
938
|
+
}
|
|
939
|
+
|
|
596
940
|
/**
|
|
597
941
|
* Persist `features.multi_agent_v2.subagent_developer_instructions`, or remove the
|
|
598
942
|
* key when `value` is null. Handles all three existing encodings: the dedicated
|
|
@@ -604,19 +948,49 @@ export function getSubagentDeveloperInstructions(configPath?: string): string |
|
|
|
604
948
|
* carries `#[serde(deny_unknown_fields)]`, so a misspelling is not ignored — it is
|
|
605
949
|
* a hard config-parse failure for the user's Codex.
|
|
606
950
|
*/
|
|
607
|
-
|
|
951
|
+
function setV2StringField(key: string, value: string | null, configPath?: string): ConfigEditResult {
|
|
608
952
|
const path = configPath ?? activeCodexConfigPath();
|
|
609
953
|
const content = readConfigText(path);
|
|
610
954
|
if (content === null) return { ok: false, error: `config.toml not readable at ${path}` };
|
|
611
955
|
const encoded = value === null ? null : encodeTomlBasicString(value);
|
|
612
956
|
|
|
613
|
-
|
|
614
|
-
|
|
957
|
+
// A parsed V2 object without one of the supported source forms came from
|
|
958
|
+
// dotted/quoted path segments. Appending a dedicated table would redefine it
|
|
959
|
+
// and make Codex reject the file, so fail closed without changing any bytes.
|
|
960
|
+
const parsedFeatures = parsedTomlTable(content, "features");
|
|
961
|
+
const parsedV2 = parsedFeatures === null ? null : plainTomlRecord(parsedFeatures.multi_agent_v2);
|
|
962
|
+
const dedicatedV2 = tomlTableBodyForStringFields(content, "features.multi_agent_v2") !== null;
|
|
963
|
+
const featuresBody = tomlTableBodyForStringFields(content, "features");
|
|
964
|
+
const featuresV2Entry = featuresBody === null ? null : findTomlAssignment(featuresBody, "multi_agent_v2");
|
|
965
|
+
const supportedFeaturesEntry = featuresV2Entry !== null;
|
|
966
|
+
if (parsedV2 !== null && !dedicatedV2 && !supportedFeaturesEntry) {
|
|
967
|
+
return { ok: false, error: "dotted or quoted multi_agent_v2 config is not supported for managed string fields" };
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
const dedicatedStringBody = tomlTableBodyForStringFields(content, "features.multi_agent_v2");
|
|
971
|
+
if (dedicatedStringBody !== null) {
|
|
972
|
+
// Multiline TOML strings ("""...""" / '''...''') span multiple lines and the
|
|
973
|
+
// single-line table editor cannot rewrite or remove them without corrupting
|
|
974
|
+
// the document. Scope this guard to the target V2 table so an unrelated
|
|
975
|
+
// table carrying the same key does not block an otherwise safe edit.
|
|
976
|
+
const dedicatedEntry = findTomlAssignment(dedicatedStringBody, key);
|
|
977
|
+
if (dedicatedEntry !== null && isMultilineTomlString(dedicatedStringBody, dedicatedEntry.valueStart)) {
|
|
978
|
+
return { ok: false, error: `multi-line TOML string for ${key} is not editable; convert it to a single-line string first` };
|
|
979
|
+
}
|
|
980
|
+
const legacyDedicatedBody = tomlTableBody(content, "features.multi_agent_v2") ?? "";
|
|
981
|
+
if (dedicatedEntry !== null && findTomlAssignment(legacyDedicatedBody, key) === null) {
|
|
982
|
+
return { ok: false, error: `cannot edit ${key} after a header-shaped multiline value safely` };
|
|
983
|
+
}
|
|
984
|
+
const next = editScalarInTable(content, "features.multi_agent_v2", key, encoded);
|
|
615
985
|
if (next === content) return { ok: true, changed: false };
|
|
616
986
|
atomicWriteFile(path, next);
|
|
617
987
|
return { ok: true, changed: true };
|
|
618
988
|
}
|
|
619
989
|
|
|
990
|
+
if (featuresBody !== null && featuresV2Entry !== null && hasInlineMultilineTomlString(featuresBody, featuresV2Entry, key)) {
|
|
991
|
+
return { ok: false, error: `multi-line TOML string for ${key} is not editable; convert it to a single-line string first` };
|
|
992
|
+
}
|
|
993
|
+
|
|
620
994
|
const eol = dominantEol(content);
|
|
621
995
|
const lines = content.split(/\r?\n/);
|
|
622
996
|
const featuresHeader = lines.findIndex(l => /^\s*\[features\]\s*(?:#.*)?$/.test(l));
|
|
@@ -632,7 +1006,7 @@ export function setSubagentDeveloperInstructions(value: string | null, configPat
|
|
|
632
1006
|
const openIdx = inlineMatch[0].length - 1;
|
|
633
1007
|
const closeIdx = findInlineTableEnd(line, openIdx);
|
|
634
1008
|
if (closeIdx === -1) return { ok: false, error: "malformed multi_agent_v2 inline table" };
|
|
635
|
-
const entry = findInlineEntry(line, openIdx + 1, closeIdx,
|
|
1009
|
+
const entry = findInlineEntry(line, openIdx + 1, closeIdx, key);
|
|
636
1010
|
if (encoded === null) {
|
|
637
1011
|
if (!entry) return { ok: true, changed: false };
|
|
638
1012
|
let start = entry.keyStart;
|
|
@@ -659,8 +1033,8 @@ export function setSubagentDeveloperInstructions(value: string | null, configPat
|
|
|
659
1033
|
while (insertPos > openIdx + 1 && line[insertPos - 1] === " ") insertPos--;
|
|
660
1034
|
const hasEntries = line.slice(openIdx + 1, insertPos).trim().length > 0;
|
|
661
1035
|
const insertion = hasEntries
|
|
662
|
-
? `,
|
|
663
|
-
: `
|
|
1036
|
+
? `, ${key} = ${encoded} `
|
|
1037
|
+
: ` ${key} = ${encoded} `;
|
|
664
1038
|
lines[i] = line.slice(0, insertPos) + insertion + line.slice(closeIdx);
|
|
665
1039
|
}
|
|
666
1040
|
atomicWriteFile(path, applyEol(lines.join("\n"), eol));
|
|
@@ -669,21 +1043,223 @@ export function setSubagentDeveloperInstructions(value: string | null, configPat
|
|
|
669
1043
|
const boolMatch = line.match(/^(\s*)multi_agent_v2\s*=\s*(true|false)(\s*(?:#.*)?)$/);
|
|
670
1044
|
if (boolMatch) {
|
|
671
1045
|
if (encoded === null) return { ok: true, changed: false };
|
|
672
|
-
lines[i] = `${boolMatch[1]}multi_agent_v2 = { enabled = ${boolMatch[2]},
|
|
1046
|
+
lines[i] = `${boolMatch[1]}multi_agent_v2 = { enabled = ${boolMatch[2]}, ${key} = ${encoded} }${boolMatch[3]}`;
|
|
673
1047
|
atomicWriteFile(path, applyEol(lines.join("\n"), eol));
|
|
674
1048
|
return { ok: true, changed: true };
|
|
675
1049
|
}
|
|
676
1050
|
}
|
|
1051
|
+
if (featuresV2Entry !== null) {
|
|
1052
|
+
return { ok: false, error: "multi_agent_v2 inside a multiline [features] table is not editable safely" };
|
|
1053
|
+
}
|
|
677
1054
|
}
|
|
678
1055
|
|
|
679
1056
|
if (encoded === null) return { ok: true, changed: false };
|
|
680
1057
|
const suffix = content.endsWith("\n") || content.length === 0 ? "" : eol;
|
|
681
1058
|
const separator = content.length > 0 && !content.endsWith(`${eol}${eol}`) ? eol : "";
|
|
682
|
-
const tableText = `[features.multi_agent_v2]${eol}
|
|
1059
|
+
const tableText = `[features.multi_agent_v2]${eol}${key} = ${encoded}${eol}`;
|
|
683
1060
|
atomicWriteFile(path, `${content}${suffix}${separator}${tableText}`);
|
|
684
1061
|
return { ok: true, changed: true };
|
|
685
1062
|
}
|
|
686
1063
|
|
|
1064
|
+
export function setSubagentDeveloperInstructions(value: string | null, configPath?: string): ConfigEditResult {
|
|
1065
|
+
return setV2StringField("subagent_developer_instructions", value, configPath);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
export const MODE_HINT_UNSUPPORTED_ERROR =
|
|
1069
|
+
"installed Codex does not support multi_agent_mode_hint_text; update Codex first";
|
|
1070
|
+
|
|
1071
|
+
/**
|
|
1072
|
+
* Persist `features.multi_agent_v2.multi_agent_mode_hint_text`, or remove the key
|
|
1073
|
+
* when `value` is null. Same encoding coverage and upstream-name discipline as
|
|
1074
|
+
* `setSubagentDeveloperInstructions`; the upstream struct rejects unknown fields,
|
|
1075
|
+
* so the key spelling must match codex-rs exactly.
|
|
1076
|
+
*/
|
|
1077
|
+
export function setMultiAgentModeHintText(value: string | null, configPath?: string): ConfigEditResult {
|
|
1078
|
+
// The upstream `multi_agent_mode_hint_text` key is newer than the v2 config
|
|
1079
|
+
// surface opencodex already manages; an older Codex build rejects the unknown
|
|
1080
|
+
// member (`#[serde(deny_unknown_fields)]`) and fails to start. Probe the
|
|
1081
|
+
// installed runtime binary for the key string and refuse the write when the
|
|
1082
|
+
// binary provably lacks it. A probe that cannot run (missing binary,
|
|
1083
|
+
// unreadable file) does not block: that is the test/hermetic path and the
|
|
1084
|
+
// headless runtime fallback.
|
|
1085
|
+
if (value !== null) {
|
|
1086
|
+
const probe = probeCodexSupportsModeHint();
|
|
1087
|
+
if (probe === false) {
|
|
1088
|
+
return {
|
|
1089
|
+
ok: false,
|
|
1090
|
+
error: MODE_HINT_UNSUPPORTED_ERROR,
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
return setV2StringField("multi_agent_mode_hint_text", value, configPath);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
export const MODE_HINT_CAPABILITY_CACHE_MAX_ENTRIES = 8;
|
|
1098
|
+
export const modeHintCapabilityCache = new Map<string, boolean | null>();
|
|
1099
|
+
|
|
1100
|
+
export function rememberModeHintCapability(cacheKey: string, capability: boolean | null): void {
|
|
1101
|
+
modeHintCapabilityCache.delete(cacheKey);
|
|
1102
|
+
modeHintCapabilityCache.set(cacheKey, capability);
|
|
1103
|
+
while (modeHintCapabilityCache.size > MODE_HINT_CAPABILITY_CACHE_MAX_ENTRIES) {
|
|
1104
|
+
const oldest = modeHintCapabilityCache.keys().next().value;
|
|
1105
|
+
if (oldest === undefined) break;
|
|
1106
|
+
modeHintCapabilityCache.delete(oldest);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
/**
|
|
1111
|
+
* True when the installed Codex runtime binary contains the
|
|
1112
|
+
* `multi_agent_mode_hint_text` config key, false when it provably does not, and
|
|
1113
|
+
* null when the probe could not run (missing binary, unreadable file).
|
|
1114
|
+
*/
|
|
1115
|
+
export function probeCodexSupportsModeHint(): boolean | null {
|
|
1116
|
+
try {
|
|
1117
|
+
const runtime = resolveAndPersistCodexRuntime({ env: process.env }).runtime;
|
|
1118
|
+
const selectedPath = resolveSelectedCommandPath(runtime.command);
|
|
1119
|
+
let selectedIdentity = selectedPath ?? "";
|
|
1120
|
+
try { if (selectedPath) selectedIdentity = realpathSync(selectedPath); } catch { /* keep lexical path */ }
|
|
1121
|
+
const candidates = codexNativeBinaryCandidates(runtime.command);
|
|
1122
|
+
const binaryStatFingerprint = candidates.map(candidate => {
|
|
1123
|
+
try {
|
|
1124
|
+
const stat = statSync(candidate);
|
|
1125
|
+
return `${candidate}\0${stat.dev}\0${stat.ino}\0${stat.size}\0${stat.mtimeMs}\0${stat.ctimeMs}`;
|
|
1126
|
+
} catch {
|
|
1127
|
+
return `${candidate}\0missing`;
|
|
1128
|
+
}
|
|
1129
|
+
}).join("\0");
|
|
1130
|
+
const cacheKey = `${runtime.command}\0${runtime.version ?? ""}\0${selectedIdentity}\0${binaryStatFingerprint}`;
|
|
1131
|
+
const cached = modeHintCapabilityCache.get(cacheKey);
|
|
1132
|
+
if (cached !== undefined) return cached;
|
|
1133
|
+
let sawBinary = false;
|
|
1134
|
+
for (const candidate of candidates) {
|
|
1135
|
+
try {
|
|
1136
|
+
if (!existsSync(candidate)) continue;
|
|
1137
|
+
const buf = readFileSync(candidate);
|
|
1138
|
+
if (!isNativeExecutable(buf)) continue;
|
|
1139
|
+
sawBinary = true;
|
|
1140
|
+
if (buf.includes(Buffer.from("multi_agent_mode_hint_text", "utf8"))) {
|
|
1141
|
+
rememberModeHintCapability(cacheKey, true);
|
|
1142
|
+
return true;
|
|
1143
|
+
}
|
|
1144
|
+
} catch {
|
|
1145
|
+
continue;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
// At least one real binary was inspected and none contained the key.
|
|
1149
|
+
const result = sawBinary ? false : null;
|
|
1150
|
+
rememberModeHintCapability(cacheKey, result);
|
|
1151
|
+
return result;
|
|
1152
|
+
} catch {
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
const CODEX_PLATFORM_PACKAGES = [
|
|
1158
|
+
["codex-darwin-arm64", "aarch64-apple-darwin", "codex"],
|
|
1159
|
+
["codex-darwin-x64", "x86_64-apple-darwin", "codex"],
|
|
1160
|
+
["codex-linux-x64", "x86_64-unknown-linux-musl", "codex"],
|
|
1161
|
+
["codex-linux-arm64", "aarch64-unknown-linux-musl", "codex"],
|
|
1162
|
+
["codex-win32-x64", "x86_64-pc-windows-msvc", "codex.exe"],
|
|
1163
|
+
["codex-win32-arm64", "aarch64-pc-windows-msvc", "codex.exe"],
|
|
1164
|
+
] as const;
|
|
1165
|
+
|
|
1166
|
+
function isNativeExecutable(buf: Buffer): boolean {
|
|
1167
|
+
if (buf.length < 4) return false;
|
|
1168
|
+
if (buf[0] === 0x4d && buf[1] === 0x5a) return true; // PE/COFF
|
|
1169
|
+
const magic = buf.readUInt32BE(0);
|
|
1170
|
+
return magic === 0x7f454c46 // ELF
|
|
1171
|
+
|| magic === 0xfeedface || magic === 0xfeedfacf
|
|
1172
|
+
|| magic === 0xcefaedfe || magic === 0xcffaedfe
|
|
1173
|
+
|| magic === 0xcafebabe || magic === 0xbebafeca; // Mach-O/fat Mach-O
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
/**
|
|
1177
|
+
* Candidate native codex binaries to probe. The resolved `command` may be the
|
|
1178
|
+
* opencodex shim or the npm JS wrapper (`codex.opencodex-real`), neither of
|
|
1179
|
+
* which embeds the Rust config schema. The real binary ships under the
|
|
1180
|
+
* platform package's `vendor/<triple>/bin/codex`; also try adjacent wrappers.
|
|
1181
|
+
*/
|
|
1182
|
+
function codexNativeBinaryCandidates(command: string): string[] {
|
|
1183
|
+
const out = new Set<string>();
|
|
1184
|
+
const resolverBases = new Set<string>();
|
|
1185
|
+
const selectedPath = resolveSelectedCommandPath(command);
|
|
1186
|
+
|
|
1187
|
+
const addSelectedTarget = (target: string) => {
|
|
1188
|
+
try {
|
|
1189
|
+
const real = realpathSync(target);
|
|
1190
|
+
out.add(target);
|
|
1191
|
+
out.add(real);
|
|
1192
|
+
if (/[\\/]@openai[\\/]codex[\\/]bin[\\/]/.test(real)) resolverBases.add(real);
|
|
1193
|
+
} catch {
|
|
1194
|
+
// Missing backing paths cannot establish capability either way.
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
|
|
1198
|
+
// Follow the resolved command to its real location. The opencodex shim and
|
|
1199
|
+
// the npm JS wrapper resolve to `@openai/codex/bin/codex.js`; the native
|
|
1200
|
+
// binary lives in the sibling platform package's vendor directory.
|
|
1201
|
+
if (selectedPath) {
|
|
1202
|
+
addSelectedTarget(selectedPath);
|
|
1203
|
+
for (const target of selectedShimBackingPaths(selectedPath)) addSelectedTarget(target);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// npm's Windows wrappers are ordinary scripts. Resolve from both the global
|
|
1207
|
+
// prefix layout and a project-local node_modules/.bin layout; createRequire
|
|
1208
|
+
// then follows the selected installation's own dependency tree (including
|
|
1209
|
+
// pnpm/nested optional dependencies) without consulting unrelated PATH bins.
|
|
1210
|
+
const wrapperPath = selectedPath ?? command;
|
|
1211
|
+
if (/\.(?:cmd|ps1)$/i.test(wrapperPath)) {
|
|
1212
|
+
resolverBases.add(resolve(dirname(wrapperPath), "node_modules", "@openai", "codex", "bin", "codex.js"));
|
|
1213
|
+
resolverBases.add(resolve(dirname(wrapperPath), "..", "@openai", "codex", "bin", "codex.js"));
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
for (const base of resolverBases) {
|
|
1217
|
+
const requireFromSelected = createRequire(base);
|
|
1218
|
+
for (const [pkg, triple, exe] of CODEX_PLATFORM_PACKAGES) {
|
|
1219
|
+
try {
|
|
1220
|
+
const manifest = requireFromSelected.resolve(`@openai/${pkg}/package.json`);
|
|
1221
|
+
out.add(join(dirname(manifest), "vendor", triple, "bin", exe));
|
|
1222
|
+
} catch {
|
|
1223
|
+
// Optional platform packages for other targets are normally absent.
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
return [...out];
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
/** Backing paths tied to this exact OCX shim entry in codex-shim.json. */
|
|
1231
|
+
function selectedShimBackingPaths(commandPath: string): string[] {
|
|
1232
|
+
try {
|
|
1233
|
+
const state = JSON.parse(readFileSync(join(getConfigDir(), "codex-shim.json"), "utf8")) as {
|
|
1234
|
+
wrapperPath?: unknown;
|
|
1235
|
+
originalPath?: unknown;
|
|
1236
|
+
backupPath?: unknown;
|
|
1237
|
+
realPath?: unknown;
|
|
1238
|
+
wrappers?: Array<Record<string, unknown>>;
|
|
1239
|
+
};
|
|
1240
|
+
const entries = Array.isArray(state.wrappers) && state.wrappers.length > 0 ? state.wrappers : [state];
|
|
1241
|
+
const selected = resolve(commandPath);
|
|
1242
|
+
for (const entry of entries) {
|
|
1243
|
+
if (typeof entry.wrapperPath !== "string" || resolve(entry.wrapperPath) !== selected) continue;
|
|
1244
|
+
return [entry.backupPath, entry.realPath, entry.originalPath]
|
|
1245
|
+
.filter((value): value is string => typeof value === "string" && value.length > 0 && resolve(value) !== selected);
|
|
1246
|
+
}
|
|
1247
|
+
} catch {
|
|
1248
|
+
// Not an OCX-owned shim, or no readable state.
|
|
1249
|
+
}
|
|
1250
|
+
return [];
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
/** Resolve only the selected bare command against PATH; never enumerate peers. */
|
|
1254
|
+
function resolveSelectedCommandPath(command: string): string | null {
|
|
1255
|
+
if (command.includes("/") || command.includes("\\")) return existsSync(command) ? resolve(command) : null;
|
|
1256
|
+
for (const dir of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
|
|
1257
|
+
const candidate = join(dir, command);
|
|
1258
|
+
if (existsSync(candidate)) return candidate;
|
|
1259
|
+
}
|
|
1260
|
+
return null;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
687
1263
|
function editAgentsMaxThreads(value: number | null, configPath?: string, migratedComment?: string): ConfigEditResult {
|
|
688
1264
|
const path = configPath ?? activeCodexConfigPath();
|
|
689
1265
|
const content = readConfigText(path);
|
|
@@ -833,10 +1409,20 @@ function activeThreadComment(content: string, v2Enabled: boolean): string | unde
|
|
|
833
1409
|
}
|
|
834
1410
|
|
|
835
1411
|
let migrationEditSeq = 0;
|
|
1412
|
+
/** Both residual classes gate the memo release: a plain residual and a
|
|
1413
|
+
* secret-bearing one alike keep their destination memo while the file
|
|
1414
|
+
* remains on disk. Exported for the regression seam. */
|
|
1415
|
+
export function isAtomicResidualError(error: unknown): boolean {
|
|
1416
|
+
return error instanceof AtomicWriteResidualTempError || error instanceof AtomicWriteSecretResidualError;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
836
1419
|
function applyConfigEditsAtomically(path: string, edit: (tempPath: string) => ConfigEditResult): ConfigEditResult {
|
|
837
1420
|
const content = readConfigText(path);
|
|
838
1421
|
if (content === null) return { ok: false, error: `config.toml not readable at ${path}` };
|
|
839
1422
|
const tempPath = `${path}.ocx-migration.${process.pid}.${++migrationEditSeq}`;
|
|
1423
|
+
// An inner residual temp (AtomicWriteResidualTempError) keeps its
|
|
1424
|
+
// destination-keyed memo: fail-closed while the residual exists.
|
|
1425
|
+
let innerResidual = false;
|
|
840
1426
|
try {
|
|
841
1427
|
atomicWriteFile(tempPath, content);
|
|
842
1428
|
const result = edit(tempPath);
|
|
@@ -846,8 +1432,19 @@ function applyConfigEditsAtomically(path: string, edit: (tempPath: string) => Co
|
|
|
846
1432
|
if (edited === content) return { ok: true, changed: false };
|
|
847
1433
|
atomicWriteFile(path, edited);
|
|
848
1434
|
return { ok: true, changed: true };
|
|
1435
|
+
} catch (error) {
|
|
1436
|
+
if (isAtomicResidualError(error)) innerResidual = true;
|
|
1437
|
+
throw error;
|
|
849
1438
|
} finally {
|
|
850
|
-
try {
|
|
1439
|
+
try {
|
|
1440
|
+
unlinkSync(tempPath);
|
|
1441
|
+
if (!innerResidual) forgetEphemeralSecretPath(tempPath);
|
|
1442
|
+
} catch (error) {
|
|
1443
|
+
// Already absent is also proven-absent; other failures keep the memo.
|
|
1444
|
+
if ((error as NodeJS.ErrnoException | undefined)?.code === "ENOENT") {
|
|
1445
|
+
if (!innerResidual) forgetEphemeralSecretPath(tempPath);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
851
1448
|
}
|
|
852
1449
|
}
|
|
853
1450
|
|