@bitkyc08/opencodex 2.83.0 → 2.86.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/README.md +0 -4
- package/bin/ocx.mjs +61 -3
- package/gui/dist/assets/App-BjlttVLy.js +58 -0
- package/gui/dist/assets/Tray-DyR9yBee.js +1 -0
- package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
- package/gui/dist/assets/index-BMssP4k3.js +94 -0
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +2 -3
- package/src/adapters/anthropic/beta-allowlist.ts +87 -8
- package/src/adapters/anthropic/client-identity.ts +1 -1
- package/src/adapters/anthropic/passthrough.ts +33 -4
- package/src/adapters/azure.ts +19 -1
- package/src/adapters/client-fingerprint.ts +24 -0
- package/src/adapters/coding-agent/protocol.ts +15 -8
- package/src/adapters/coding-agent/turn.ts +22 -9
- package/src/adapters/cursor/arg-codec.ts +17 -5
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/cursor/freeform-args.ts +75 -0
- package/src/adapters/cursor/protobuf-events.ts +36 -23
- package/src/adapters/google-antigravity-wire.ts +22 -1
- package/src/adapters/google.ts +2 -2
- package/src/adapters/ollama-native.ts +29 -1
- package/src/adapters/openai-chat/messages.ts +8 -4
- package/src/adapters/openai-chat/response-events.ts +59 -1
- package/src/adapters/openai-chat.ts +8 -5
- package/src/adapters/openai-responses/empty-assistant.ts +32 -0
- package/src/adapters/openai-responses/passthrough.ts +3 -0
- package/src/adapters/openai-responses/request-strips.ts +10 -2
- package/src/adapters/qoder/adapter.ts +56 -15
- package/src/advisor/consult.ts +220 -0
- package/src/advisor/context.ts +226 -0
- package/src/advisor/disclosure.ts +20 -0
- package/src/advisor/runtime.ts +305 -0
- package/src/advisor/settings.ts +135 -0
- package/src/advisor/state.ts +369 -0
- package/src/advisor/synthetic-tool.ts +33 -0
- package/src/bridge/errors.ts +24 -0
- package/src/bridge.ts +1 -1
- package/src/chat/inbound.ts +30 -6
- package/src/chat/outbound.ts +5 -0
- package/src/claude/context-windows.ts +54 -1
- package/src/claude/desktop-3p-library.ts +43 -3
- package/src/claude/desktop-3p.ts +22 -8
- package/src/claude/desktop-picker-profile.ts +11 -4
- package/src/claude/desktop-profile.ts +10 -1
- package/src/claude/desktop-remote-store-io.ts +5 -3
- package/src/claude/inbound.ts +121 -50
- package/src/claude/intercept/connect-proxy.ts +10 -3
- package/src/claude/intercept/local-ca.ts +8 -1
- package/src/claude/intercept/picker-ca-persistence.ts +3 -1
- package/src/claude/intercept/picker-listener.ts +49 -1
- package/src/claude/intercept/settings-no-proxy.ts +146 -0
- package/src/claude/intercept/settings.ts +101 -9
- package/src/claude/model-default-settings.ts +154 -0
- package/src/claude/model-info.ts +7 -7
- package/src/claude/outbound.ts +13 -6
- package/src/cli/account-api.ts +65 -0
- package/src/cli/account-auth.ts +26 -2
- package/src/cli/account.ts +41 -5
- package/src/cli/advisor.ts +151 -0
- package/src/cli/auxiliary-listener-preflight.ts +37 -0
- package/src/cli/capabilities-access-remote.ts +8 -0
- package/src/cli/capabilities-agents-routing.ts +18 -0
- package/src/cli/capabilities-base.ts +14 -2
- package/src/cli/catalog-command-result.ts +2 -1
- package/src/cli/chatgpt-command.ts +35 -14
- package/src/cli/claude.ts +60 -11
- package/src/cli/config-command.ts +8 -2
- package/src/cli/debug.ts +8 -0
- package/src/cli/dispatch.ts +21 -4
- package/src/cli/doctor.ts +9 -0
- package/src/cli/ensure-desktop-cold-start.ts +329 -0
- package/src/cli/export-command.ts +15 -2
- package/src/cli/help.ts +2 -1
- package/src/cli/hub-disable.ts +99 -0
- package/src/cli/hub.ts +4 -2
- package/src/cli/index.ts +26 -8
- package/src/cli/integration-plan-dto.ts +2 -0
- package/src/cli/link-child.ts +104 -0
- package/src/cli/link-enrollment.ts +96 -0
- package/src/cli/link.ts +39 -24
- package/src/cli/observe-stream.ts +6 -1
- package/src/cli/observe.ts +10 -2
- package/src/cli/provider.ts +12 -0
- package/src/cli/registry.ts +20 -7
- package/src/cli/restart-scope.ts +3 -1
- package/src/cli/runtime-api.ts +27 -2
- package/src/cli/status.ts +104 -20
- package/src/client/connect.ts +39 -4
- package/src/client/hub-client.ts +14 -7
- package/src/client/hub-state.ts +25 -14
- package/src/client/link-home.ts +248 -0
- package/src/client/link-management-relay.ts +61 -0
- package/src/client/link-relay.ts +6 -1
- package/src/client/link-status.ts +14 -0
- package/src/client/machine-api.ts +14 -7
- package/src/client/machine-listener.ts +29 -4
- package/src/clients/config-export/contracts.ts +1 -0
- package/src/clients/config-export/model-metadata.ts +3 -2
- package/src/clients/config-export/qoder.ts +93 -0
- package/src/clients/config-export.ts +59 -16
- package/src/clients/omo-role-models.ts +6 -3
- package/src/codex/agent-role-models.ts +26 -8
- package/src/codex/auth-api/account-list.ts +17 -2
- package/src/codex/auth-api/account-selection.ts +17 -7
- package/src/codex/auth-api/pool-mode-gate.ts +5 -2
- package/src/codex/auth-api/routes.ts +5 -1
- package/src/codex/auth-context.ts +79 -5
- package/src/codex/autostart-health.ts +7 -1
- package/src/codex/catalog/build-entries.ts +11 -2
- package/src/codex/catalog/bundled.ts +5 -1
- package/src/codex/catalog/configured-tiers.ts +50 -0
- package/src/codex/catalog/effort.ts +6 -3
- package/src/codex/catalog/metadata.ts +4 -2
- package/src/codex/catalog/model-hints.ts +21 -3
- package/src/codex/catalog/native-models.ts +16 -2
- package/src/codex/catalog/native-ultrafast.ts +106 -0
- package/src/codex/catalog/parsing.ts +5 -0
- package/src/codex/catalog/provider-models.ts +4 -0
- package/src/codex/catalog/retained-sync.ts +2 -0
- package/src/codex/catalog/routed-gather.ts +22 -4
- package/src/codex/catalog/vendor-context.ts +82 -0
- package/src/codex/config-write-lock.ts +40 -2
- package/src/codex/convergence.ts +2 -0
- package/src/codex/desktop-app/types.ts +2 -0
- package/src/codex/desktop-app/windows.ts +29 -16
- package/src/codex/desktop-app-restart.ts +4 -2
- package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
- package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
- package/src/codex/desktop-compatibility/connection-store.ts +87 -0
- package/src/codex/desktop-compatibility/installed-build.ts +55 -0
- package/src/codex/desktop-compatibility/json-body.ts +29 -0
- package/src/codex/desktop-compatibility/native-identity.ts +62 -0
- package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
- package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
- package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
- package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
- package/src/codex/desktop-compatibility/runtime.ts +173 -0
- package/src/codex/desktop-compatibility/service.ts +30 -0
- package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
- package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
- package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
- package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
- package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
- package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
- package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
- package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
- package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
- package/src/codex/desktop-compatibility/windows-key-protection.ts +59 -0
- package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
- package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
- package/src/codex/history-provider.ts +1 -1
- package/src/codex/inject/config-write-section.ts +4 -1
- package/src/codex/inject/remove.ts +18 -6
- package/src/codex/journal.ts +16 -1
- package/src/codex/main-account.ts +99 -15
- package/src/codex/native-main-refresh-mode.ts +217 -0
- package/src/codex/native-profile-startup.ts +4 -1
- package/src/codex/prompt-journal.ts +20 -2
- package/src/codex/prompt-layers/encoding.ts +49 -0
- package/src/codex/prompt-layers/import-source.ts +30 -0
- package/src/codex/prompt-layers/toml-edit.ts +269 -37
- package/src/codex/prompt-layers/toml-read.ts +52 -4
- package/src/codex/prompt-layers.ts +383 -20
- package/src/codex/prompt-lock-claim.ts +13 -3
- package/src/codex/prompt-lock-evidence.ts +3 -3
- package/src/codex/prompt-lock-owner.ts +14 -6
- package/src/codex/prompt-text-probe.ts +34 -5
- package/src/codex/quota.ts +13 -6
- package/src/codex/role-auto-assign.ts +2 -5
- package/src/codex/routing/active-account.ts +21 -0
- package/src/codex/routing/health-store.ts +1 -1
- package/src/codex/routing/pool-recovery.ts +79 -0
- package/src/codex/routing.ts +1 -1
- package/src/codex/runtime.ts +196 -8
- package/src/codex/shim-diagnostics.ts +6 -0
- package/src/codex/shim-overlay.ts +15 -6
- package/src/codex/shim.ts +1 -1
- package/src/combos/failover.ts +3 -0
- package/src/config/api-key-reconcile.ts +34 -0
- package/src/config/diagnostics.ts +18 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/live-reconcile.ts +56 -29
- package/src/config/live-topology.ts +29 -0
- package/src/config/load-degrade.ts +8 -2
- package/src/config/paths.ts +13 -3
- package/src/config/salvage.ts +3 -1
- package/src/config/schema/config-schema.ts +19 -0
- package/src/config/schema/desktop-compatibility.ts +9 -0
- package/src/config/schema/leaf-validators.ts +1 -0
- package/src/generated/compatibility-version.json +713 -285
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/grpc-web.ts +23 -1
- package/src/grok/reset-coupon-ledger.ts +75 -5
- package/src/grok/reset-coupons.ts +4 -2
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/config-io.ts +20 -2
- package/src/integrations/journal.ts +15 -1
- package/src/integrations/mutation-plan.ts +14 -7
- package/src/integrations/registry.ts +52 -11
- package/src/integrations/state.ts +19 -7
- package/src/integrations/store.ts +3 -0
- package/src/integrations/target.ts +42 -5
- package/src/integrations/writer.ts +24 -7
- package/src/lib/advisor-activation.ts +16 -0
- package/src/lib/app-owned-memory-stores.ts +4 -0
- package/src/lib/app-owned-memory.ts +11 -0
- package/src/lib/background-work.ts +65 -0
- package/src/lib/bounded-file-read.ts +58 -0
- package/src/lib/bun-path-runtime.d.mts +10 -1
- package/src/lib/bun-path-runtime.mjs +62 -3
- package/src/lib/desktop-proxy-route.ts +30 -0
- package/src/lib/desktop-upstream-tunnel.ts +107 -0
- package/src/lib/errors.ts +7 -0
- package/src/lib/json-byte-size.ts +31 -0
- package/src/lib/local-internal-call-capability.ts +55 -0
- package/src/lib/optional-shutdown-hooks.ts +35 -0
- package/src/lib/outbound-credential-registry.ts +210 -0
- package/src/lib/package-tree-integrity.ts +2 -2
- package/src/lib/passive-read.ts +35 -0
- package/src/lib/shadow-call.ts +44 -5
- package/src/lib/socks5-fetch.ts +48 -71
- package/src/lib/socks5-handshake.ts +112 -0
- package/src/lib/translator-budget.ts +7 -1
- package/src/link/codex-settings.ts +155 -0
- package/src/link/management-routes.ts +89 -0
- package/src/link/remote-probe.ts +38 -0
- package/src/link/ssh-argv.ts +26 -5
- package/src/link/ssh-runner.ts +58 -27
- package/src/link/status-projection.ts +22 -4
- package/src/link/supervisor.ts +144 -49
- package/src/link/tunnel-state.ts +11 -4
- package/src/oauth/anthropic-continuity.ts +4 -3
- package/src/oauth/anthropic-identity.ts +13 -2
- package/src/oauth/anthropic-model-quota.ts +13 -1
- package/src/oauth/anthropic-routing.ts +230 -23
- package/src/oauth/anthropic.ts +18 -4
- package/src/oauth/generic-account-failover.ts +29 -5
- package/src/oauth/google-antigravity.ts +24 -7
- package/src/oauth/index.ts +28 -17
- package/src/oauth/kiro-account-identity.ts +106 -0
- package/src/oauth/kiro-device-login.ts +2 -1
- package/src/oauth/kiro.ts +55 -6
- package/src/oauth/plan.ts +29 -0
- package/src/oauth/store.ts +40 -3
- package/src/oauth/types.ts +2 -0
- package/src/plugins/loader.ts +11 -0
- package/src/protocols/codecs/chat-ir.ts +467 -0
- package/src/protocols/codecs/chat.ts +10 -1
- package/src/protocols/codecs/messages-ir.ts +500 -0
- package/src/protocols/features.ts +10 -2
- package/src/protocols/path.ts +11 -1
- package/src/protocols/plan-snapshot.ts +39 -16
- package/src/protocols/plan.ts +9 -1
- package/src/protocols/settings.ts +8 -0
- package/src/providers/anthropic-billing-ownership.ts +74 -0
- package/src/providers/anthropic-billing-recovery.ts +31 -0
- package/src/providers/anthropic-billing-refusal.ts +55 -0
- package/src/providers/anthropic-reset-admission.ts +62 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +28 -6
- package/src/providers/antigravity-account-roster.ts +102 -0
- package/src/providers/antigravity-models.ts +85 -7
- package/src/providers/api-key-resolve.ts +17 -2
- package/src/providers/key-failover.ts +52 -5
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/model-rename-migration.ts +5 -3
- package/src/providers/model-rename-startup.ts +5 -3
- package/src/providers/quota/account-cache.ts +11 -0
- package/src/providers/quota/anthropic-account-quota.ts +4 -2
- package/src/providers/quota/report-cache.ts +14 -0
- package/src/providers/quota/vendor-probes-oauth.ts +3 -3
- package/src/providers/quota.ts +4 -1
- package/src/providers/registry/entries-core.ts +21 -20
- package/src/providers/registry/entries-extended.ts +19 -1
- package/src/providers/registry/model-seeds.ts +17 -0
- package/src/providers/request-pacing.ts +9 -0
- package/src/providers/stale-no-vision-seed-migration.ts +72 -0
- package/src/responses/compaction.ts +14 -0
- package/src/responses/parser.ts +37 -20
- package/src/responses/state.ts +32 -29
- package/src/routing/history/build-file.ts +92 -0
- package/src/routing/history/indexer.ts +248 -216
- package/src/routing/history/ingest.ts +238 -0
- package/src/routing/history/rebuild-job.ts +111 -0
- package/src/routing/history/rebuild-protocol.ts +15 -0
- package/src/routing/history/rebuild-worker.ts +34 -0
- package/src/server/admission-model-scope.ts +4 -1
- package/src/server/antigravity-direct-eligibility.ts +208 -0
- package/src/server/auth-cors.ts +46 -0
- package/src/server/chat-completions.ts +75 -9
- package/src/server/claude-messages.ts +110 -20
- package/src/server/configured-credentials.ts +194 -0
- package/src/server/index/desktop-compatibility-startup.ts +56 -0
- package/src/server/index/link-management.ts +58 -0
- package/src/server/index/optional-listeners.ts +5 -1
- package/src/server/index/serve-options.ts +15 -2
- package/src/server/index/startup-warnings.ts +9 -0
- package/src/server/index/websocket-handler.ts +9 -0
- package/src/server/index.ts +13 -10
- package/src/server/inference/client-encoder-delivery.ts +6 -5
- package/src/server/lifecycle.ts +2 -1
- package/src/server/login-shell-path.ts +146 -0
- package/src/server/management/advisor-routes.ts +191 -0
- package/src/server/management/agent-settings-routes.ts +50 -14
- package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
- package/src/server/management/codex-agent-role-routes.ts +19 -6
- package/src/server/management/codex-prompt-routes.ts +136 -5
- package/src/server/management/codex-role-auto-assign.ts +1 -1
- package/src/server/management/companion-routes.ts +4 -0
- package/src/server/management/config-routes.ts +2 -2
- package/src/server/management/context.ts +4 -0
- package/src/server/management/desktop-compatibility-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
- package/src/server/management/grok-coupon-routes.ts +78 -27
- package/src/server/management/link-enrollment.ts +192 -0
- package/src/server/management/link-route-state.ts +185 -0
- package/src/server/management/link-routes.ts +82 -402
- package/src/server/management/model-routes.ts +2 -2
- package/src/server/management/model-rows.ts +3 -1
- package/src/server/management/protocol-settings-patch.ts +1 -0
- package/src/server/management/provider-overwrite-carry.ts +10 -0
- package/src/server/management/provider-routes.ts +1 -0
- package/src/server/management/route-registry.ts +16 -3
- package/src/server/management/shared.ts +3 -0
- package/src/server/management/sibling-guard.ts +1 -0
- package/src/server/management-api.ts +9 -1
- package/src/server/messages-antigravity-direct.ts +195 -0
- package/src/server/messages-native.ts +73 -43
- package/src/server/relay-eager.ts +2 -0
- package/src/server/request-log-filter.ts +6 -1
- package/src/server/request-log-terminal-status.ts +63 -6
- package/src/server/request-log.ts +7 -0
- package/src/server/responses/adapter-continuation.ts +35 -18
- package/src/server/responses/adapter-delivery.ts +24 -2
- package/src/server/responses/adapter-dispatch.ts +261 -81
- package/src/server/responses/advisor-plan-slot.ts +27 -0
- package/src/server/responses/advisor-slot.ts +336 -0
- package/src/server/responses/code-mode-collab-surface.ts +313 -0
- package/src/server/responses/codex-ws-exchange.ts +147 -6
- package/src/server/responses/codex-ws-wire.ts +203 -0
- package/src/server/responses/collaboration.ts +36 -7
- package/src/server/responses/compact.ts +126 -48
- package/src/server/responses/context-overflow.ts +13 -1
- package/src/server/responses/core-codex-account.ts +105 -1
- package/src/server/responses/core-combo-failure.ts +24 -1
- package/src/server/responses/core-combo.ts +28 -2
- package/src/server/responses/core-errors.ts +29 -1
- package/src/server/responses/core-lifetime.ts +2 -0
- package/src/server/responses/core-normalize.ts +11 -0
- package/src/server/responses/core-options.ts +48 -0
- package/src/server/responses/core-replay.ts +7 -4
- package/src/server/responses/core.ts +1 -1
- package/src/server/responses/deferred-collab-surface.ts +158 -0
- package/src/server/responses/deferred-send-reporter.ts +25 -0
- package/src/server/responses/fetch-helpers.ts +12 -1
- package/src/server/responses/passthrough-delivery.ts +20 -11
- package/src/server/responses/passthrough-dispatch.ts +29 -7
- package/src/server/responses/request-prepare.ts +74 -9
- package/src/server/responses/request-transport.ts +22 -8
- package/src/server/responses/run-turn-execution.ts +70 -8
- package/src/server/responses/sidecar-execution.ts +89 -5
- package/src/server/responses/terminal-guard.ts +2 -1
- package/src/server/responses/ws-upstream.ts +9 -1
- package/src/server/responses-undeclared-tool-guard.ts +30 -9
- package/src/server/startup-action-control.ts +38 -4
- package/src/server/startup-health-cache.ts +5 -0
- package/src/server/system-env-shell.ts +7 -2
- package/src/server/system-env.ts +11 -1
- package/src/service/cli.ts +5 -1
- package/src/service/desktop-startup.ts +161 -34
- package/src/service/desktop-supervision.d.mts +27 -1
- package/src/service/desktop-supervision.mjs +111 -5
- package/src/service/health-sentinel-launch.ts +176 -0
- package/src/service/health-sentinel-main.ts +466 -0
- package/src/service/health-sentinel-policy.ts +241 -0
- package/src/service/health-sentinel-probe.ts +71 -0
- package/src/service/health-sentinel-windows.ts +87 -0
- package/src/service/linux-linger.ts +20 -0
- package/src/service/logging.ts +72 -0
- package/src/tray/windows-tray.ps1 +43 -24
- package/src/types/config.ts +68 -6
- package/src/types/provider.ts +7 -0
- package/src/types/request.ts +15 -0
- package/src/types/tools.ts +2 -0
- package/src/update/async-check.ts +1 -1
- package/src/update/badge.ts +2 -2
- package/src/update/index.ts +88 -15
- package/src/update/job-supervised-restart.ts +107 -0
- package/src/update/job.ts +32 -32
- package/src/update/notify.ts +1 -1
- package/src/update/refresh-scheduler.ts +2 -2
- package/src/update/supervised-recovery.d.mts +27 -0
- package/src/update/supervised-recovery.mjs +157 -0
- package/src/usage/cache-diagnostic.ts +2 -0
- package/src/usage/expected-prices.ts +16 -0
- package/src/usage/log.ts +19 -1
- package/src/web-search/gemini-executor.ts +2 -2
- package/assets/sponsors/packycode.png +0 -0
- package/gui/dist/assets/App-Ba8uG696.js +0 -54
- package/gui/dist/assets/Tray-IImbNPRy.js +0 -1
- package/gui/dist/assets/index-BHRZdJuv.js +0 -94
- package/gui/dist/assets/quota-summary-DiJO6sYY.js +0 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { closeSync, constants, fstatSync, openSync, readSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
// CRLF normalization can halve the byte count. Every body within 64 KiB
|
|
4
|
+
// therefore fits this raw ceiling; title bytes stay outside the body budget.
|
|
5
|
+
const MAX_IMPORT_SOURCE_BYTES = 2 * 64 * 1024;
|
|
6
|
+
|
|
7
|
+
/** The prompt-text-probe descriptor pattern: nonblocking, regular, bounded. */
|
|
8
|
+
export function readBaseImportSource(path: string): string {
|
|
9
|
+
const nonBlocking = (constants as { O_NONBLOCK?: number }).O_NONBLOCK ?? 0;
|
|
10
|
+
const descriptor = openSync(path, constants.O_RDONLY | nonBlocking);
|
|
11
|
+
try {
|
|
12
|
+
if (!fstatSync(descriptor).isFile()) throw new Error("prompt source is not a regular file");
|
|
13
|
+
const parts: Buffer[] = [];
|
|
14
|
+
const view = Buffer.allocUnsafe(8192);
|
|
15
|
+
let total = 0;
|
|
16
|
+
for (;;) {
|
|
17
|
+
const count = readSync(descriptor, view, 0, Math.min(view.length, MAX_IMPORT_SOURCE_BYTES + 1 - total), total);
|
|
18
|
+
if (count === 0) return Buffer.concat(parts).toString("utf8");
|
|
19
|
+
total += count;
|
|
20
|
+
if (total > MAX_IMPORT_SOURCE_BYTES) {
|
|
21
|
+
const error = new Error("prompt source exceeds the import byte ceiling") as NodeJS.ErrnoException;
|
|
22
|
+
error.code = "EFBIG";
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
parts.push(Buffer.from(view.subarray(0, count)));
|
|
26
|
+
}
|
|
27
|
+
} finally {
|
|
28
|
+
closeSync(descriptor);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OCX_SECTION_MARKER } from "../injected-marker";
|
|
2
|
-
import { encodeBasicString } from "./encoding";
|
|
2
|
+
import { encodeBasicString, lexicalLineStarts, matchKeyHead } from "./encoding";
|
|
3
3
|
import { TABLE_HEADER, ANY_DEV_INSTRUCTIONS, DEV_INSTRUCTIONS_KEY } from "./toml-read";
|
|
4
4
|
|
|
5
5
|
/** Line editing, not re-serialization: the user's comments and layout survive. */
|
|
@@ -37,28 +37,156 @@ function joinLines(lines: string[], eol: "\r\n" | "\n"): string {
|
|
|
37
37
|
return eol === "\n" ? text : text.replace(/\n/g, "\r\n");
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
function firstTableIndex(lines: string[]): number {
|
|
41
|
-
const idx = lines.findIndex(l => TABLE_HEADER.test(l));
|
|
40
|
+
function firstTableIndex(lines: string[], starts?: readonly boolean[]): number {
|
|
41
|
+
const idx = lines.findIndex((l, i) => (starts === undefined || starts[i] === true) && TABLE_HEADER.test(l));
|
|
42
42
|
return idx === -1 ? lines.length : idx;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
/**
|
|
46
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Raised when a recognized `key =` line carries a value a line editor cannot
|
|
47
|
+
* replace safely — a multi-line `"""…"""`/`'''…'''` span, an unterminated
|
|
48
|
+
* quote, or trailing junk that is neither value nor comment. Callers refuse
|
|
49
|
+
* BEFORE writing rather than splice over a span they cannot see the end of.
|
|
50
|
+
*/
|
|
51
|
+
export class UnsupportedTomlForm extends Error {
|
|
52
|
+
constructor(readonly line: string) {
|
|
53
|
+
super(`unsupported TOML value form: ${line.trim()}`);
|
|
54
|
+
this.name = "UnsupportedTomlForm";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Where `<key> =` ends and the value begins. `prefix` keeps indent and key quoting. */
|
|
59
|
+
interface AssignmentHead {
|
|
60
|
+
prefix: string;
|
|
61
|
+
rest: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Lexical line scope for the editors below (see `lexicalLineStarts`). A span
|
|
66
|
+
* that never closes means the scope of every later line is unknown, so the
|
|
67
|
+
* whole edit refuses instead of guessing.
|
|
68
|
+
*/
|
|
69
|
+
function scopedLineStarts(lines: readonly string[]): boolean[] {
|
|
70
|
+
const starts = lexicalLineStarts(lines);
|
|
71
|
+
if (starts === null) throw new UnsupportedTomlForm("configuration contains an unterminated string or composite value");
|
|
72
|
+
return starts;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Split `rest` (everything after `key =`) into the value token and a trailing
|
|
77
|
+
* `#…` comment — adjacent or whitespace-separated, both preserved.
|
|
78
|
+
*
|
|
79
|
+
* The scan is string-aware so a `#` inside a quoted value is never mistaken
|
|
80
|
+
* for a comment opener: `"a#b" # note` parses as value `"a#b"` + comment
|
|
81
|
+
* ` # note`, while `false# note` parses as `false` + `# note`. Anything the
|
|
82
|
+
* token scan cannot classify — a multi-line string opener, an unterminated
|
|
83
|
+
* quote, text after the value that is not a comment — throws
|
|
84
|
+
* UnsupportedTomlForm so the caller refuses rather than guesses a span.
|
|
85
|
+
*/
|
|
86
|
+
function splitValueComment(head: AssignmentHead, line: string): { value: string; comment: string } {
|
|
87
|
+
const { rest } = head;
|
|
88
|
+
if (rest.startsWith('"""') || rest.startsWith("'''")) throw new UnsupportedTomlForm(line);
|
|
89
|
+
let value: string;
|
|
90
|
+
const first = rest[0];
|
|
91
|
+
if (first === '"') {
|
|
92
|
+
// Basic string: an escaped quote does not close, an unterminated one refuses.
|
|
93
|
+
let i = 1;
|
|
94
|
+
while (i < rest.length && rest[i] !== '"') i += rest[i] === "\\" ? 2 : 1;
|
|
95
|
+
if (i >= rest.length) throw new UnsupportedTomlForm(line);
|
|
96
|
+
value = rest.slice(0, i + 1);
|
|
97
|
+
} else if (first === "'") {
|
|
98
|
+
// Literal string: no escapes, the next ' closes it.
|
|
99
|
+
const end = rest.indexOf("'", 1);
|
|
100
|
+
if (end === -1) throw new UnsupportedTomlForm(line);
|
|
101
|
+
value = rest.slice(0, end + 1);
|
|
102
|
+
} else {
|
|
103
|
+
// Composite values may continue onto another line. This scalar editor
|
|
104
|
+
// refuses them instead of replacing only their opener and orphaning the tail.
|
|
105
|
+
if (first === "[" || first === "{") throw new UnsupportedTomlForm(line);
|
|
106
|
+
const m = /^[^\s#]+/.exec(rest);
|
|
107
|
+
if (!m) throw new UnsupportedTomlForm(line);
|
|
108
|
+
value = m[0];
|
|
109
|
+
}
|
|
110
|
+
const tail = rest.slice(value.length);
|
|
111
|
+
if (tail !== "" && !/^\s*(?:#|$)/.test(tail)) throw new UnsupportedTomlForm(line);
|
|
112
|
+
return { value, comment: tail };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The shape of `key`'s root-scope assignment, or "absent". "unsupported" means
|
|
117
|
+
* a `key =` line was found but its value is a form the line editor refuses —
|
|
118
|
+
* multiline strings and the like — so a caller can fail BEFORE touching disk
|
|
119
|
+
* instead of inside the transaction.
|
|
120
|
+
*/
|
|
121
|
+
export function rootKeyValueForm(content: string, key: string): "absent" | "simple" | "unsupported" {
|
|
122
|
+
const { body } = splitBom(content);
|
|
123
|
+
const lines = splitLines(body);
|
|
124
|
+
let starts: boolean[];
|
|
125
|
+
try { starts = scopedLineStarts(lines); } catch { return "unsupported"; }
|
|
126
|
+
const limit = firstTableIndex(lines, starts);
|
|
127
|
+
for (let i = 0; i < limit; i += 1) {
|
|
128
|
+
if (!starts[i]) continue;
|
|
129
|
+
const head = matchKeyHead(lines[i]!, key);
|
|
130
|
+
if (!head) continue;
|
|
131
|
+
try {
|
|
132
|
+
splitValueComment(head, lines[i]!);
|
|
133
|
+
return "simple";
|
|
134
|
+
} catch {
|
|
135
|
+
return "unsupported";
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return "absent";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Replace or remove one matched line. The line's own indentation, key quoting,
|
|
143
|
+
* and trailing comment are preserved; `null` removes the assignment but keeps
|
|
144
|
+
* a comment behind so a `# note` written beside the key survives a reset.
|
|
145
|
+
*/
|
|
146
|
+
function applyLineEdit(lines: string[], i: number, key: string, replacement: string | null): void {
|
|
147
|
+
const head = matchKeyHead(lines[i]!, key)!;
|
|
148
|
+
const { comment } = splitValueComment(head, lines[i]!);
|
|
149
|
+
if (replacement === null) {
|
|
150
|
+
if (comment.trim()) lines[i] = comment.trim();
|
|
151
|
+
else lines.splice(i, 1);
|
|
152
|
+
} else {
|
|
153
|
+
lines[i] = `${head.prefix}${replacement}${comment}`;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
type EditMode = "inplace" | "appended" | "noop";
|
|
158
|
+
interface LineEdit { text: string; mode: EditMode }
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Set a root-scope boolean, inserting above the first table when absent.
|
|
162
|
+
*
|
|
163
|
+
* `null` REMOVES the key rather than writing a value, which is what restoring a
|
|
164
|
+
* documented default means: `include_permissions_instructions = true` and an
|
|
165
|
+
* absent key are different facts about the same file, and only the absent one
|
|
166
|
+
* lets a changed upstream default ever take effect again.
|
|
167
|
+
*
|
|
168
|
+
* The value slot is classified, not just skipped over: any value form the line
|
|
169
|
+
* editor cannot see the end of — a multi-line string span, an unterminated
|
|
170
|
+
* quote — throws UnsupportedTomlForm so the write is refused instead of
|
|
171
|
+
* appending a second assignment TOML would reject as a duplicate key. A value
|
|
172
|
+
* that is not a boolean is a bad fact about the same key, so it is REPLACED,
|
|
173
|
+
* and a trailing comment survives a write or a removal whether or not a space
|
|
174
|
+
* precedes the `#`.
|
|
175
|
+
*/
|
|
176
|
+
function setRootBoolLines(content: string, key: string, value: boolean | null): LineEdit {
|
|
47
177
|
const eol = dominantEol(content);
|
|
48
178
|
const { bom, body } = splitBom(content);
|
|
49
179
|
const lines = splitLines(body);
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const pattern = new RegExp(`^(\\s*${escaped}\\s*=\\s*)(?:true|false)(\\s*(?:#.*)?)$`);
|
|
180
|
+
const starts = scopedLineStarts(lines);
|
|
181
|
+
const limit = firstTableIndex(lines, starts);
|
|
53
182
|
for (let i = 0; i < limit; i += 1) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return bom + joinLines(lines, eol);
|
|
58
|
-
}
|
|
183
|
+
if (!starts[i] || !matchKeyHead(lines[i]!, key)) continue;
|
|
184
|
+
applyLineEdit(lines, i, key, value === null ? null : String(value));
|
|
185
|
+
return { text: bom + joinLines(lines, eol), mode: "inplace" };
|
|
59
186
|
}
|
|
187
|
+
if (value === null) return { text: bom + joinLines(lines, eol), mode: "noop" };
|
|
60
188
|
lines.splice(limit, 0, `${key} = ${value}`);
|
|
61
|
-
return bom + joinLines(lines, eol);
|
|
189
|
+
return { text: bom + joinLines(lines, eol), mode: "appended" };
|
|
62
190
|
}
|
|
63
191
|
|
|
64
192
|
/**
|
|
@@ -68,51 +196,56 @@ export function setRootBool(content: string, key: string, value: boolean): strin
|
|
|
68
196
|
* rather than an empty string: `model_instructions_file = ""` is a path Codex would try
|
|
69
197
|
* to read, not an absent setting.
|
|
70
198
|
*/
|
|
71
|
-
|
|
199
|
+
function setRootStringLines(content: string, key: string, value: string | null): LineEdit {
|
|
72
200
|
const eol = dominantEol(content);
|
|
73
201
|
const { bom, body } = splitBom(content);
|
|
74
202
|
const lines = splitLines(body);
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
const pattern = new RegExp(`^\\s*${escaped}\\s*=\\s*"[^"]*"\\s*(?:#.*)?$`);
|
|
203
|
+
const starts = scopedLineStarts(lines);
|
|
204
|
+
const limit = firstTableIndex(lines, starts);
|
|
78
205
|
for (let i = 0; i < limit; i += 1) {
|
|
79
|
-
if (!
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return bom + joinLines(lines, eol);
|
|
206
|
+
if (!starts[i] || !matchKeyHead(lines[i]!, key)) continue;
|
|
207
|
+
applyLineEdit(lines, i, key, value === null ? null : encodeBasicString(value));
|
|
208
|
+
return { text: bom + joinLines(lines, eol), mode: "inplace" };
|
|
83
209
|
}
|
|
84
|
-
if (value === null) return bom + joinLines(lines, eol);
|
|
210
|
+
if (value === null) return { text: bom + joinLines(lines, eol), mode: "noop" };
|
|
85
211
|
lines.splice(limit, 0, `${key} = ${encodeBasicString(value)}`);
|
|
86
|
-
return bom + joinLines(lines, eol);
|
|
212
|
+
return { text: bom + joinLines(lines, eol), mode: "appended" };
|
|
87
213
|
}
|
|
88
214
|
|
|
89
|
-
/**
|
|
90
|
-
|
|
215
|
+
/**
|
|
216
|
+
* Set a boolean inside `[table]`, appending the table when absent.
|
|
217
|
+
*
|
|
218
|
+
* `null` removes the key line but leaves the table header: an empty `[skills]`
|
|
219
|
+
* is valid TOML, and deleting the header would also orphan any comments the
|
|
220
|
+
* user wrote inside the table.
|
|
221
|
+
*/
|
|
222
|
+
function setTableBoolLines(content: string, table: string, key: string, value: boolean | null): LineEdit {
|
|
91
223
|
const eol = dominantEol(content);
|
|
92
224
|
const { bom, body } = splitBom(content);
|
|
93
225
|
const lines = splitLines(body);
|
|
226
|
+
const starts = scopedLineStarts(lines);
|
|
94
227
|
const escaped = table.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
95
|
-
const
|
|
228
|
+
const header = new RegExp(`^\\s*\\[${escaped}\\]\\s*(?:#.*)?$`);
|
|
229
|
+
const start = lines.findIndex((l, i) => starts[i] === true && header.test(l));
|
|
96
230
|
if (start === -1) {
|
|
231
|
+
if (value === null) return { text: bom + joinLines(lines, eol), mode: "noop" };
|
|
97
232
|
const tail = lines.length > 0 && lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
|
|
98
233
|
lines.splice(tail, 0, `[${table}]`, `${key} = ${value}`);
|
|
99
|
-
return bom + joinLines(lines, eol);
|
|
234
|
+
return { text: bom + joinLines(lines, eol), mode: "appended" };
|
|
100
235
|
}
|
|
101
|
-
const keyEscaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
102
|
-
const pattern = new RegExp(`^(\\s*${keyEscaped}\\s*=\\s*)(?:true|false)(\\s*(?:#.*)?)$`);
|
|
103
236
|
let end = start + 1;
|
|
104
|
-
while (end < lines.length && !TABLE_HEADER.test(lines[end]!)) end += 1;
|
|
237
|
+
while (end < lines.length && !(starts[end] && TABLE_HEADER.test(lines[end]!))) end += 1;
|
|
105
238
|
for (let i = start + 1; i < end; i += 1) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return bom + joinLines(lines, eol);
|
|
110
|
-
}
|
|
239
|
+
if (!starts[i] || !matchKeyHead(lines[i]!, key)) continue;
|
|
240
|
+
applyLineEdit(lines, i, key, value === null ? null : String(value));
|
|
241
|
+
return { text: bom + joinLines(lines, eol), mode: "inplace" };
|
|
111
242
|
}
|
|
243
|
+
if (value === null) return { text: bom + joinLines(lines, eol), mode: "noop" };
|
|
112
244
|
lines.splice(end, 0, `${key} = ${value}`);
|
|
113
|
-
return bom + joinLines(lines, eol);
|
|
245
|
+
return { text: bom + joinLines(lines, eol), mode: "appended" };
|
|
114
246
|
}
|
|
115
247
|
|
|
248
|
+
|
|
116
249
|
/**
|
|
117
250
|
* Replace, insert, or remove the generated two-line block. Canonical form is
|
|
118
251
|
* marker + assignment at the top of the document; replacement is "find the
|
|
@@ -161,3 +294,102 @@ export function removeUnownedProjection(content: string): string {
|
|
|
161
294
|
}
|
|
162
295
|
return joinLines(lines, eol);
|
|
163
296
|
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Refuse, before any write, an edit that does not do exactly what it was asked.
|
|
300
|
+
*
|
|
301
|
+
* The line editors above address bare table headers and bare or quoted keys. A
|
|
302
|
+
* quoted, spaced, inline or dotted spelling of the same table makes them append a
|
|
303
|
+
* duplicate header (a file Codex cannot load) or, for restore-default, leave the
|
|
304
|
+
* override in place while the caller reports success. Both are caught by parsing
|
|
305
|
+
* the before and after images: the parsed result must equal the parsed input with
|
|
306
|
+
* only the intended path set or removed. When Bun cannot parse the input (its
|
|
307
|
+
* parser has documented gaps a valid Codex config can hit, such as i64 extremes),
|
|
308
|
+
* only an in-place replacement of a key the editor located is trusted; an append
|
|
309
|
+
* or a no-op restore cannot be verified and is refused. A target value with
|
|
310
|
+
* control characters is checked for presence and type only, for the same reason.
|
|
311
|
+
*/
|
|
312
|
+
function checkedEdit(before: string, edit: LineEdit, path: readonly string[], value: string | boolean | null): string {
|
|
313
|
+
const after = edit.text;
|
|
314
|
+
let parsedBefore: Record<string, unknown>;
|
|
315
|
+
try { parsedBefore = Bun.TOML.parse(before.replace(/^\ufeff/, "")) as Record<string, unknown>; } catch {
|
|
316
|
+
if (edit.mode === "inplace") return after;
|
|
317
|
+
throw new UnsupportedTomlForm("config.toml uses syntax this editor cannot verify; edit it by hand");
|
|
318
|
+
}
|
|
319
|
+
let parsedAfter: Record<string, unknown>;
|
|
320
|
+
try { parsedAfter = Bun.TOML.parse(after.replace(/^\ufeff/, "")) as Record<string, unknown>; } catch {
|
|
321
|
+
throw new UnsupportedTomlForm("edit would leave config.toml unparseable");
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
const expected = tomlClone(parsedBefore) as Record<string, unknown>;
|
|
325
|
+
let parent: Record<string, unknown> | null = expected;
|
|
326
|
+
let afterParent: unknown = parsedAfter;
|
|
327
|
+
for (const segment of path.slice(0, -1)) {
|
|
328
|
+
const next: unknown = parent?.[segment];
|
|
329
|
+
afterParent = isPlainTable(afterParent) ? afterParent[segment] : undefined;
|
|
330
|
+
if (next === undefined) {
|
|
331
|
+
if (value === null) { parent = null; break; }
|
|
332
|
+
parent![segment] = {};
|
|
333
|
+
parent = parent![segment] as Record<string, unknown>;
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (!isPlainTable(next)) throw new UnsupportedTomlForm("edit target is not a plain table");
|
|
337
|
+
parent = next;
|
|
338
|
+
}
|
|
339
|
+
const key = path[path.length - 1]!;
|
|
340
|
+
if (parent !== null) {
|
|
341
|
+
if (value === null) delete parent[key];
|
|
342
|
+
else if (typeof value === "string" && /[\u0000-\u001f\u007f]/.test(value)) {
|
|
343
|
+
const landed = isPlainTable(afterParent) ? afterParent[key] : undefined;
|
|
344
|
+
if (typeof landed !== "string") throw new UnsupportedTomlForm("edit did not land on the intended key");
|
|
345
|
+
parent[key] = landed;
|
|
346
|
+
} else parent[key] = value;
|
|
347
|
+
}
|
|
348
|
+
if (!tomlEqual(parsedAfter, expected)) throw new UnsupportedTomlForm("edit did not land on the intended key");
|
|
349
|
+
} catch (error) {
|
|
350
|
+
if (error instanceof UnsupportedTomlForm) throw error;
|
|
351
|
+
throw new UnsupportedTomlForm("edit could not be verified");
|
|
352
|
+
}
|
|
353
|
+
return after;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function isPlainTable(value: unknown): value is Record<string, unknown> {
|
|
357
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
358
|
+
const proto = Object.getPrototypeOf(value);
|
|
359
|
+
return proto === Object.prototype || proto === null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Arrays and plain tables are copied; TOML date/time values are immutable and shared. */
|
|
363
|
+
function tomlClone(value: unknown): unknown {
|
|
364
|
+
if (Array.isArray(value)) return value.map(tomlClone);
|
|
365
|
+
if (isPlainTable(value)) return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, tomlClone(v)]));
|
|
366
|
+
return value;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function tomlEqual(a: unknown, b: unknown): boolean {
|
|
370
|
+
if (Object.is(a, b)) return true;
|
|
371
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
372
|
+
return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((v, i) => tomlEqual(v, b[i]));
|
|
373
|
+
}
|
|
374
|
+
if (isPlainTable(a) || isPlainTable(b)) {
|
|
375
|
+
if (!isPlainTable(a) || !isPlainTable(b)) return false;
|
|
376
|
+
const ka = Object.keys(a), kb = Object.keys(b);
|
|
377
|
+
return ka.length === kb.length && ka.every(k => Object.hasOwn(b, k) && tomlEqual(a[k], b[k]));
|
|
378
|
+
}
|
|
379
|
+
if (a !== null && b !== null && typeof a === "object" && typeof b === "object") {
|
|
380
|
+
return Object.getPrototypeOf(a) === Object.getPrototypeOf(b) && String(a) === String(b);
|
|
381
|
+
}
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export function setRootBool(content: string, key: string, value: boolean | null): string {
|
|
386
|
+
return checkedEdit(content, setRootBoolLines(content, key, value), [key], value);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export function setRootString(content: string, key: string, value: string | null): string {
|
|
390
|
+
return checkedEdit(content, setRootStringLines(content, key, value), [key], value);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export function setTableBool(content: string, table: string, key: string, value: boolean | null): string {
|
|
394
|
+
return checkedEdit(content, setTableBoolLines(content, table, key, value), [...table.split("."), key], value);
|
|
395
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OCX_SECTION_MARKER } from "../injected-marker";
|
|
2
|
-
import { decodeBasicString } from "./encoding";
|
|
2
|
+
import { decodeBasicString, lexicalLineStarts, matchKeyHead } from "./encoding";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Decoded string entries of a root-scope TOML array.
|
|
@@ -118,6 +118,25 @@ export function rootLines(content: string): string[] {
|
|
|
118
118
|
return first === -1 ? lines : lines.slice(0, first);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Root-scope lines that start outside every string and composite span. Used by
|
|
123
|
+
* fallbacks that scan lines because the parser refused the file (for example a
|
|
124
|
+
* Codex-valid i64 Bun cannot represent): prose inside a multiline value must not
|
|
125
|
+
* be read as a setting. A file whose spans never close keeps the plain scan.
|
|
126
|
+
*/
|
|
127
|
+
export function scopedRootLines(content: string): string[] {
|
|
128
|
+
const lines = content.split("\n");
|
|
129
|
+
const starts = lexicalLineStarts(lines);
|
|
130
|
+
if (starts === null) return rootLines(content);
|
|
131
|
+
const out: string[] = [];
|
|
132
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
133
|
+
if (!starts[i]) continue;
|
|
134
|
+
if (TABLE_HEADER.test(lines[i]!)) break;
|
|
135
|
+
out.push(lines[i]!);
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
|
|
121
140
|
/** Lines of `[header]`'s body, up to the next table header. */
|
|
122
141
|
export function tableLines(content: string, header: string): string[] | null {
|
|
123
142
|
const lines = content.split("\n");
|
|
@@ -129,11 +148,40 @@ export function tableLines(content: string, header: string): string[] | null {
|
|
|
129
148
|
return end === -1 ? rest : rest.slice(0, end);
|
|
130
149
|
}
|
|
131
150
|
|
|
151
|
+
/**
|
|
152
|
+
* A root-scope (`table === null`) or `[table]` boolean, reading only lines that
|
|
153
|
+
* start outside every string and composite span, so assignment-shaped prose in a
|
|
154
|
+
* multiline value is never reported as the setting. A file whose spans never
|
|
155
|
+
* close falls back to the plain line scan; Codex cannot load it either way.
|
|
156
|
+
*/
|
|
157
|
+
export function scopedBool(content: string, table: string | null, key: string): boolean | null {
|
|
158
|
+
const lines = content.split("\n");
|
|
159
|
+
const starts = lexicalLineStarts(lines);
|
|
160
|
+
if (starts === null) {
|
|
161
|
+
const scope = table === null ? rootLines(content) : tableLines(content, table);
|
|
162
|
+
return scope === null ? null : boolInLines(scope, key);
|
|
163
|
+
}
|
|
164
|
+
const top = (i: number) => starts[i] === true;
|
|
165
|
+
let from = 0;
|
|
166
|
+
if (table !== null) {
|
|
167
|
+
const escaped = table.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
168
|
+
const header = new RegExp(`^\\s*\\[${escaped}\\]\\s*(?:#.*)?$`);
|
|
169
|
+
from = lines.findIndex((l, i) => top(i) && header.test(l)) + 1;
|
|
170
|
+
if (from === 0) return null;
|
|
171
|
+
}
|
|
172
|
+
for (let i = from; i < lines.length; i += 1) {
|
|
173
|
+
if (!top(i)) continue;
|
|
174
|
+
if (TABLE_HEADER.test(lines[i]!)) break;
|
|
175
|
+
const value = boolInLines([lines[i]!], key);
|
|
176
|
+
if (value !== null) return value;
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
132
181
|
export function boolInLines(lines: string[], key: string): boolean | null {
|
|
133
|
-
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
134
|
-
const pattern = new RegExp(`^\\s*${escaped}\\s*=\\s*(true|false)\\s*(?:#.*)?$`);
|
|
135
182
|
for (const line of lines) {
|
|
136
|
-
const
|
|
183
|
+
const head = matchKeyHead(line, key);
|
|
184
|
+
const m = head === null ? null : /^(true|false)\s*(?:#.*)?$/.exec(head.rest);
|
|
137
185
|
if (m) return m[1] === "true";
|
|
138
186
|
}
|
|
139
187
|
return null;
|