@bitkyc08/opencodex 2.59.0 → 2.61.0-preview.20260922
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 +64 -0
- package/README.md +28 -1
- package/bin/ocx.mjs +382 -209
- package/gui/dist/assets/App-E64Rzjap.js +50 -0
- package/gui/dist/assets/Tray-_nfzD8k4.js +1 -0
- package/gui/dist/assets/index-DpdfZWMK.js +86 -0
- package/gui/dist/assets/index-_bpvxJu0.css +1 -0
- package/gui/dist/assets/usage-companion-chart-DtoK7T6h.js +1 -0
- package/gui/dist/favicon.png +0 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/stepfun-color.svg +1 -0
- package/package.json +8 -1
- package/src/adapters/anthropic-image-codec.ts +16 -2
- package/src/adapters/anthropic-image-normalize.ts +49 -2
- package/src/adapters/anthropic.ts +20 -1
- package/src/adapters/base.ts +23 -0
- package/src/adapters/coding-agent/protocol.ts +36 -6
- package/src/adapters/coding-agent/turn.ts +32 -4
- package/src/adapters/command-code.ts +52 -4
- package/src/adapters/cursor/catalog.ts +51 -7
- package/src/adapters/cursor/checkpoint-store.ts +3 -0
- package/src/adapters/cursor/discovery.ts +11 -8
- package/src/adapters/cursor/live-transport.ts +26 -9
- package/src/adapters/cursor/protobuf-request.ts +6 -3
- package/src/adapters/cursor/request-builder.ts +20 -4
- package/src/adapters/cursor/transport.ts +19 -0
- package/src/adapters/cursor.ts +14 -3
- package/src/adapters/declaration-carrier.ts +45 -0
- package/src/adapters/devin/cloud-direct/chat.ts +3 -1
- package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +42 -5
- package/src/adapters/devin.ts +125 -36
- package/src/adapters/google-antigravity-replay.ts +1 -1
- package/src/adapters/google-antigravity-wire.ts +34 -7
- package/src/adapters/google-errors.ts +7 -1
- package/src/adapters/google-http.ts +49 -10
- package/src/adapters/google-tool-schema.ts +595 -31
- package/src/adapters/google-wire-compiler.ts +93 -10
- package/src/adapters/google-wire-shape.ts +461 -0
- package/src/adapters/google.ts +66 -11
- package/src/adapters/image.ts +4 -1
- package/src/adapters/input-media-guard.ts +21 -9
- package/src/adapters/kiro/usage.ts +3 -2
- package/src/adapters/kiro-tool-fallback.ts +1 -1
- package/src/adapters/ollama-native.ts +6 -0
- package/src/adapters/openai-chat/developer-role.ts +61 -0
- package/src/adapters/openai-chat/messages.ts +46 -27
- package/src/adapters/openai-chat/parallel-tool-calls.ts +32 -0
- package/src/adapters/openai-chat/passthrough.ts +33 -9
- package/src/adapters/openai-chat/reasoning-wire.ts +89 -0
- package/src/adapters/openai-chat-images.ts +3 -1
- package/src/adapters/openai-chat.ts +23 -58
- package/src/adapters/openai-responses/image-gen.ts +8 -6
- package/src/adapters/openai-responses/passthrough.ts +17 -3
- package/src/adapters/openai-responses/reasoning.ts +7 -0
- package/src/adapters/opencode-go-additional-tools.ts +12 -2
- package/src/adapters/registry.ts +3 -2
- package/src/adapters/run-turn-queue.ts +178 -29
- package/src/adapters/xai-web-search.ts +16 -1
- package/src/bridge/errors.ts +8 -2
- package/src/bridge/response-json.ts +9 -1
- package/src/bridge/sse.ts +13 -151
- package/src/chat/inbound.ts +141 -5
- package/src/claude/desktop-3p.ts +7 -1
- package/src/claude/desktop-first-party.ts +183 -0
- package/src/claude/desktop-gateway-state.ts +41 -0
- package/src/claude/inbound-content-options.ts +6 -0
- package/src/claude/inbound.ts +32 -6
- package/src/claude/intercept/connect-proxy.ts +179 -0
- package/src/claude/intercept/listener.ts +122 -0
- package/src/claude/intercept/local-ca.ts +298 -0
- package/src/claude/intercept/runtime.ts +98 -0
- package/src/claude/intercept/settings.ts +189 -0
- package/src/cli/access.ts +87 -0
- package/src/cli/account-auth.ts +19 -0
- package/src/cli/account-extended.ts +4 -4
- package/src/cli/capabilities.ts +31 -0
- package/src/cli/claude-desktop.ts +206 -16
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/companion.ts +56 -0
- package/src/cli/dispatch.ts +46 -7
- package/src/cli/doctor.ts +28 -9
- package/src/cli/ensure-desired-integrations.ts +43 -5
- package/src/cli/help.ts +7 -9
- package/src/cli/hub.ts +3 -2
- package/src/cli/index.ts +203 -59
- package/src/cli/init.ts +8 -0
- package/src/cli/integrations.ts +7 -1
- package/src/cli/opencode.ts +2 -2
- package/src/cli/provider.ts +13 -1
- package/src/cli/registry.ts +41 -2
- package/src/cli/resolve.ts +230 -0
- package/src/cli/root.ts +24 -1
- package/src/cli/start-ownership-publication.ts +56 -0
- package/src/cli/status-probes.ts +2 -18
- package/src/cli/status.ts +62 -0
- package/src/cli/stop-report.ts +143 -0
- package/src/cli/uninstall-plan.ts +9 -0
- package/src/client/machine-api.ts +2 -2
- package/src/client/machine-listener.ts +4 -7
- package/src/client/runtime.ts +26 -2
- package/src/clients/aside-profiles.ts +4 -0
- package/src/clients/config-export/zcode-store.ts +157 -0
- package/src/clients/config-export.ts +36 -0
- package/src/codex/account-store.ts +65 -0
- package/src/codex/app-server-processes.ts +72 -40
- package/src/codex/auth-api/account-list.ts +19 -11
- package/src/codex/auth-api/login-flow.ts +6 -1
- package/src/codex/auth-api/pool-quota-probe.ts +30 -7
- package/src/codex/autostart-health.ts +28 -0
- package/src/codex/catalog/build-entries.ts +2 -2
- package/src/codex/catalog/effort.ts +3 -3
- package/src/codex/catalog/gather-capture.ts +21 -2
- package/src/codex/catalog/model-hints.ts +29 -28
- package/src/codex/catalog/parsing.ts +7 -0
- package/src/codex/catalog/provider-models.ts +43 -17
- package/src/codex/catalog/retained-sync.ts +24 -28
- package/src/codex/catalog/routed-gather.ts +19 -0
- package/src/codex/context-compat.ts +5 -2
- package/src/codex/convergence.ts +2 -2
- package/src/codex/desired-state.ts +4 -1
- package/src/codex/history-job.ts +6 -6
- package/src/codex/history-provider.ts +31 -166
- package/src/codex/history-rollout-read.ts +174 -0
- package/src/codex/inject/config-toml.ts +41 -6
- package/src/codex/inject/paginated-openai-compat.ts +90 -0
- package/src/codex/inject.ts +18 -15
- package/src/codex/injected-marker.ts +18 -0
- package/src/codex/internal/catalog-writer.ts +33 -1
- package/src/codex/main-account.ts +6 -0
- package/src/codex/model-cache.ts +99 -6
- package/src/codex/model-entitlement-admission.ts +59 -0
- package/src/codex/model-entitlements.ts +116 -54
- package/src/codex/native-main-admission.ts +83 -0
- package/src/codex/observed-model-denials.ts +101 -8
- package/src/codex/prompt-text-probe.ts +9 -6
- package/src/codex/routing/health-store.ts +39 -0
- package/src/codex/routing/selection.ts +37 -1
- package/src/codex/routing.ts +12 -42
- package/src/codex/shim-templates.ts +29 -3
- package/src/codex/shim.ts +1 -1
- package/src/codex/subagent-model-fallback.ts +22 -4
- package/src/combos/failover.ts +3 -0
- package/src/companion/settings.ts +132 -0
- package/src/config/admitted-identity.ts +222 -0
- package/src/config/atomic-write.ts +117 -5
- package/src/config/diagnostics.ts +22 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/load-degrade.ts +52 -7
- package/src/config/process-state.ts +1 -1
- package/src/config/proxy-env.ts +8 -2
- package/src/config/schema/compaction-triggers.ts +11 -0
- package/src/config/schema/config-schema.ts +31 -1
- package/src/config/schema/leaf-validators.ts +59 -0
- package/src/config.ts +3 -3
- package/src/generated/compatibility-version.json +604 -280
- package/src/grok/reset-coupons.ts +38 -19
- package/src/images/loop.ts +6 -1
- package/src/integrations/aside-profile-context.ts +37 -3
- package/src/integrations/aside-profile-journal.ts +68 -3
- package/src/integrations/aside-profiles.ts +128 -3
- package/src/integrations/config-io.ts +44 -10
- package/src/integrations/merge.ts +120 -13
- package/src/integrations/mutation-plan.ts +921 -0
- package/src/integrations/registry.ts +38 -0
- package/src/integrations/state.ts +78 -45
- package/src/integrations/target.ts +208 -0
- package/src/integrations/writer.ts +134 -110
- package/src/lab/conformance/fixture-provider.ts +5 -0
- package/src/lab/live/transport.ts +4 -0
- package/src/lab/live/types.ts +5 -0
- package/src/lab/subject/behavior-fingerprint.ts +1 -1
- package/src/lib/admin-secrets.ts +9 -1
- package/src/lib/browser-launch-notice.ts +59 -0
- package/src/lib/bun-runtime.ts +6 -2
- package/src/lib/debug-log-buffer.ts +6 -1
- package/src/lib/debug.ts +63 -0
- package/src/lib/errors.ts +79 -0
- package/src/lib/http-response-semantics.ts +57 -0
- package/src/lib/lab-live-pinned-sender.ts +26 -12
- package/src/lib/open-url.ts +51 -7
- package/src/lib/package-tree-integrity.ts +2 -1
- package/src/lib/package-version.ts +8 -0
- package/src/lib/pinned-http.ts +142 -2
- package/src/lib/plain-data.ts +103 -0
- package/src/lib/process-control.ts +13 -5
- package/src/lib/provider-egress.ts +310 -0
- package/src/lib/provider-outbound.ts +109 -16
- package/src/lib/proxy-env.ts +82 -7
- package/src/lib/request-execution-budget.ts +72 -0
- package/src/lib/request-failure-attribution.ts +183 -0
- package/src/lib/request-failure-model.ts +236 -0
- package/src/lib/request-resend-gate.ts +138 -0
- package/src/lib/socks5-fetch.ts +136 -26
- package/src/lib/spend-ledger-owner.ts +364 -0
- package/src/lib/spend-reservation-ledger.ts +218 -27
- package/src/lib/standalone.ts +16 -0
- package/src/lib/upstream-retry.ts +167 -16
- package/src/lib/windows-system-proxy.ts +16 -11
- package/src/lib/winsw.ts +2 -2
- package/src/oauth/callback-server.ts +4 -3
- package/src/oauth/generic-account-failover.ts +1 -0
- package/src/oauth/health.ts +12 -1
- package/src/oauth/index.ts +27 -108
- package/src/oauth/login-cli.ts +80 -29
- package/src/oauth/login-flow-state.ts +127 -0
- package/src/providers/api-key-resolve.ts +133 -0
- package/src/providers/api-key-selection.ts +5 -1
- package/src/providers/derive.ts +34 -17
- package/src/providers/devin-cli-authmode-migration.ts +14 -10
- package/src/providers/key-failover.ts +97 -19
- package/src/providers/key-store.ts +34 -110
- package/src/providers/model-rename-fields.ts +147 -0
- package/src/providers/model-rename-migration.ts +179 -38
- package/src/providers/model-rename-startup.ts +7 -5
- package/src/providers/openai-virtual-models.ts +42 -2
- package/src/providers/quota/antigravity.ts +22 -2
- package/src/providers/quota/vendor-probes-key.ts +38 -23
- package/src/providers/reasoning-metadata.ts +43 -18
- package/src/providers/registry/entries-core.ts +47 -20
- package/src/providers/registry/entries-extended.ts +63 -4
- package/src/providers/registry/model-ids.ts +168 -0
- package/src/providers/registry/model-seeds.ts +56 -10
- package/src/providers/registry/types.ts +2 -0
- package/src/providers/resolved-model-policy-merge.ts +167 -0
- package/src/providers/resolved-model-policy.ts +406 -0
- package/src/providers/stale-vision-classification-migration.ts +137 -0
- package/src/providers/xai-transport.ts +12 -1
- package/src/reasoning-effort.ts +8 -0
- package/src/responses/apply-patch-envelope.ts +0 -12
- package/src/responses/freeform-wrapper-scan.ts +279 -0
- package/src/responses/function-call-compat.ts +38 -1
- package/src/responses/inline-document.ts +65 -0
- package/src/responses/input-media.ts +42 -8
- package/src/responses/legacy-dotted-tool-name-repair.ts +134 -0
- package/src/responses/muse-tool-name-alias.ts +19 -0
- package/src/responses/parser-content.ts +8 -2
- package/src/responses/parser-tools.ts +3 -0
- package/src/responses/parser.ts +3 -1
- package/src/responses/progressive-freeform-input.ts +130 -0
- package/src/responses/reasoning-envelope.ts +30 -0
- package/src/responses/schema.ts +3 -0
- package/src/responses/state.ts +5 -12
- package/src/responses/tool-name-aliases.ts +15 -1
- package/src/router.ts +108 -117
- package/src/routing/compatibility/behavior.ts +9 -0
- package/src/routing/compatibility/subject.ts +16 -1
- package/src/server/adapter-resolve.ts +9 -0
- package/src/server/admission-model-scope.ts +219 -0
- package/src/server/audio-live.ts +9 -3
- package/src/server/audio-upstream.ts +18 -0
- package/src/server/auth-cors.ts +29 -0
- package/src/server/chat-completions.ts +60 -4
- package/src/server/chat-native.ts +19 -4
- package/src/server/claude-messages.ts +61 -20
- package/src/server/effort-row.ts +11 -3
- package/src/server/grok-responses-control-frame.ts +160 -1
- package/src/server/grok-responses-snapshot-repair.ts +113 -11
- package/src/server/gui-freshness.ts +103 -0
- package/src/server/gui-static.ts +7 -9
- package/src/server/images.ts +59 -6
- package/src/server/index/claude-intercept-lifecycle.ts +49 -0
- package/src/server/index/serve-options.ts +142 -39
- package/src/server/index/spend-ledger-lifecycle.ts +92 -0
- package/src/server/index/startup-warnings.ts +24 -0
- package/src/server/index/websocket-handler.ts +6 -1
- package/src/server/index.ts +34 -38
- package/src/server/lifecycle.ts +4 -4
- package/src/server/live-call-bindings.ts +6 -0
- package/src/server/live.ts +88 -3
- package/src/server/management/agent-settings-routes.ts +121 -36
- package/src/server/management/aside-profile-routes.ts +266 -7
- package/src/server/management/companion-routes.ts +77 -0
- package/src/server/management/config-routes.ts +18 -2
- package/src/server/management/context.ts +3 -0
- package/src/server/management/integration-routes.ts +287 -5
- package/src/server/management/logs-usage-routes.ts +19 -0
- package/src/server/management/metrics-routes.ts +20 -0
- package/src/server/management/model-rows.ts +224 -12
- package/src/server/management/native-integration-routes.ts +103 -6
- package/src/server/management/oauth-account-routes.ts +45 -7
- package/src/server/management/route-registry.ts +20 -0
- package/src/server/management/shared.ts +28 -4
- package/src/server/management/system-restart.ts +7 -2
- package/src/server/management/system-routes.ts +2 -0
- package/src/server/management/usage-aggregate-cache.ts +4 -0
- package/src/server/management/usage-timeline-routes.ts +44 -0
- package/src/server/management-api.ts +10 -9
- package/src/server/management-auth.ts +15 -1
- package/src/server/proxy-liveness.ts +75 -0
- package/src/server/readiness.ts +29 -10
- package/src/server/relay-eager.ts +24 -2
- package/src/server/relay.ts +138 -12
- package/src/server/request-log-failure-attribution.ts +99 -0
- package/src/server/request-log.ts +169 -2
- package/src/server/request-metrics.ts +298 -0
- package/src/server/responses/adapter-continuation.ts +3 -3
- package/src/server/responses/adapter-dispatch.ts +11 -6
- package/src/server/responses/codex-ws-wire.ts +34 -8
- package/src/server/responses/combo-stream-preflight.ts +168 -6
- package/src/server/responses/compact.ts +43 -10
- package/src/server/responses/compaction-routing.ts +111 -0
- package/src/server/responses/core-codex-account.ts +8 -3
- package/src/server/responses/core-combo.ts +7 -7
- package/src/server/responses/core-normalize.ts +6 -12
- package/src/server/responses/core-opaque-recovery.ts +91 -0
- package/src/server/responses/core-options.ts +4 -0
- package/src/server/responses/encrypted-payload.ts +20 -2
- package/src/server/responses/fetch-helpers.ts +124 -8
- package/src/server/responses/input-admission.ts +10 -0
- package/src/server/responses/passthrough-delivery.ts +45 -15
- package/src/server/responses/passthrough-dispatch.ts +215 -44
- package/src/server/responses/passthrough-error.ts +27 -8
- package/src/server/responses/policy-fallback.ts +5 -13
- package/src/server/responses/request-prepare.ts +109 -18
- package/src/server/responses/request-send-budget.ts +17 -1
- package/src/server/responses/request-sidecar-auth.ts +1 -1
- package/src/server/responses/request-transport.ts +26 -6
- package/src/server/responses/reset-replay.ts +108 -0
- package/src/server/responses/run-turn-execution.ts +25 -3
- package/src/server/responses/sidecar-execution.ts +17 -2
- package/src/server/responses/ws-upstream.ts +14 -27
- package/src/server/responses-custom-tool-repair.ts +27 -54
- package/src/server/responses-request-tool-scope.ts +214 -0
- package/src/server/responses-undeclared-tool-guard.ts +35 -2
- package/src/server/search.ts +25 -1
- package/src/server/sse-payload-rewrite.ts +1 -1
- package/src/server/usage-ledger-retention.ts +73 -0
- package/src/service/cli.ts +48 -2
- package/src/service/health.ts +3 -2
- package/src/service/install-state-contract.d.mts +27 -0
- package/src/service/install-state-contract.mjs +34 -0
- package/src/service/launchd.ts +1 -1
- package/src/service/orchestration.ts +2 -4
- package/src/service/ownership-compatibility.ts +164 -0
- package/src/service/ownership-mutation-lease.d.mts +32 -0
- package/src/service/ownership-mutation-lease.mjs +211 -0
- package/src/service/repair.ts +45 -1
- package/src/service/state-lock.ts +269 -0
- package/src/service/state-record.d.mts +36 -0
- package/src/service/state-record.mjs +138 -0
- package/src/service/state.ts +582 -68
- package/src/service/windows-taskxml.ts +11 -10
- package/src/service.ts +7 -3
- package/src/tray/windows-tray.ps1 +156 -4
- package/src/types/config.ts +48 -3
- package/src/types/provider.ts +91 -0
- package/src/types/request.ts +30 -2
- package/src/types/tools.ts +33 -0
- package/src/types.ts +4 -0
- package/src/update/index.ts +207 -63
- package/src/update/job.ts +9 -5
- package/src/update/ownership-transaction.ts +47 -0
- package/src/update/restart-ownership.ts +54 -0
- package/src/update/runtime-ownership.d.mts +40 -0
- package/src/update/runtime-ownership.mjs +122 -0
- package/src/usage/attempt-delivery.ts +198 -0
- package/src/usage/cache-diagnostic.ts +305 -0
- package/src/usage/failure-fingerprint.ts +118 -0
- package/src/usage/failure-projection-cache.ts +174 -0
- package/src/usage/failure-projection.ts +174 -0
- package/src/usage/ledger-retention.ts +165 -0
- package/src/usage/log.ts +126 -79
- package/src/usage/request-outcome.ts +150 -0
- package/src/usage/retention-contract.ts +28 -0
- package/src/usage/summary.ts +2 -2
- package/src/usage/telemetry-contract.ts +237 -0
- package/src/usage/timeline.ts +236 -0
- package/src/vision/eligibility.ts +88 -9
- package/src/vision/plan.ts +34 -10
- package/src/web-search/alpha-search.ts +21 -1
- package/src/web-search/executor.ts +41 -2
- package/src/web-search/loop.ts +6 -1
- package/gui/dist/assets/index-C5IebErG.js +0 -136
- package/gui/dist/assets/index-OESInAjC.css +0 -1
|
@@ -43,6 +43,11 @@ import {
|
|
|
43
43
|
raycastConfigPath,
|
|
44
44
|
zcodeConfigPath,
|
|
45
45
|
zcodeHomeDir,
|
|
46
|
+
zcodeProviderStorePath,
|
|
47
|
+
buildZcodeStoreContribution,
|
|
48
|
+
zcodeStoreSchemaEstablished,
|
|
49
|
+
type BuildContribution,
|
|
50
|
+
type ConfigFormat,
|
|
46
51
|
type ExportClientId,
|
|
47
52
|
} from "../clients/config-export";
|
|
48
53
|
|
|
@@ -58,6 +63,28 @@ export interface IntegrationClientSpec {
|
|
|
58
63
|
configPath: (env?: NodeJS.ProcessEnv, home?: string) => string;
|
|
59
64
|
/** Directory whose existence is the cheap "is it installed?" signal. */
|
|
60
65
|
detectDir: (env?: NodeJS.ProcessEnv, home?: string) => string;
|
|
66
|
+
/**
|
|
67
|
+
* The provider store this client reads INSTEAD of `configPath`.
|
|
68
|
+
*
|
|
69
|
+
* A client that moves its store between releases usually keeps a one-shot
|
|
70
|
+
* import from the old location, and that import is exactly what makes the old
|
|
71
|
+
* write look like it still works: it runs once, on an install that has never
|
|
72
|
+
* created the new file, and never again. Everything after it lands in a file
|
|
73
|
+
* the client does not open.
|
|
74
|
+
*
|
|
75
|
+
* A declaration carries everything needed to write the store, not only its
|
|
76
|
+
* location: the text format, the contribution shape its reader understands,
|
|
77
|
+
* and the predicate that says whether a document on disk is a version whose
|
|
78
|
+
* shape has been observed. The last one is what keeps this honest — a store
|
|
79
|
+
* we cannot establish is reported as the reason the write cannot reach the
|
|
80
|
+
* client, never merged into on a guess.
|
|
81
|
+
*/
|
|
82
|
+
currentStore?: {
|
|
83
|
+
path: (env?: NodeJS.ProcessEnv, home?: string) => string;
|
|
84
|
+
format: ConfigFormat;
|
|
85
|
+
establishes: (parsed: unknown) => boolean;
|
|
86
|
+
buildContribution: BuildContribution;
|
|
87
|
+
};
|
|
61
88
|
/** Patch only this block-map YAML leaf; never re-render the shared file. */
|
|
62
89
|
sourcePreservingYaml?: { path: readonly string[] };
|
|
63
90
|
/** Coordinate the complete mutation through a sibling config lock. */
|
|
@@ -231,6 +258,17 @@ export const INTEGRATION_CLIENTS: Record<IntegrationClientId, IntegrationClientS
|
|
|
231
258
|
id: "zcode",
|
|
232
259
|
configPath: (env = process.env, home = homedir()) => zcodeConfigPath(env, home),
|
|
233
260
|
detectDir: (env = process.env, home = homedir()) => zcodeHomeDir(env, home),
|
|
261
|
+
/*
|
|
262
|
+
* ZCode 3.14 reads its providers from `v2/provider_config.json` and reaches
|
|
263
|
+
* `v2/config.json` only through the import that seeded it. Where the new
|
|
264
|
+
* file exists the import is spent, so our write is read by nobody (#5348).
|
|
265
|
+
*/
|
|
266
|
+
currentStore: {
|
|
267
|
+
path: (env = process.env, home = homedir()) => zcodeProviderStorePath(env, home),
|
|
268
|
+
format: "json",
|
|
269
|
+
establishes: zcodeStoreSchemaEstablished,
|
|
270
|
+
buildContribution: buildZcodeStoreContribution,
|
|
271
|
+
},
|
|
234
272
|
},
|
|
235
273
|
prime: {
|
|
236
274
|
id: "prime",
|
|
@@ -11,10 +11,18 @@
|
|
|
11
11
|
import { createClineIO } from "./cline-io";
|
|
12
12
|
import { parseClineDocument } from "./cline-document";
|
|
13
13
|
import { ClientPathError, EXPORT_CLIENTS, opencodeProxyBaseUrl, type ExportModel, type ManagedContribution } from "../clients/config-export";
|
|
14
|
+
import type { ConfigFormat } from "../clients/config-export";
|
|
14
15
|
import type { OcxConfig } from "../types";
|
|
15
16
|
import { PARSE_FAILED, loadTarget, parseConfig, type IntegrationIO } from "./config-io";
|
|
16
17
|
import { SNAPSHOT_RETENTION } from "./journal";
|
|
17
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
AmbiguousSelectorError,
|
|
20
|
+
InvalidSelectorError,
|
|
21
|
+
parseSegment,
|
|
22
|
+
readPath,
|
|
23
|
+
selectIndex,
|
|
24
|
+
type PathSegment,
|
|
25
|
+
} from "./merge";
|
|
18
26
|
import { canonicalContribution, fingerprint, semanticContribution, type OwnershipRecord } from "./ownership";
|
|
19
27
|
import {
|
|
20
28
|
protectedContributionFingerprint,
|
|
@@ -28,6 +36,7 @@ import {
|
|
|
28
36
|
unresolvedPathHintFor,
|
|
29
37
|
type IntegrationClientId,
|
|
30
38
|
} from "./registry";
|
|
39
|
+
import { resolveIntegrationTarget, type IntegrationTarget } from "./target";
|
|
31
40
|
import { createIntegrationStateStore, type IntegrationStateStore } from "./store";
|
|
32
41
|
|
|
33
42
|
export type IntegrationState = "absent" | "current" | "stale" | "conflict" | "unsafe";
|
|
@@ -50,6 +59,21 @@ export interface IntegrationStatus {
|
|
|
50
59
|
appliedAt?: string;
|
|
51
60
|
lastOpId?: string;
|
|
52
61
|
reason?: StateReason;
|
|
62
|
+
/**
|
|
63
|
+
* The store this client reads instead of `configPath`.
|
|
64
|
+
*
|
|
65
|
+
* Present only when the integration is NOT writing that store: either our
|
|
66
|
+
* block is still in the config file, or the store is not a document whose
|
|
67
|
+
* shape has been observed. Where the store is written, `configPath` names it
|
|
68
|
+
* and there is nothing to report beside the state.
|
|
69
|
+
*
|
|
70
|
+
* Orthogonal to `state`, which answers "what is on disk, and did we put it
|
|
71
|
+
* there?" — and answers it correctly here: the block can be byte-for-byte
|
|
72
|
+
* current in a file the client stopped opening. That pair is not a
|
|
73
|
+
* contradiction, it is the whole of #5348, so the surface that reports
|
|
74
|
+
* `current` has to be able to report this beside it.
|
|
75
|
+
*/
|
|
76
|
+
supersededBy?: string;
|
|
53
77
|
/** Snapshot files retained for this client; -1 when they cannot be inspected. */
|
|
54
78
|
snapshotCount: number;
|
|
55
79
|
/** Pruning is behind, so older (possibly credential-bearing) snapshots remain. */
|
|
@@ -64,37 +88,14 @@ function assertNever(segment: never): never {
|
|
|
64
88
|
throw new Error(`unknown path segment ${JSON.stringify(segment)}`);
|
|
65
89
|
}
|
|
66
90
|
|
|
67
|
-
/** The element a selector names, or `undefined` when none matches. */
|
|
68
|
-
function selectElement(items: readonly unknown[], segment: PathSegment & { kind: "select" }): unknown {
|
|
69
|
-
return items[selectIndex(items, segment.field, segment.value)];
|
|
70
|
-
}
|
|
71
|
-
|
|
72
91
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
92
|
+
* The path reader lives with the path grammar, in `merge`, and is re-exported
|
|
93
|
+
* here because the classifier was its original home and every caller imports
|
|
94
|
+
* it from this module. One implementation is the point: a reader that resolved
|
|
95
|
+
* a selector differently from the writer would report one element as ours and
|
|
96
|
+
* then rewrite another.
|
|
77
97
|
*/
|
|
78
|
-
export
|
|
79
|
-
let cursor: unknown = doc;
|
|
80
|
-
for (const raw of path) {
|
|
81
|
-
const segment = parseSegment(raw);
|
|
82
|
-
switch (segment.kind) {
|
|
83
|
-
case "key":
|
|
84
|
-
if (!isPlainRecord(cursor)) return undefined;
|
|
85
|
-
cursor = cursor[segment.key];
|
|
86
|
-
break;
|
|
87
|
-
case "select":
|
|
88
|
-
if (!Array.isArray(cursor)) return undefined;
|
|
89
|
-
cursor = selectElement(cursor, segment);
|
|
90
|
-
break;
|
|
91
|
-
default:
|
|
92
|
-
return assertNever(segment);
|
|
93
|
-
}
|
|
94
|
-
if (cursor === undefined) return undefined;
|
|
95
|
-
}
|
|
96
|
-
return cursor;
|
|
97
|
-
}
|
|
98
|
+
export { readPath };
|
|
98
99
|
|
|
99
100
|
/** Does the document carry any fragment we would write? */
|
|
100
101
|
export function hasOurFragments(doc: unknown, contribution: ManagedContribution): boolean {
|
|
@@ -136,7 +137,7 @@ export function blockedContainerPath(
|
|
|
136
137
|
case "key":
|
|
137
138
|
return (value as Record<string, unknown>)[segment.key];
|
|
138
139
|
case "select":
|
|
139
|
-
return
|
|
140
|
+
return (value as readonly unknown[])[selectIndex(value as readonly unknown[], segment.criteria)];
|
|
140
141
|
default:
|
|
141
142
|
return assertNever(segment);
|
|
142
143
|
}
|
|
@@ -285,6 +286,14 @@ export function classifyIntegration(input: {
|
|
|
285
286
|
*/
|
|
286
287
|
configPath?: string;
|
|
287
288
|
clientId?: IntegrationClientId;
|
|
289
|
+
/**
|
|
290
|
+
* Text format of the file being classified, which is not always the client's
|
|
291
|
+
* config format: a client that moved its providers keeps a second document
|
|
292
|
+
* whose format is declared with the store. Only the comment-capability of the
|
|
293
|
+
* format is read here, and reading the wrong one would decide a sibling edit
|
|
294
|
+
* the wrong way.
|
|
295
|
+
*/
|
|
296
|
+
format?: ConfigFormat;
|
|
288
297
|
}): { state: IntegrationState; reason?: StateReason } {
|
|
289
298
|
if (input.fileText !== null && !input.fileIsRegular) {
|
|
290
299
|
return { state: "unsafe", reason: "not-regular-file" };
|
|
@@ -310,8 +319,13 @@ export function classifyIntegration(input: {
|
|
|
310
319
|
}
|
|
311
320
|
}
|
|
312
321
|
} catch (error) {
|
|
313
|
-
if (
|
|
314
|
-
|
|
322
|
+
if (error instanceof AmbiguousSelectorError) {
|
|
323
|
+
return { state: "unsafe", reason: "ambiguous-selector" };
|
|
324
|
+
}
|
|
325
|
+
if (error instanceof InvalidSelectorError) {
|
|
326
|
+
return { state: "unsafe", reason: "unparseable" };
|
|
327
|
+
}
|
|
328
|
+
throw error;
|
|
315
329
|
}
|
|
316
330
|
if (!hasOurFragments(input.parsed, input.contribution)) return { state: "absent" };
|
|
317
331
|
|
|
@@ -393,7 +407,7 @@ export function classifyIntegration(input: {
|
|
|
393
407
|
* stands and re-owns the file. This also lets disable proceed on a
|
|
394
408
|
* drifted file — removal still touches only the recorded fragment paths.
|
|
395
409
|
*/
|
|
396
|
-
if (EXPORT_CLIENTS[clientId].format !== "json") {
|
|
410
|
+
if ((input.format ?? EXPORT_CLIENTS[clientId].format) !== "json") {
|
|
397
411
|
return { state: "conflict", reason: "foreign-edit" };
|
|
398
412
|
}
|
|
399
413
|
return { state: "stale" };
|
|
@@ -491,7 +505,6 @@ export function readIntegrationState(input: IntegrationStateInput): IntegrationS
|
|
|
491
505
|
retryPendingPrunesOnce(store);
|
|
492
506
|
let io = input.io ?? store.io();
|
|
493
507
|
const spec = INTEGRATION_CLIENTS[input.clientId];
|
|
494
|
-
const exportSpec = EXPORT_CLIENTS[input.clientId];
|
|
495
508
|
const retention = retentionOf(input.clientId, store);
|
|
496
509
|
/*
|
|
497
510
|
* Resolution can refuse — a relative OPENCLAW_* selector names a file whose
|
|
@@ -499,14 +512,25 @@ export function readIntegrationState(input: IntegrationStateInput): IntegrationS
|
|
|
499
512
|
* every client for its state, so letting that escape would answer 500 for
|
|
500
513
|
* the whole Integrations page because one client is misconfigured.
|
|
501
514
|
*/
|
|
502
|
-
let configPath: string;
|
|
503
515
|
let installed: boolean;
|
|
516
|
+
let effective: IntegrationTarget;
|
|
517
|
+
let record: OwnershipRecord | null;
|
|
504
518
|
try {
|
|
505
519
|
// One resolution for both, so a client whose paths come from mutable state
|
|
506
520
|
// cannot report one account's install beside another account's config path.
|
|
507
521
|
const paths = input.resolvedPaths ?? resolveIntegrationPaths(input.clientId, input.env, input.home);
|
|
508
|
-
configPath = paths.configPath;
|
|
509
522
|
installed = io.statKind(paths.detectDir) === "dir";
|
|
523
|
+
if (input.clientId === "cline") io = createClineIO(io, paths.configPath, store);
|
|
524
|
+
/*
|
|
525
|
+
* The record is one of the inputs the target is chosen from, so it is read
|
|
526
|
+
* here rather than after the file. The status this function reports is about
|
|
527
|
+
* whichever file the next mutation would act on; reading a different one
|
|
528
|
+
* would let the badge and the switch disagree.
|
|
529
|
+
*/
|
|
530
|
+
record = store.readRecords()[input.clientId] ?? null;
|
|
531
|
+
effective = resolveIntegrationTarget({
|
|
532
|
+
clientId: input.clientId, configPath: paths.configPath, io, record, env: input.env, home: input.home,
|
|
533
|
+
});
|
|
510
534
|
} catch (error) {
|
|
511
535
|
if (!(error instanceof ClientPathError)) throw error;
|
|
512
536
|
/*
|
|
@@ -531,30 +555,32 @@ export function readIntegrationState(input: IntegrationStateInput): IntegrationS
|
|
|
531
555
|
};
|
|
532
556
|
}
|
|
533
557
|
|
|
534
|
-
|
|
535
|
-
const
|
|
536
|
-
if (!
|
|
558
|
+
const configPath = effective.configPath;
|
|
559
|
+
const loaded = loadTarget(io, configPath);
|
|
560
|
+
if (!loaded.ok) {
|
|
537
561
|
return {
|
|
538
562
|
clientId: input.clientId,
|
|
539
563
|
state: "unsafe",
|
|
540
564
|
installed,
|
|
541
565
|
configPath,
|
|
542
|
-
reason:
|
|
566
|
+
reason: loaded.why === "read-failed" ? "unparseable" : "not-regular-file",
|
|
543
567
|
...retention,
|
|
544
568
|
};
|
|
545
569
|
}
|
|
546
570
|
|
|
547
|
-
const parsed = input.clientId === "cline"
|
|
548
|
-
|
|
549
|
-
|
|
571
|
+
const parsed = input.clientId === "cline"
|
|
572
|
+
? parseClineDocument(loaded.before)
|
|
573
|
+
: parseConfig(loaded.before, effective.format);
|
|
574
|
+
const contribution = effective.buildContribution(exportContextOf(input));
|
|
550
575
|
const { state, reason } = classifyIntegration({
|
|
551
|
-
fileText:
|
|
576
|
+
fileText: loaded.before,
|
|
552
577
|
fileIsRegular: true,
|
|
553
578
|
parsed,
|
|
554
579
|
record,
|
|
555
580
|
contribution,
|
|
556
581
|
configPath,
|
|
557
582
|
clientId: input.clientId,
|
|
583
|
+
format: effective.format,
|
|
558
584
|
});
|
|
559
585
|
|
|
560
586
|
return {
|
|
@@ -563,6 +589,13 @@ export function readIntegrationState(input: IntegrationStateInput): IntegrationS
|
|
|
563
589
|
installed,
|
|
564
590
|
configPath,
|
|
565
591
|
...(reason ? { reason } : {}),
|
|
592
|
+
/*
|
|
593
|
+
* Only when the client reads a DIFFERENT file than the one this status is
|
|
594
|
+
* about. A store we are writing needs no notice; the path already names it.
|
|
595
|
+
*/
|
|
596
|
+
...(effective.ineffective && effective.ineffective.store !== configPath
|
|
597
|
+
? { supersededBy: effective.ineffective.store }
|
|
598
|
+
: {}),
|
|
566
599
|
...(record ? { appliedAt: record.appliedAt, lastOpId: record.opId } : {}),
|
|
567
600
|
...retention,
|
|
568
601
|
};
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which file an integration operation reads, writes and records — and, when
|
|
3
|
+
* that is not the file the client reads, which file is.
|
|
4
|
+
*
|
|
5
|
+
* A client may move its provider list between releases and keep the old file
|
|
6
|
+
* reachable only through a one-shot import. Everything the integration checks
|
|
7
|
+
* still passes against the old file: it is writable, the block merges, the
|
|
8
|
+
* ownership record describes it correctly. The only thing that changed is
|
|
9
|
+
* whether anything reads it (#5348).
|
|
10
|
+
*
|
|
11
|
+
* So the target is chosen before anything is classified, and it is chosen by
|
|
12
|
+
* three facts in this order:
|
|
13
|
+
*
|
|
14
|
+
* 1. No declared store, or no store on disk — the config file, exactly as
|
|
15
|
+
* before. A client that has never run still imports what we write there.
|
|
16
|
+
* 2. Our own block already sits in the config file this record names. Stay
|
|
17
|
+
* there. Disable removes what we wrote from where we wrote it, and the
|
|
18
|
+
* caller reports the write as ineffective rather than orphaning a block in
|
|
19
|
+
* one file while writing another.
|
|
20
|
+
* 3. Otherwise the store, but only when its schema can be established. A store
|
|
21
|
+
* we cannot parse or whose version we have not observed is reported as the
|
|
22
|
+
* reason the write cannot reach the client, because merging an invented
|
|
23
|
+
* nesting into a file holding the user's other providers would trade a
|
|
24
|
+
* silent no-op for a silent loss.
|
|
25
|
+
*/
|
|
26
|
+
import { EXPORT_CLIENTS, ClientPathError, type BuildContribution, type ConfigFormat } from "../clients/config-export";
|
|
27
|
+
import { PARSE_FAILED, loadTarget, parseConfig, type IntegrationIO } from "./config-io";
|
|
28
|
+
import { AmbiguousSelectorError, InvalidSelectorError, readPath } from "./merge";
|
|
29
|
+
import type { OwnershipRecord } from "./ownership";
|
|
30
|
+
import { INTEGRATION_CLIENTS, type IntegrationClientId } from "./registry";
|
|
31
|
+
|
|
32
|
+
export interface IntegrationTarget {
|
|
33
|
+
/** The file this operation reads, writes, journals and records ownership for. */
|
|
34
|
+
readonly configPath: string;
|
|
35
|
+
/** Text format of that file, which is not always the client's config format. */
|
|
36
|
+
readonly format: ConfigFormat;
|
|
37
|
+
/** The contribution shape that file's reader understands. */
|
|
38
|
+
readonly buildContribution: BuildContribution;
|
|
39
|
+
/**
|
|
40
|
+
* Set when a write to the file above would not reach the client.
|
|
41
|
+
*
|
|
42
|
+
* `why` is carried rather than re-derived because the cases have different
|
|
43
|
+
* remedies and only this function knows which one it took. Disable is not
|
|
44
|
+
* gated on it: removing bytes this project wrote from the file it wrote them
|
|
45
|
+
* to is as effective as it ever was.
|
|
46
|
+
*/
|
|
47
|
+
readonly ineffective: IneffectiveWrite | null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface IneffectiveWrite {
|
|
51
|
+
/** The provider store the client reads. */
|
|
52
|
+
readonly store: string;
|
|
53
|
+
/**
|
|
54
|
+
* `owned-config-file` — this project's block is still in the config file, so
|
|
55
|
+
* the operation stays there and the remedy is to remove it first, rather than
|
|
56
|
+
* leave a block in one file while writing another.
|
|
57
|
+
*
|
|
58
|
+
* `unestablished-schema` — the store is not a document whose shape has been
|
|
59
|
+
* observed, so there is nothing safe to merge into it. This one is reachable
|
|
60
|
+
* with the store itself as the target: a client that bumps its schema after
|
|
61
|
+
* we wrote the store leaves our block there, removable, and the file no
|
|
62
|
+
* longer one we may merge into.
|
|
63
|
+
*/
|
|
64
|
+
readonly why: "owned-config-file" | "unestablished-schema";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function configFileTarget(
|
|
68
|
+
clientId: IntegrationClientId,
|
|
69
|
+
configPath: string,
|
|
70
|
+
ineffective: IneffectiveWrite | null,
|
|
71
|
+
): IntegrationTarget {
|
|
72
|
+
const exportSpec = EXPORT_CLIENTS[clientId];
|
|
73
|
+
return {
|
|
74
|
+
configPath,
|
|
75
|
+
format: exportSpec.format,
|
|
76
|
+
buildContribution: exportSpec.buildContribution,
|
|
77
|
+
ineffective,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function storeTarget(
|
|
82
|
+
declared: NonNullable<(typeof INTEGRATION_CLIENTS)[IntegrationClientId]["currentStore"]>,
|
|
83
|
+
configPath: string,
|
|
84
|
+
ineffective: IneffectiveWrite | null = null,
|
|
85
|
+
): IntegrationTarget {
|
|
86
|
+
return {
|
|
87
|
+
configPath,
|
|
88
|
+
format: declared.format,
|
|
89
|
+
buildContribution: declared.buildContribution,
|
|
90
|
+
ineffective,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Does the file this record names still hold any fragment it claims?
|
|
96
|
+
*
|
|
97
|
+
* Uncertainty answers yes. An unreadable or unparseable config file is a state
|
|
98
|
+
* the classifier is about to refuse on, and it must refuse on the file our
|
|
99
|
+
* record is about rather than silently move the operation to a different one.
|
|
100
|
+
* An ambiguous or invalid selector is the same kind of answer.
|
|
101
|
+
*/
|
|
102
|
+
function recordedBlockStillPresent(
|
|
103
|
+
io: IntegrationIO,
|
|
104
|
+
format: ConfigFormat,
|
|
105
|
+
configPath: string,
|
|
106
|
+
record: OwnershipRecord,
|
|
107
|
+
): boolean {
|
|
108
|
+
const loaded = loadTarget(io, configPath);
|
|
109
|
+
if (!loaded.ok) return true;
|
|
110
|
+
const parsed = parseConfig(loaded.before, format);
|
|
111
|
+
if (parsed === PARSE_FAILED) return true;
|
|
112
|
+
try {
|
|
113
|
+
return record.fragmentPaths.some(path => readPath(parsed, path) !== undefined);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (error instanceof AmbiguousSelectorError || error instanceof InvalidSelectorError) return true;
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Resolve the target for one operation.
|
|
122
|
+
*
|
|
123
|
+
* `configPath` is the client's config file as the registry resolves it now, and
|
|
124
|
+
* `record` is the stored ownership as read, before any path filtering — the
|
|
125
|
+
* filtering depends on the answer this function returns.
|
|
126
|
+
*
|
|
127
|
+
* The store is observed through the caller's own `IntegrationIO`, never the
|
|
128
|
+
* real filesystem directly, so status and mutation cannot disagree about
|
|
129
|
+
* whether a write can land.
|
|
130
|
+
*/
|
|
131
|
+
export function resolveIntegrationTarget(args: {
|
|
132
|
+
clientId: IntegrationClientId;
|
|
133
|
+
configPath: string;
|
|
134
|
+
io: IntegrationIO;
|
|
135
|
+
record: OwnershipRecord | null;
|
|
136
|
+
env?: NodeJS.ProcessEnv;
|
|
137
|
+
home?: string;
|
|
138
|
+
}): IntegrationTarget {
|
|
139
|
+
const { clientId, configPath, io, record } = args;
|
|
140
|
+
const declared = INTEGRATION_CLIENTS[clientId].currentStore;
|
|
141
|
+
if (!declared) return configFileTarget(clientId, configPath, null);
|
|
142
|
+
const storePath = declared.path(args.env, args.home);
|
|
143
|
+
const kind = io.statKind(storePath);
|
|
144
|
+
if (kind === "missing") return configFileTarget(clientId, configPath, null);
|
|
145
|
+
// Only proven absence permits a legacy write. Unreadable or non-file stores
|
|
146
|
+
// cannot establish what the client reads; preserve the recorded removal target.
|
|
147
|
+
if (kind !== "file") {
|
|
148
|
+
const ineffective: IneffectiveWrite = { store: storePath, why: "unestablished-schema" };
|
|
149
|
+
return record?.clientId === clientId && record.configPath === storePath
|
|
150
|
+
? storeTarget(declared, storePath, ineffective)
|
|
151
|
+
: configFileTarget(clientId, configPath, ineffective);
|
|
152
|
+
}
|
|
153
|
+
const loaded = loadTarget(io, storePath);
|
|
154
|
+
const parsed = loaded.ok ? parseConfig(loaded.before, declared.format) : PARSE_FAILED;
|
|
155
|
+
const established = parsed !== PARSE_FAILED && declared.establishes(parsed);
|
|
156
|
+
const unestablished: IneffectiveWrite = { store: storePath, why: "unestablished-schema" };
|
|
157
|
+
/*
|
|
158
|
+
* Our own block decides the target before the store's schema does, whichever
|
|
159
|
+
* file holds it. Disable has to remove what we wrote from where we wrote it,
|
|
160
|
+
* and an apply must never leave a block in one file while writing another.
|
|
161
|
+
*/
|
|
162
|
+
const owned = record !== null && record.clientId === clientId ? record : null;
|
|
163
|
+
if (owned?.configPath === storePath && recordedBlockStillPresent(io, declared.format, storePath, owned)) {
|
|
164
|
+
return storeTarget(declared, storePath, established ? null : unestablished);
|
|
165
|
+
}
|
|
166
|
+
if (
|
|
167
|
+
owned?.configPath === configPath
|
|
168
|
+
&& recordedBlockStillPresent(io, EXPORT_CLIENTS[clientId].format, configPath, owned)
|
|
169
|
+
) {
|
|
170
|
+
return configFileTarget(clientId, configPath, { store: storePath, why: "owned-config-file" });
|
|
171
|
+
}
|
|
172
|
+
if (!established) {
|
|
173
|
+
return configFileTarget(clientId, configPath, unestablished);
|
|
174
|
+
}
|
|
175
|
+
return storeTarget(declared, storePath);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The target a path NAMES, or null when this client may not write there.
|
|
180
|
+
*
|
|
181
|
+
* Undo acts on the file its journal row recorded, so it needs the meaning of a
|
|
182
|
+
* historical path rather than the choice above: which contribution shape
|
|
183
|
+
* describes those bytes, and whether this client still resolves that location
|
|
184
|
+
* at all. Resolving a path the client no longer names is what would let a row
|
|
185
|
+
* recorded for one home delete a file in another, so an unrecognised path is
|
|
186
|
+
* null and the caller refuses.
|
|
187
|
+
*/
|
|
188
|
+
export function declaredIntegrationTarget(args: {
|
|
189
|
+
clientId: IntegrationClientId;
|
|
190
|
+
configPath: string;
|
|
191
|
+
resolvedConfigPath: string;
|
|
192
|
+
env?: NodeJS.ProcessEnv;
|
|
193
|
+
home?: string;
|
|
194
|
+
}): IntegrationTarget | null {
|
|
195
|
+
const { clientId, configPath, resolvedConfigPath } = args;
|
|
196
|
+
if (configPath === resolvedConfigPath) return configFileTarget(clientId, configPath, null);
|
|
197
|
+
const declared = INTEGRATION_CLIENTS[clientId].currentStore;
|
|
198
|
+
if (!declared) return null;
|
|
199
|
+
try {
|
|
200
|
+
if (declared.path(args.env, args.home) !== configPath) return null;
|
|
201
|
+
} catch (error) {
|
|
202
|
+
// A store the operator relocated with a path we cannot resolve proves
|
|
203
|
+
// nothing about the row, so the row is not a legal target.
|
|
204
|
+
if (error instanceof ClientPathError) return null;
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
return storeTarget(declared, configPath);
|
|
208
|
+
}
|