@lesliechan721/aw 0.1.0-beta.6 → 0.1.0-beta.61
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/dist/authoring/assets/aw-config.mjs +100 -501
- package/dist/authoring/assets/aw-config.mjs.map +1 -1
- package/dist/authoring/assets/aw-runtime-config.d.mts +13 -0
- package/dist/authoring/assets/aw-runtime-config.mjs +199 -0
- package/dist/authoring/assets/aw-runtime-config.mjs.map +1 -0
- package/dist/authoring/scripts/add-skill.mjs +4 -4
- package/dist/authoring/scripts/add-skill.mjs.map +1 -1
- package/dist/authoring/scripts/init-plugin.mjs +67 -97
- package/dist/authoring/scripts/init-plugin.mjs.map +1 -1
- package/dist/authoring/scripts/lib/plugin-root.mjs +4 -4
- package/dist/authoring/scripts/lib/plugin-root.mjs.map +1 -1
- package/dist/authoring/scripts/lib/plugin-version-check.d.mts +1 -0
- package/dist/authoring/scripts/lib/plugin-version-check.mjs +229 -8
- package/dist/authoring/scripts/lib/plugin-version-check.mjs.map +1 -1
- package/dist/authoring/scripts/lib/skill-scaffold.d.mts +77 -92
- package/dist/authoring/scripts/lib/skill-scaffold.mjs +99 -124
- package/dist/authoring/scripts/lib/skill-scaffold.mjs.map +1 -1
- package/dist/authoring/scripts/plugin-status.mjs +8 -11
- package/dist/authoring/scripts/plugin-status.mjs.map +1 -1
- package/dist/authoring/scripts/remove-plugin.mjs +3 -2
- package/dist/authoring/scripts/remove-plugin.mjs.map +1 -1
- package/dist/authoring/scripts/validate-plugin.mjs +552 -1142
- package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
- package/dist/native-runtime/reader-runtime.mjs +34 -0
- package/dist/native-runtime/reader.d.mts +6 -0
- package/dist/native-runtime/reader.mjs +11 -0
- package/dist/scripts/aw-skill-eval-materializer.d.ts +26 -0
- package/dist/scripts/aw-skill-eval-materializer.js +306 -0
- package/dist/scripts/aw-skill-eval-materializer.js.map +1 -0
- package/dist/scripts/bundle-plugins.d.ts +1 -0
- package/dist/scripts/bundle-plugins.js +19 -8
- package/dist/scripts/bundle-plugins.js.map +1 -1
- package/dist/scripts/plugin-staging.d.ts +7 -9
- package/dist/scripts/plugin-staging.js +18 -29
- package/dist/scripts/plugin-staging.js.map +1 -1
- package/dist/scripts/plugin-test-reporter.d.mts +2 -0
- package/dist/scripts/plugin-test-reporter.mjs +21 -0
- package/dist/scripts/plugin-test-reporter.mjs.map +1 -0
- package/dist/scripts/test-plugin-scripts.d.ts +5 -0
- package/dist/scripts/test-plugin-scripts.js +208 -89
- package/dist/scripts/test-plugin-scripts.js.map +1 -1
- package/dist/scripts/test-workers.d.mts +24 -0
- package/dist/scripts/test-workers.mjs +59 -0
- package/dist/scripts/test-workers.mjs.map +1 -0
- package/dist/src/adapters/helpers.d.ts +7 -0
- package/dist/src/adapters/helpers.js +2 -2
- package/dist/src/adapters/helpers.js.map +1 -1
- package/dist/src/authoring/workspace-commands.d.ts +19 -1
- package/dist/src/authoring/workspace-commands.js +24 -5
- package/dist/src/authoring/workspace-commands.js.map +1 -1
- package/dist/src/commands/aw-capabilities.d.ts +78 -0
- package/dist/src/commands/aw-capabilities.js +266 -0
- package/dist/src/commands/aw-capabilities.js.map +1 -0
- package/dist/src/commands/catalog-dev-source.d.ts +154 -0
- package/dist/src/commands/catalog-dev-source.js +655 -0
- package/dist/src/commands/catalog-dev-source.js.map +1 -0
- package/dist/src/commands/catalog-generation.d.ts +21 -0
- package/dist/src/commands/catalog-generation.js +9 -0
- package/dist/src/commands/catalog-generation.js.map +1 -0
- package/dist/src/commands/catalog-publication-inspection.d.ts +71 -30
- package/dist/src/commands/catalog-publication-inspection.js +475 -63
- package/dist/src/commands/catalog-publication-inspection.js.map +1 -1
- package/dist/src/commands/catalog-publication-journal.d.ts +68 -0
- package/dist/src/commands/catalog-publication-journal.js +292 -0
- package/dist/src/commands/catalog-publication-journal.js.map +1 -0
- package/dist/src/commands/catalog-publication-target.d.ts +18 -0
- package/dist/src/commands/catalog-publication-target.js +95 -0
- package/dist/src/commands/catalog-publication-target.js.map +1 -0
- package/dist/src/commands/catalog-publish.d.ts +73 -15
- package/dist/src/commands/catalog-publish.js +701 -212
- package/dist/src/commands/catalog-publish.js.map +1 -1
- package/dist/src/commands/catalog-source.d.ts +60 -22
- package/dist/src/commands/catalog-source.js +311 -91
- package/dist/src/commands/catalog-source.js.map +1 -1
- package/dist/src/commands/catalog-status.d.ts +18 -22
- package/dist/src/commands/catalog-status.js +81 -115
- package/dist/src/commands/catalog-status.js.map +1 -1
- package/dist/src/commands/catalog-store.d.ts +104 -0
- package/dist/src/commands/catalog-store.js +685 -0
- package/dist/src/commands/catalog-store.js.map +1 -0
- package/dist/src/commands/catalog-workspace-resolution.d.ts +13 -0
- package/dist/src/commands/catalog-workspace-resolution.js +42 -0
- package/dist/src/commands/catalog-workspace-resolution.js.map +1 -0
- package/dist/src/commands/console.js +28 -17
- package/dist/src/commands/console.js.map +1 -1
- package/dist/src/commands/dependency-operation-plan.d.ts +52 -15
- package/dist/src/commands/dependency-operation-plan.js +289 -7
- package/dist/src/commands/dependency-operation-plan.js.map +1 -1
- package/dist/src/commands/init.d.ts +5 -11
- package/dist/src/commands/init.js +30 -51
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/managed-commands.d.ts +28 -0
- package/dist/src/commands/managed-commands.js +315 -0
- package/dist/src/commands/managed-commands.js.map +1 -0
- package/dist/src/commands/offline-cleanup-evidence.d.ts +67 -0
- package/dist/src/commands/offline-cleanup-evidence.js +181 -0
- package/dist/src/commands/offline-cleanup-evidence.js.map +1 -0
- package/dist/src/commands/operation-plan.d.ts +182 -0
- package/dist/src/commands/operation-plan.js +71 -0
- package/dist/src/commands/operation-plan.js.map +1 -0
- package/dist/src/commands/operation-planner.d.ts +47 -0
- package/dist/src/commands/operation-planner.js +142 -0
- package/dist/src/commands/operation-planner.js.map +1 -0
- package/dist/src/commands/options.d.ts +9 -7
- package/dist/src/commands/options.js +83 -21
- package/dist/src/commands/options.js.map +1 -1
- package/dist/src/commands/plugin-capabilities.js +65 -4
- package/dist/src/commands/plugin-capabilities.js.map +1 -1
- package/dist/src/commands/rebuild-command.d.ts +78 -31
- package/dist/src/commands/rebuild-command.js +5229 -942
- package/dist/src/commands/rebuild-command.js.map +1 -1
- package/dist/src/commands/rebuild-integration.d.ts +143 -21
- package/dist/src/commands/rebuild-integration.js +3384 -1342
- package/dist/src/commands/rebuild-integration.js.map +1 -1
- package/dist/src/commands/rebuild-operations.d.ts +60 -30
- package/dist/src/commands/rebuild-operations.js +267 -320
- package/dist/src/commands/rebuild-operations.js.map +1 -1
- package/dist/src/commands/reconcile-operation-plan.d.ts +28 -0
- package/dist/src/commands/reconcile-operation-plan.js +47 -0
- package/dist/src/commands/reconcile-operation-plan.js.map +1 -0
- package/dist/src/commands/review-inspection.d.ts +60 -0
- package/dist/src/commands/review-inspection.js +422 -0
- package/dist/src/commands/review-inspection.js.map +1 -0
- package/dist/src/commands/review-preparation.d.ts +49 -0
- package/dist/src/commands/review-preparation.js +427 -0
- package/dist/src/commands/review-preparation.js.map +1 -0
- package/dist/src/commands/runtime.d.ts +1 -5
- package/dist/src/commands/runtime.js +27 -53
- package/dist/src/commands/runtime.js.map +1 -1
- package/dist/src/commands/skill-eval.d.ts +39 -0
- package/dist/src/commands/skill-eval.js +400 -0
- package/dist/src/commands/skill-eval.js.map +1 -0
- package/dist/src/commands/status-v6.d.ts +279 -0
- package/dist/src/commands/status-v6.js +2785 -0
- package/dist/src/commands/status-v6.js.map +1 -0
- package/dist/src/commands/sync-command.d.ts +40 -0
- package/dist/src/commands/sync-command.js +254 -0
- package/dist/src/commands/sync-command.js.map +1 -0
- package/dist/src/commands/wizard.d.ts +8 -5
- package/dist/src/commands/wizard.js +6 -4
- package/dist/src/commands/wizard.js.map +1 -1
- package/dist/src/commands/write-commands.d.ts +9 -3
- package/dist/src/commands/write-commands.js +136 -31
- package/dist/src/commands/write-commands.js.map +1 -1
- package/dist/src/constants.d.ts +7 -2
- package/dist/src/constants.js +7 -2
- package/dist/src/constants.js.map +1 -1
- package/dist/src/eval/manifest-v1.d.ts +68 -0
- package/dist/src/eval/manifest-v1.js +353 -0
- package/dist/src/eval/manifest-v1.js.map +1 -0
- package/dist/src/generators/local-plugin.d.ts +5 -1
- package/dist/src/generators/local-plugin.js +263 -109
- package/dist/src/generators/local-plugin.js.map +1 -1
- package/dist/src/generators/permissions.d.ts +5 -2
- package/dist/src/generators/permissions.js +28 -15
- package/dist/src/generators/permissions.js.map +1 -1
- package/dist/src/index.d.ts +10 -4
- package/dist/src/index.js +1774 -609
- package/dist/src/index.js.map +1 -1
- package/dist/src/output/command-receipt.d.ts +47 -46
- package/dist/src/output/command-receipt.js +277 -79
- package/dist/src/output/command-receipt.js.map +1 -1
- package/dist/src/output/model.d.ts +3 -44
- package/dist/src/output/model.js +1 -144
- package/dist/src/output/model.js.map +1 -1
- package/dist/src/output/rebuild-progress.js +15 -13
- package/dist/src/output/rebuild-progress.js.map +1 -1
- package/dist/src/portable/commands.d.ts +46 -7
- package/dist/src/portable/commands.js +349 -37
- package/dist/src/portable/commands.js.map +1 -1
- package/dist/src/portable/materializer.d.ts +17 -4
- package/dist/src/portable/materializer.js +186 -41
- package/dist/src/portable/materializer.js.map +1 -1
- package/dist/src/portable/skills-cli.d.ts +15 -7
- package/dist/src/portable/skills-cli.js +74 -20
- package/dist/src/portable/skills-cli.js.map +1 -1
- package/dist/src/review/eval-source-input.d.ts +43 -0
- package/dist/src/review/eval-source-input.js +207 -0
- package/dist/src/review/eval-source-input.js.map +1 -0
- package/dist/src/review/review-profile.d.ts +17 -0
- package/dist/src/review/review-profile.js +234 -0
- package/dist/src/review/review-profile.js.map +1 -0
- package/dist/src/review/review-selection.d.ts +30 -0
- package/dist/src/review/review-selection.js +527 -0
- package/dist/src/review/review-selection.js.map +1 -0
- package/dist/src/review/review-types.d.ts +68 -0
- package/dist/src/review/review-types.js +16 -0
- package/dist/src/review/review-types.js.map +1 -0
- package/dist/src/review/skill-materialization.d.ts +26 -0
- package/dist/src/review/skill-materialization.js +217 -0
- package/dist/src/review/skill-materialization.js.map +1 -0
- package/dist/src/review/source-binding.d.ts +183 -0
- package/dist/src/review/source-binding.js +708 -0
- package/dist/src/review/source-binding.js.map +1 -0
- package/dist/src/tui/catalog-freshness.d.ts +1 -0
- package/dist/src/tui/catalog-freshness.js +2 -0
- package/dist/src/tui/catalog-freshness.js.map +1 -0
- package/dist/src/tui/catalog-generation.d.ts +21 -0
- package/dist/src/tui/catalog-generation.js +156 -0
- package/dist/src/tui/catalog-generation.js.map +1 -0
- package/dist/src/tui/catalog-sources/projection.d.ts +4 -0
- package/dist/src/tui/catalog-sources/projection.js +17 -0
- package/dist/src/tui/catalog-sources/projection.js.map +1 -0
- package/dist/src/tui/catalog-sources/review.d.ts +3 -0
- package/dist/src/tui/catalog-sources/review.js +13 -0
- package/dist/src/tui/catalog-sources/review.js.map +1 -0
- package/dist/src/tui/catalog-sources/screens.d.ts +12 -0
- package/dist/src/tui/catalog-sources/screens.js +40 -0
- package/dist/src/tui/catalog-sources/screens.js.map +1 -0
- package/dist/src/tui/catalog-sources/session.d.ts +32 -0
- package/dist/src/tui/catalog-sources/session.js +300 -0
- package/dist/src/tui/catalog-sources/session.js.map +1 -0
- package/dist/src/tui/catalog-sources/types.d.ts +54 -0
- package/dist/src/tui/catalog-sources/types.js +2 -0
- package/dist/src/tui/catalog-sources/types.js.map +1 -0
- package/dist/src/tui/catalog-store-task.d.ts +16 -0
- package/dist/src/tui/catalog-store-task.js +19 -0
- package/dist/src/tui/catalog-store-task.js.map +1 -0
- package/dist/src/tui/clipboard.d.ts +13 -0
- package/dist/src/tui/clipboard.js +95 -0
- package/dist/src/tui/clipboard.js.map +1 -0
- package/dist/src/tui/components.d.ts +110 -3
- package/dist/src/tui/components.js +183 -36
- package/dist/src/tui/components.js.map +1 -1
- package/dist/src/tui/config-array-draft.d.ts +29 -0
- package/dist/src/tui/config-array-draft.js +48 -0
- package/dist/src/tui/config-array-draft.js.map +1 -0
- package/dist/src/tui/config-leaf-editor.d.ts +19 -0
- package/dist/src/tui/config-leaf-editor.js +212 -0
- package/dist/src/tui/config-leaf-editor.js.map +1 -0
- package/dist/src/tui/config-references.d.ts +34 -0
- package/dist/src/tui/config-references.js +70 -0
- package/dist/src/tui/config-references.js.map +1 -0
- package/dist/src/tui/config-value-presentation.d.ts +20 -0
- package/dist/src/tui/config-value-presentation.js +104 -0
- package/dist/src/tui/config-value-presentation.js.map +1 -0
- package/dist/src/tui/console-app-v4.d.ts +14 -4
- package/dist/src/tui/console-app-v4.js +9205 -1951
- package/dist/src/tui/console-app-v4.js.map +1 -1
- package/dist/src/tui/content-item-card.d.ts +13 -0
- package/dist/src/tui/content-item-card.js +51 -0
- package/dist/src/tui/content-item-card.js.map +1 -0
- package/dist/src/tui/controller-runtime.d.ts +10 -2
- package/dist/src/tui/controller-runtime.js +3289 -388
- package/dist/src/tui/controller-runtime.js.map +1 -1
- package/dist/src/tui/controller.d.ts +622 -45
- package/dist/src/tui/controller.js +3589 -768
- package/dist/src/tui/controller.js.map +1 -1
- package/dist/src/tui/dependency-graph-view.d.ts +39 -0
- package/dist/src/tui/dependency-graph-view.js +121 -0
- package/dist/src/tui/dependency-graph-view.js.map +1 -0
- package/dist/src/tui/dependency-graph.d.ts +81 -0
- package/dist/src/tui/dependency-graph.js +452 -0
- package/dist/src/tui/dependency-graph.js.map +1 -0
- package/dist/src/tui/diagnostic-projection.d.ts +23 -0
- package/dist/src/tui/diagnostic-projection.js +595 -0
- package/dist/src/tui/diagnostic-projection.js.map +1 -0
- package/dist/src/tui/diagnostics.d.ts +3 -3
- package/dist/src/tui/diagnostics.js +14 -2
- package/dist/src/tui/diagnostics.js.map +1 -1
- package/dist/src/tui/entity-cards.d.ts +55 -0
- package/dist/src/tui/entity-cards.js +220 -0
- package/dist/src/tui/entity-cards.js.map +1 -0
- package/dist/src/tui/glossary.d.ts +23 -0
- package/dist/src/tui/glossary.js +42 -0
- package/dist/src/tui/glossary.js.map +1 -0
- package/dist/src/tui/index.d.ts +6 -1
- package/dist/src/tui/index.js +5 -1
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/tui/input-display.d.ts +9 -0
- package/dist/src/tui/input-display.js +58 -0
- package/dist/src/tui/input-display.js.map +1 -0
- package/dist/src/tui/keys.d.ts +44 -0
- package/dist/src/tui/keys.js +43 -15
- package/dist/src/tui/keys.js.map +1 -1
- package/dist/src/tui/lifecycle-action-view.d.ts +6 -0
- package/dist/src/tui/lifecycle-action-view.js +36 -0
- package/dist/src/tui/lifecycle-action-view.js.map +1 -0
- package/dist/src/tui/model.d.ts +224 -31
- package/dist/src/tui/model.js +1510 -397
- package/dist/src/tui/model.js.map +1 -1
- package/dist/src/tui/operation-diagnostic-screens.d.ts +8 -0
- package/dist/src/tui/operation-diagnostic-screens.js +71 -0
- package/dist/src/tui/operation-diagnostic-screens.js.map +1 -0
- package/dist/src/tui/operation-history.d.ts +81 -0
- package/dist/src/tui/operation-history.js +136 -0
- package/dist/src/tui/operation-history.js.map +1 -0
- package/dist/src/tui/operation-review-planner.d.ts +21 -0
- package/dist/src/tui/operation-review-planner.js +1392 -0
- package/dist/src/tui/operation-review-planner.js.map +1 -0
- package/dist/src/tui/plugin-content.d.ts +31 -0
- package/dist/src/tui/plugin-content.js +680 -0
- package/dist/src/tui/plugin-content.js.map +1 -0
- package/dist/src/tui/plugin-detail.d.ts +52 -0
- package/dist/src/tui/plugin-detail.js +165 -0
- package/dist/src/tui/plugin-detail.js.map +1 -0
- package/dist/src/tui/plugin-write-task.d.ts +69 -0
- package/dist/src/tui/plugin-write-task.js +286 -0
- package/dist/src/tui/plugin-write-task.js.map +1 -0
- package/dist/src/tui/project-inventory.d.ts +68 -0
- package/dist/src/tui/project-inventory.js +203 -0
- package/dist/src/tui/project-inventory.js.map +1 -0
- package/dist/src/tui/prompt.js +4 -15
- package/dist/src/tui/prompt.js.map +1 -1
- package/dist/src/tui/query-header.d.ts +30 -0
- package/dist/src/tui/query-header.js +40 -0
- package/dist/src/tui/query-header.js.map +1 -0
- package/dist/src/tui/reader.d.ts +37 -0
- package/dist/src/tui/reader.js +47 -0
- package/dist/src/tui/reader.js.map +1 -0
- package/dist/src/tui/router.d.ts +197 -10
- package/dist/src/tui/router.js +338 -50
- package/dist/src/tui/router.js.map +1 -1
- package/dist/src/tui/screen-registry.d.ts +701 -0
- package/dist/src/tui/screen-registry.js +230 -0
- package/dist/src/tui/screen-registry.js.map +1 -0
- package/dist/src/tui/session-console-app.d.ts +6 -0
- package/dist/src/tui/session-console-app.js +13 -0
- package/dist/src/tui/session-console-app.js.map +1 -0
- package/dist/src/tui/session-host.d.ts +45 -0
- package/dist/src/tui/session-host.js +209 -0
- package/dist/src/tui/session-host.js.map +1 -0
- package/dist/src/tui/source-detail-card.d.ts +29 -0
- package/dist/src/tui/source-detail-card.js +45 -0
- package/dist/src/tui/source-detail-card.js.map +1 -0
- package/dist/src/tui/source-ref-presets.d.ts +3 -0
- package/dist/src/tui/source-ref-presets.js +11 -0
- package/dist/src/tui/source-ref-presets.js.map +1 -0
- package/dist/src/tui/theme.d.ts +12 -0
- package/dist/src/tui/theme.js +12 -0
- package/dist/src/tui/theme.js.map +1 -1
- package/dist/src/tui/viewport.d.ts +1 -0
- package/dist/src/tui/viewport.js +23 -0
- package/dist/src/tui/viewport.js.map +1 -1
- package/dist/src/tui/views.js +31 -3
- package/dist/src/tui/views.js.map +1 -1
- package/dist/src/tui/wizard.js +37 -13
- package/dist/src/tui/wizard.js.map +1 -1
- package/dist/src/types.d.ts +264 -21
- package/dist/src/types.js +1 -0
- package/dist/src/types.js.map +1 -1
- package/dist/src/utils/agent-version.d.ts +7 -1
- package/dist/src/utils/agent-version.js +36 -7
- package/dist/src/utils/agent-version.js.map +1 -1
- package/dist/src/utils/artifact-plan.d.ts +8 -6
- package/dist/src/utils/artifact-plan.js +67 -13
- package/dist/src/utils/artifact-plan.js.map +1 -1
- package/dist/src/utils/artifact-renderer.d.ts +24 -10
- package/dist/src/utils/artifact-renderer.js +230 -220
- package/dist/src/utils/artifact-renderer.js.map +1 -1
- package/dist/src/utils/artifact-request.d.ts +32 -0
- package/dist/src/utils/artifact-request.js +90 -0
- package/dist/src/utils/artifact-request.js.map +1 -0
- package/dist/src/utils/artifact-sources.d.ts +4 -0
- package/dist/src/utils/artifact-sources.js +17 -0
- package/dist/src/utils/artifact-sources.js.map +1 -1
- package/dist/src/utils/aw-home-mutation-lock.d.ts +1 -0
- package/dist/src/utils/aw-home-mutation-lock.js +32 -4
- package/dist/src/utils/aw-home-mutation-lock.js.map +1 -1
- package/dist/src/utils/bundle-integrity.d.ts +0 -1
- package/dist/src/utils/bundle-integrity.js +0 -25
- package/dist/src/utils/bundle-integrity.js.map +1 -1
- package/dist/src/utils/bundle-layout.js +29 -29
- package/dist/src/utils/bundle-layout.js.map +1 -1
- package/dist/src/utils/catalog-capability-projection.d.ts +8 -0
- package/dist/src/utils/catalog-capability-projection.js +14 -0
- package/dist/src/utils/catalog-capability-projection.js.map +1 -0
- package/dist/src/utils/catalog-digests.d.ts +10 -0
- package/dist/src/utils/catalog-digests.js +57 -20
- package/dist/src/utils/catalog-digests.js.map +1 -1
- package/dist/src/utils/catalog-generation-control-snapshot.d.ts +11 -0
- package/dist/src/utils/catalog-generation-control-snapshot.js +45 -0
- package/dist/src/utils/catalog-generation-control-snapshot.js.map +1 -0
- package/dist/src/utils/catalog-generation-error.d.ts +16 -0
- package/dist/src/utils/catalog-generation-error.js +30 -0
- package/dist/src/utils/catalog-generation-error.js.map +1 -0
- package/dist/src/utils/catalog-generation-resolver.d.ts +44 -0
- package/dist/src/utils/catalog-generation-resolver.js +273 -0
- package/dist/src/utils/catalog-generation-resolver.js.map +1 -0
- package/dist/src/utils/catalog-generation-service.d.ts +172 -0
- package/dist/src/utils/catalog-generation-service.js +1271 -0
- package/dist/src/utils/catalog-generation-service.js.map +1 -0
- package/dist/src/utils/catalog-generation-state.d.ts +22 -0
- package/dist/src/utils/catalog-generation-state.js +192 -0
- package/dist/src/utils/catalog-generation-state.js.map +1 -0
- package/dist/src/utils/catalog-generation-store.d.ts +58 -0
- package/dist/src/utils/catalog-generation-store.js +452 -0
- package/dist/src/utils/catalog-generation-store.js.map +1 -0
- package/dist/src/utils/catalog-manifest-v7.d.ts +5 -0
- package/dist/src/utils/{catalog-manifest-v4.js → catalog-manifest-v7.js} +35 -43
- package/dist/src/utils/catalog-manifest-v7.js.map +1 -0
- package/dist/src/utils/catalog-operation-context.d.ts +20 -0
- package/dist/src/utils/catalog-operation-context.js +142 -0
- package/dist/src/utils/catalog-operation-context.js.map +1 -0
- package/dist/src/utils/catalog-public-error.d.ts +7 -3
- package/dist/src/utils/catalog-public-error.js +28 -8
- package/dist/src/utils/catalog-public-error.js.map +1 -1
- package/dist/src/utils/catalog-runtime-projection.d.ts +3 -0
- package/dist/src/utils/catalog-runtime-projection.js +5 -40
- package/dist/src/utils/catalog-runtime-projection.js.map +1 -1
- package/dist/src/utils/catalog-snapshot.d.ts +5 -3
- package/dist/src/utils/catalog-snapshot.js +11 -3
- package/dist/src/utils/catalog-snapshot.js.map +1 -1
- package/dist/src/utils/catalog-source-identity.d.ts +1 -0
- package/dist/src/utils/catalog-source-identity.js +21 -2
- package/dist/src/utils/catalog-source-identity.js.map +1 -1
- package/dist/src/utils/catalog-source-impact.d.ts +21 -0
- package/dist/src/utils/catalog-source-impact.js +344 -0
- package/dist/src/utils/catalog-source-impact.js.map +1 -0
- package/dist/src/utils/catalog-source-references.d.ts +3 -2
- package/dist/src/utils/catalog-source-references.js +195 -347
- package/dist/src/utils/catalog-source-references.js.map +1 -1
- package/dist/src/utils/catalog-sources.d.ts +15 -1
- package/dist/src/utils/catalog-sources.js +206 -27
- package/dist/src/utils/catalog-sources.js.map +1 -1
- package/dist/src/utils/claude-skills-root.d.ts +8 -0
- package/dist/src/utils/claude-skills-root.js +76 -0
- package/dist/src/utils/claude-skills-root.js.map +1 -0
- package/dist/src/utils/codex-config.d.ts +2 -0
- package/dist/src/utils/codex-config.js +26 -5
- package/dist/src/utils/codex-config.js.map +1 -1
- package/dist/src/utils/codex-manager.d.ts +79 -0
- package/dist/src/utils/codex-manager.js +735 -0
- package/dist/src/utils/codex-manager.js.map +1 -0
- package/dist/src/utils/codex-marketplace-path.d.ts +5 -0
- package/dist/src/utils/codex-marketplace-path.js +45 -0
- package/dist/src/utils/codex-marketplace-path.js.map +1 -0
- package/dist/src/utils/codex-native-observation.d.ts +49 -0
- package/dist/src/utils/codex-native-observation.js +154 -0
- package/dist/src/utils/codex-native-observation.js.map +1 -0
- package/dist/src/utils/codex-native-render-receipt.d.ts +40 -0
- package/dist/src/utils/codex-native-render-receipt.js +205 -0
- package/dist/src/utils/codex-native-render-receipt.js.map +1 -0
- package/dist/src/utils/codex-reconciler.d.ts +126 -0
- package/dist/src/utils/codex-reconciler.js +298 -0
- package/dist/src/utils/codex-reconciler.js.map +1 -0
- package/dist/src/utils/codex-shared-native.d.ts +43 -0
- package/dist/src/utils/codex-shared-native.js +81 -0
- package/dist/src/utils/codex-shared-native.js.map +1 -0
- package/dist/src/utils/codex-skill-staging.d.ts +2 -0
- package/dist/src/utils/codex-skill-staging.js.map +1 -1
- package/dist/src/utils/codex-skills-root.d.ts +4 -0
- package/dist/src/utils/codex-skills-root.js +25 -0
- package/dist/src/utils/codex-skills-root.js.map +1 -0
- package/dist/src/utils/codex-surface-analysis.d.ts +46 -0
- package/dist/src/utils/codex-surface-analysis.js +247 -0
- package/dist/src/utils/codex-surface-analysis.js.map +1 -0
- package/dist/src/utils/dynamic-skill-tree.d.ts +5 -0
- package/dist/src/utils/dynamic-skill-tree.js +56 -7
- package/dist/src/utils/dynamic-skill-tree.js.map +1 -1
- package/dist/src/utils/git-catalog-provider.d.ts +16 -0
- package/dist/src/utils/git-catalog-provider.js +177 -145
- package/dist/src/utils/git-catalog-provider.js.map +1 -1
- package/dist/src/utils/local-marketplace.d.ts +27 -1
- package/dist/src/utils/local-marketplace.js +434 -84
- package/dist/src/utils/local-marketplace.js.map +1 -1
- package/dist/src/utils/managed-command-closure.d.ts +3 -0
- package/dist/src/utils/managed-command-closure.js +35 -0
- package/dist/src/utils/managed-command-closure.js.map +1 -0
- package/dist/src/utils/managed-command-evidence.d.ts +7 -0
- package/dist/src/utils/managed-command-evidence.js +86 -0
- package/dist/src/utils/managed-command-evidence.js.map +1 -0
- package/dist/src/utils/managed-command-materialization.d.ts +115 -0
- package/dist/src/utils/managed-command-materialization.js +359 -0
- package/dist/src/utils/managed-command-materialization.js.map +1 -0
- package/dist/src/utils/managed-command-runtime.d.ts +33 -0
- package/dist/src/utils/managed-command-runtime.js +121 -0
- package/dist/src/utils/managed-command-runtime.js.map +1 -0
- package/dist/src/utils/managed-command-store.d.ts +128 -0
- package/dist/src/utils/managed-command-store.js +589 -0
- package/dist/src/utils/managed-command-store.js.map +1 -0
- package/dist/src/utils/manifest.d.ts +1 -3
- package/dist/src/utils/manifest.js +1 -3
- package/dist/src/utils/manifest.js.map +1 -1
- package/dist/src/utils/multi-source-catalog.d.ts +3 -9
- package/dist/src/utils/multi-source-catalog.js +54 -110
- package/dist/src/utils/multi-source-catalog.js.map +1 -1
- package/dist/src/utils/native-cli.d.ts +61 -7
- package/dist/src/utils/native-cli.js +426 -58
- package/dist/src/utils/native-cli.js.map +1 -1
- package/dist/src/utils/native-tool-context.d.ts +14 -0
- package/dist/src/utils/native-tool-context.js +38 -0
- package/dist/src/utils/native-tool-context.js.map +1 -0
- package/dist/src/utils/native-tool-review.d.ts +21 -0
- package/dist/src/utils/native-tool-review.js +102 -0
- package/dist/src/utils/native-tool-review.js.map +1 -0
- package/dist/src/utils/orphan-marketplace-cleanup.js +0 -4
- package/dist/src/utils/orphan-marketplace-cleanup.js.map +1 -1
- package/dist/src/utils/ownership.d.ts +88 -20
- package/dist/src/utils/ownership.js +185 -223
- package/dist/src/utils/ownership.js.map +1 -1
- package/dist/src/utils/path-catalog-provider.d.ts +1 -1
- package/dist/src/utils/path-catalog-provider.js +54 -65
- package/dist/src/utils/path-catalog-provider.js.map +1 -1
- package/dist/src/utils/paths.d.ts +0 -1
- package/dist/src/utils/paths.js +0 -1
- package/dist/src/utils/paths.js.map +1 -1
- package/dist/src/utils/plugin-config-impact.d.ts +40 -0
- package/dist/src/utils/plugin-config-impact.js +373 -0
- package/dist/src/utils/plugin-config-impact.js.map +1 -0
- package/dist/src/utils/plugin-config-write.d.ts +52 -27
- package/dist/src/utils/plugin-config-write.js +1112 -298
- package/dist/src/utils/plugin-config-write.js.map +1 -1
- package/dist/src/utils/plugin-config.d.ts +153 -93
- package/dist/src/utils/plugin-config.js +1116 -968
- package/dist/src/utils/plugin-config.js.map +1 -1
- package/dist/src/utils/plugin-intent-state.d.ts +49 -0
- package/dist/src/utils/plugin-intent-state.js +320 -0
- package/dist/src/utils/plugin-intent-state.js.map +1 -0
- package/dist/src/utils/plugin-scripts.d.ts +66 -125
- package/dist/src/utils/plugin-scripts.js +518 -526
- package/dist/src/utils/plugin-scripts.js.map +1 -1
- package/dist/src/utils/process-lock.js +14 -5
- package/dist/src/utils/process-lock.js.map +1 -1
- package/dist/src/utils/process-tree.d.ts +2 -2
- package/dist/src/utils/process-tree.js +1 -1
- package/dist/src/utils/process-tree.js.map +1 -1
- package/dist/src/utils/project-boundary.d.ts +11 -13
- package/dist/src/utils/project-boundary.js +28 -34
- package/dist/src/utils/project-boundary.js.map +1 -1
- package/dist/src/utils/rendered-contract.js +18 -1
- package/dist/src/utils/rendered-contract.js.map +1 -1
- package/dist/src/utils/runtime-config.d.ts +20 -0
- package/dist/src/utils/runtime-config.js +75 -0
- package/dist/src/utils/runtime-config.js.map +1 -0
- package/dist/src/utils/runtime-skill-closure.js +47 -6
- package/dist/src/utils/runtime-skill-closure.js.map +1 -1
- package/dist/src/utils/safe-fs.d.ts +21 -0
- package/dist/src/utils/safe-fs.js +111 -0
- package/dist/src/utils/safe-fs.js.map +1 -0
- package/dist/src/utils/safe-operation-diagnostic.d.ts +15 -0
- package/dist/src/utils/safe-operation-diagnostic.js +33 -0
- package/dist/src/utils/safe-operation-diagnostic.js.map +1 -0
- package/dist/src/utils/sensitive-content.d.ts +4 -0
- package/dist/src/utils/sensitive-content.js +36 -0
- package/dist/src/utils/sensitive-content.js.map +1 -0
- package/dist/src/utils/state-paths.js +7 -2
- package/dist/src/utils/state-paths.js.map +1 -1
- package/dist/src/utils/target-eligibility.d.ts +8 -2
- package/dist/src/utils/target-eligibility.js +17 -8
- package/dist/src/utils/target-eligibility.js.map +1 -1
- package/dist/src/utils/windows-claude-shim.d.ts +7 -0
- package/dist/src/utils/windows-claude-shim.js +10 -0
- package/dist/src/utils/windows-claude-shim.js.map +1 -0
- package/dist/src/utils/windows-npm-shim.d.ts +14 -0
- package/dist/src/utils/windows-npm-shim.js +140 -0
- package/dist/src/utils/windows-npm-shim.js.map +1 -0
- package/dist/vitest.config.js +100 -9
- package/dist/vitest.config.js.map +1 -1
- package/package.json +31 -21
- package/dist/authoring/scripts/check-plugin-versions.d.mts +0 -2
- package/dist/authoring/scripts/check-plugin-versions.mjs +0 -5
- package/dist/authoring/scripts/check-plugin-versions.mjs.map +0 -1
- package/dist/scripts/aw-skill-eval-engine.d.ts +0 -13
- package/dist/scripts/aw-skill-eval-engine.js +0 -31
- package/dist/scripts/aw-skill-eval-engine.js.map +0 -1
- package/dist/scripts/codex-eval-app-server.d.ts +0 -89
- package/dist/scripts/codex-eval-app-server.js +0 -1228
- package/dist/scripts/codex-eval-app-server.js.map +0 -1
- package/dist/scripts/codex-eval-isolation.d.ts +0 -61
- package/dist/scripts/codex-eval-isolation.js +0 -318
- package/dist/scripts/codex-eval-isolation.js.map +0 -1
- package/dist/scripts/codex-eval-process-management.d.ts +0 -8
- package/dist/scripts/codex-eval-process-management.js +0 -69
- package/dist/scripts/codex-eval-process-management.js.map +0 -1
- package/dist/scripts/codex-eval-process-tree.d.ts +0 -38
- package/dist/scripts/codex-eval-process-tree.js +0 -803
- package/dist/scripts/codex-eval-process-tree.js.map +0 -1
- package/dist/scripts/codex-eval-runtime-config.d.ts +0 -62
- package/dist/scripts/codex-eval-runtime-config.js +0 -843
- package/dist/scripts/codex-eval-runtime-config.js.map +0 -1
- package/dist/scripts/codex-trigger-eval.d.ts +0 -84
- package/dist/scripts/codex-trigger-eval.js +0 -968
- package/dist/scripts/codex-trigger-eval.js.map +0 -1
- package/dist/scripts/eval-plugin-skills-audited.d.ts +0 -6
- package/dist/scripts/eval-plugin-skills-audited.js +0 -261
- package/dist/scripts/eval-plugin-skills-audited.js.map +0 -1
- package/dist/scripts/eval-plugin-skills.d.ts +0 -45
- package/dist/scripts/eval-plugin-skills.js +0 -1586
- package/dist/scripts/eval-plugin-skills.js.map +0 -1
- package/dist/scripts/eval-run-layout.d.ts +0 -17
- package/dist/scripts/eval-run-layout.js +0 -26
- package/dist/scripts/eval-run-layout.js.map +0 -1
- package/dist/scripts/plugin-eval-contract.d.ts +0 -185
- package/dist/scripts/plugin-eval-contract.js +0 -695
- package/dist/scripts/plugin-eval-contract.js.map +0 -1
- package/dist/scripts/plugin-eval-materializer.d.ts +0 -13
- package/dist/scripts/plugin-eval-materializer.js +0 -421
- package/dist/scripts/plugin-eval-materializer.js.map +0 -1
- package/dist/scripts/skill-reporting.d.ts +0 -212
- package/dist/scripts/skill-reporting.js +0 -922
- package/dist/scripts/skill-reporting.js.map +0 -1
- package/dist/src/commands/review-publication.d.ts +0 -99
- package/dist/src/commands/review-publication.js +0 -904
- package/dist/src/commands/review-publication.js.map +0 -1
- package/dist/src/commands/status-v4.d.ts +0 -123
- package/dist/src/commands/status-v4.js +0 -905
- package/dist/src/commands/status-v4.js.map +0 -1
- package/dist/src/commands/status.d.ts +0 -93
- package/dist/src/commands/status.js +0 -442
- package/dist/src/commands/status.js.map +0 -1
- package/dist/src/utils/active-marketplace-runtime-lock.d.ts +0 -12
- package/dist/src/utils/active-marketplace-runtime-lock.js +0 -133
- package/dist/src/utils/active-marketplace-runtime-lock.js.map +0 -1
- package/dist/src/utils/catalog-manifest-v3.d.ts +0 -2
- package/dist/src/utils/catalog-manifest-v3.js +0 -3
- package/dist/src/utils/catalog-manifest-v3.js.map +0 -1
- package/dist/src/utils/catalog-manifest-v4.d.ts +0 -5
- package/dist/src/utils/catalog-manifest-v4.js.map +0 -1
- package/dist/src/utils/catalog-source-cache.d.ts +0 -62
- package/dist/src/utils/catalog-source-cache.js +0 -296
- package/dist/src/utils/catalog-source-cache.js.map +0 -1
- package/dist/src/utils/codex-project-surface.d.ts +0 -3
- package/dist/src/utils/codex-project-surface.js +0 -49
- package/dist/src/utils/codex-project-surface.js.map +0 -1
- package/dist/src/utils/lifecycle-active-store.d.ts +0 -175
- package/dist/src/utils/lifecycle-active-store.js +0 -1276
- package/dist/src/utils/lifecycle-active-store.js.map +0 -1
- package/dist/src/utils/plugin-release.d.ts +0 -2
- package/dist/src/utils/plugin-release.js +0 -2
- package/dist/src/utils/plugin-release.js.map +0 -1
|
@@ -1,20 +1,73 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
2
|
import * as nodeFs from 'node:fs/promises';
|
|
4
3
|
import path from 'node:path';
|
|
5
4
|
import fs from 'fs-extra';
|
|
5
|
+
import { canonicalizePluginConfigValues, formatPluginConfigIssue, getPluginConfigDependencyKeys, matchPluginConfigPreset, redactPluginConfigValue, stablePluginConfigStringify, validatePluginConfigCandidate, validatePluginConfigDocument, validatePluginConfigValue as validateCorePluginConfigValue, } from '@lesliechan721/aw-plugin-core/manifest';
|
|
6
6
|
import { assertTestReadablePath, assertTestWritablePath, } from './test-path-fence.js';
|
|
7
|
-
import { markActiveMarketplaceDurableCommit, withActiveMarketplaceRuntimeLease, } from './active-marketplace-runtime-lock.js';
|
|
8
7
|
import { canonicalizePathThroughExistingAncestor, resolveAwHome, resolveRepoRootSync, } from './paths.js';
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
import { assertRealFileParentWithinRoot, assertSafeFileMutationPath, atomicReplaceSafeFile, } from './safe-fs.js';
|
|
9
|
+
import { assertProjectSharedConfigPathVisible, PROJECT_CONFIG_SHAREABILITY_UNVERIFIED_WARNING, } from './project-boundary.js';
|
|
10
|
+
export function codexNativeRefreshKeys(manifest) {
|
|
11
|
+
return new Set((manifest.artifacts?.targets ?? []).flatMap((target) => target.platform === 'codex'
|
|
12
|
+
&& target.target === 'codex.skill'
|
|
13
|
+
&& target.delivery?.mode === 'codex-native'
|
|
14
|
+
? target.refreshKeys
|
|
15
|
+
: []));
|
|
16
|
+
}
|
|
17
|
+
function readPluginConfigBinding(value, scope) {
|
|
18
|
+
if (!isRecord(value))
|
|
19
|
+
return null;
|
|
20
|
+
if (scope === 'user') {
|
|
21
|
+
return value.kind === 'source'
|
|
22
|
+
&& typeof value.sourceIdentity === 'string'
|
|
23
|
+
&& /^sha256:[0-9a-f]{64}$/u.test(value.sourceIdentity)
|
|
24
|
+
&& Object.keys(value).every((key) => ['kind', 'sourceIdentity'].includes(key))
|
|
25
|
+
? { kind: 'source', sourceIdentity: value.sourceIdentity }
|
|
26
|
+
: null;
|
|
27
|
+
}
|
|
28
|
+
return value.kind === 'catalog'
|
|
29
|
+
&& typeof value.logicalIdentity === 'string'
|
|
30
|
+
&& value.logicalIdentity.trim().length > 0
|
|
31
|
+
&& Object.keys(value).every((key) => ['kind', 'logicalIdentity'].includes(key))
|
|
32
|
+
? { kind: 'catalog', logicalIdentity: value.logicalIdentity }
|
|
33
|
+
: null;
|
|
34
|
+
}
|
|
35
|
+
const PLUGIN_CONFIG_ENVELOPE_V5_KEYS = new Set([
|
|
36
|
+
'schemaVersion',
|
|
37
|
+
'sourceId',
|
|
38
|
+
'pluginName',
|
|
39
|
+
'binding',
|
|
40
|
+
'values',
|
|
41
|
+
]);
|
|
42
|
+
function readPluginConfigEnvelopeV5(value, scope) {
|
|
43
|
+
if (!isRecord(value)
|
|
44
|
+
|| value.schemaVersion !== 5
|
|
45
|
+
|| typeof value.sourceId !== 'string'
|
|
46
|
+
|| typeof value.pluginName !== 'string'
|
|
47
|
+
|| !isRecord(value.values)
|
|
48
|
+
|| Object.keys(value).some((key) => !PLUGIN_CONFIG_ENVELOPE_V5_KEYS.has(key))) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const binding = readPluginConfigBinding(value.binding, scope);
|
|
52
|
+
if (!binding)
|
|
53
|
+
return null;
|
|
54
|
+
return {
|
|
55
|
+
schemaVersion: 5,
|
|
56
|
+
sourceId: value.sourceId,
|
|
57
|
+
pluginName: value.pluginName,
|
|
58
|
+
binding,
|
|
59
|
+
values: value.values,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export class PluginConfigResetError extends Error {
|
|
63
|
+
code;
|
|
64
|
+
osCode;
|
|
65
|
+
constructor(code, message, osCode) {
|
|
66
|
+
super(`${code}: ${message}`);
|
|
67
|
+
this.name = 'PluginConfigResetError';
|
|
68
|
+
this.code = code;
|
|
69
|
+
this.osCode = osCode;
|
|
16
70
|
}
|
|
17
|
-
markActiveMarketplaceDurableCommit();
|
|
18
71
|
}
|
|
19
72
|
export class PluginConfigSchemaResolutionError extends Error {
|
|
20
73
|
code;
|
|
@@ -40,16 +93,12 @@ const CONFIG_DIR = 'plugin-config';
|
|
|
40
93
|
const CONFIG_FILE = 'config.json';
|
|
41
94
|
let atomicConfigBeforeRenameHookForTests;
|
|
42
95
|
let atomicConfigAfterRenameHookForTests;
|
|
43
|
-
let configFileLockBeforeReleaseHookForTests;
|
|
44
96
|
export function setAtomicConfigBeforeRenameHookForTests(hook) {
|
|
45
97
|
atomicConfigBeforeRenameHookForTests = hook;
|
|
46
98
|
}
|
|
47
99
|
export function setAtomicConfigAfterRenameHookForTests(hook) {
|
|
48
100
|
atomicConfigAfterRenameHookForTests = hook;
|
|
49
101
|
}
|
|
50
|
-
export function setConfigFileLockBeforeReleaseHookForTests(hook) {
|
|
51
|
-
configFileLockBeforeReleaseHookForTests = hook;
|
|
52
|
-
}
|
|
53
102
|
function assertConfigPathBoundary(allowedRoot, candidatePath) {
|
|
54
103
|
const root = path.resolve(allowedRoot);
|
|
55
104
|
const candidate = path.resolve(candidatePath);
|
|
@@ -113,179 +162,40 @@ async function inspectConfigInventoryStoreRoot(boundaryRoot, storeRoot) {
|
|
|
113
162
|
}
|
|
114
163
|
return true;
|
|
115
164
|
}
|
|
116
|
-
async function readConfigDirectoryIdentity(directoryPath) {
|
|
117
|
-
const stats = await fs.lstat(directoryPath);
|
|
118
|
-
if (stats.isSymbolicLink() || !stats.isDirectory()) {
|
|
119
|
-
throw new Error(`Config parent must be a real directory: ${directoryPath}`);
|
|
120
|
-
}
|
|
121
|
-
return {
|
|
122
|
-
dev: stats.dev,
|
|
123
|
-
ino: stats.ino,
|
|
124
|
-
realPath: await fs.realpath(directoryPath),
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
function assertConfigDirectoryStable(label, before, after) {
|
|
128
|
-
if (before.dev !== after.dev || before.ino !== after.ino || before.realPath !== after.realPath) {
|
|
129
|
-
throw new Error(`${label} identity changed before atomic config replacement.`);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
function assertRealConfigBoundary(root, parent) {
|
|
133
|
-
const relative = path.relative(root.realPath, parent.realPath);
|
|
134
|
-
if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
135
|
-
throw new Error('Config parent realpath escapes its allowed root.');
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
165
|
async function prepareConfigFileParent(filePath, allowedRoot) {
|
|
139
166
|
assertConfigPathBoundary(allowedRoot, filePath);
|
|
140
167
|
await assertNoConfigSymlink(allowedRoot, filePath);
|
|
141
168
|
await fs.ensureDir(path.dirname(filePath));
|
|
142
169
|
await assertNoConfigSymlink(allowedRoot, filePath);
|
|
143
170
|
}
|
|
144
|
-
export function
|
|
145
|
-
return path.join(path.dirname(filePath), `.${path.basename(filePath)}.aw.lock`);
|
|
146
|
-
}
|
|
147
|
-
async function releaseConfigFileLock(lease) {
|
|
148
|
-
let lastError;
|
|
149
|
-
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
150
|
-
try {
|
|
151
|
-
await configFileLockBeforeReleaseHookForTests?.(lease.lockPath, attempt);
|
|
152
|
-
await lease.release();
|
|
153
|
-
return null;
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
lastError = error;
|
|
157
|
-
if (attempt < 3) {
|
|
158
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
try {
|
|
163
|
-
await lease.release();
|
|
164
|
-
}
|
|
165
|
-
catch {
|
|
166
|
-
// Preserve the first release diagnostic. A final direct retry avoids a hook-created self-lock.
|
|
167
|
-
}
|
|
168
|
-
return lastError;
|
|
169
|
-
}
|
|
170
|
-
function emitConfigReleaseWarning(message) {
|
|
171
|
-
try {
|
|
172
|
-
process.emitWarning(message, { code: 'AW_CONFIG_LOCK_RELEASE_FAILED' });
|
|
173
|
-
}
|
|
174
|
-
catch {
|
|
175
|
-
// Diagnostics cannot replace the primary action result or error.
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
export async function withConfigFileLock(options) {
|
|
171
|
+
export async function withConfigFileMutation(options) {
|
|
179
172
|
await prepareConfigFileParent(options.filePath, options.allowedRoot);
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const lease = await acquireProcessLock({ lockPath });
|
|
183
|
-
let result;
|
|
184
|
-
let actionError;
|
|
185
|
-
let actionSucceeded = false;
|
|
186
|
-
const commitContext = {
|
|
187
|
-
parent: configCommitStorage.getStore(),
|
|
188
|
-
durableCommit: false,
|
|
189
|
-
};
|
|
190
|
-
try {
|
|
191
|
-
await assertNoConfigSymlink(options.allowedRoot, options.filePath);
|
|
192
|
-
result = await configCommitStorage.run(commitContext, options.action);
|
|
193
|
-
actionSucceeded = true;
|
|
194
|
-
}
|
|
195
|
-
catch (error) {
|
|
196
|
-
actionError = error;
|
|
197
|
-
}
|
|
198
|
-
const releaseError = await releaseConfigFileLock(lease);
|
|
199
|
-
if (!actionSucceeded) {
|
|
200
|
-
if (releaseError) {
|
|
201
|
-
emitConfigReleaseWarning('Config action failed, and its process lock release also failed.');
|
|
202
|
-
}
|
|
203
|
-
throw actionError;
|
|
204
|
-
}
|
|
205
|
-
if (releaseError) {
|
|
206
|
-
if (commitContext.durableCommit) {
|
|
207
|
-
emitConfigReleaseWarning('Config action completed successfully, but its process lock could not be released after 3 attempts. The committed result remains successful; the lock can require process exit or manual recovery before another update.');
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
throw releaseError;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return result;
|
|
173
|
+
await assertNoConfigSymlink(options.allowedRoot, options.filePath);
|
|
174
|
+
return options.action();
|
|
214
175
|
}
|
|
215
|
-
export async function
|
|
216
|
-
|
|
176
|
+
export async function atomicReplaceConfigFile(options) {
|
|
177
|
+
await atomicReplaceSafeFile({
|
|
217
178
|
...options,
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
action: options.action,
|
|
179
|
+
label: 'Config path',
|
|
180
|
+
temporaryTag: 'tmp',
|
|
181
|
+
beforeRename: atomicConfigBeforeRenameHookForTests,
|
|
182
|
+
afterRename: async (targetPath) => {
|
|
183
|
+
await atomicConfigAfterRenameHookForTests?.(targetPath).catch(() => undefined);
|
|
184
|
+
},
|
|
225
185
|
});
|
|
226
186
|
}
|
|
227
|
-
export async function atomicReplaceConfigFile(options) {
|
|
228
|
-
await prepareConfigFileParent(options.filePath, options.allowedRoot);
|
|
229
|
-
const rootPath = path.resolve(options.allowedRoot);
|
|
230
|
-
const parentPath = path.dirname(path.resolve(options.filePath));
|
|
231
|
-
const rootIdentity = await readConfigDirectoryIdentity(rootPath);
|
|
232
|
-
const parentIdentity = await readConfigDirectoryIdentity(parentPath);
|
|
233
|
-
assertRealConfigBoundary(rootIdentity, parentIdentity);
|
|
234
|
-
const currentTarget = await lstatIfExists(options.filePath);
|
|
235
|
-
if (currentTarget && (!currentTarget.isFile() || currentTarget.isSymbolicLink())) {
|
|
236
|
-
throw new Error(`Config target must be a regular file: ${options.filePath}`);
|
|
237
|
-
}
|
|
238
|
-
const temporaryPath = path.join(parentPath, `.${path.basename(options.filePath)}.tmp-${process.pid}-${crypto.randomUUID()}`);
|
|
239
|
-
assertConfigPathBoundary(options.allowedRoot, temporaryPath);
|
|
240
|
-
let handle;
|
|
241
|
-
try {
|
|
242
|
-
handle = await nodeFs.open(temporaryPath, 'wx', options.mode ?? (currentTarget ? currentTarget.mode & 0o777 : 0o600));
|
|
243
|
-
await handle.writeFile(options.content, 'utf8');
|
|
244
|
-
await handle.sync();
|
|
245
|
-
await handle.close();
|
|
246
|
-
handle = undefined;
|
|
247
|
-
await atomicConfigBeforeRenameHookForTests?.(options.filePath, temporaryPath);
|
|
248
|
-
await assertNoConfigSymlink(options.allowedRoot, options.filePath);
|
|
249
|
-
const currentRootIdentity = await readConfigDirectoryIdentity(rootPath);
|
|
250
|
-
const currentParentIdentity = await readConfigDirectoryIdentity(parentPath);
|
|
251
|
-
assertRealConfigBoundary(currentRootIdentity, currentParentIdentity);
|
|
252
|
-
assertConfigDirectoryStable('Config allowed root', rootIdentity, currentRootIdentity);
|
|
253
|
-
assertConfigDirectoryStable('Config parent', parentIdentity, currentParentIdentity);
|
|
254
|
-
const targetBeforeRename = await lstatIfExists(options.filePath);
|
|
255
|
-
if (targetBeforeRename && (!targetBeforeRename.isFile() || targetBeforeRename.isSymbolicLink())) {
|
|
256
|
-
throw new Error(`Config target must be a regular file: ${options.filePath}`);
|
|
257
|
-
}
|
|
258
|
-
await nodeFs.rename(temporaryPath, options.filePath);
|
|
259
|
-
markConfigDurableCommit();
|
|
260
|
-
// rename is the commit point. Diagnostics and durability work after it are best effort,
|
|
261
|
-
// because reporting failure here would leave the new generation on disk without giving
|
|
262
|
-
// the caller a safe way to restore the old generation.
|
|
263
|
-
await atomicConfigAfterRenameHookForTests?.(options.filePath).catch(() => undefined);
|
|
264
|
-
await (async () => {
|
|
265
|
-
const finalTarget = await fs.lstat(options.filePath);
|
|
266
|
-
if (!finalTarget.isFile() || finalTarget.isSymbolicLink()) {
|
|
267
|
-
throw new Error('Atomic config replacement produced an invalid target.');
|
|
268
|
-
}
|
|
269
|
-
if (await fs.readFile(options.filePath, 'utf8') !== options.content) {
|
|
270
|
-
throw new Error('Atomic config replacement content differs from the committed content.');
|
|
271
|
-
}
|
|
272
|
-
})().catch(() => undefined);
|
|
273
|
-
const directoryHandle = await nodeFs.open(parentPath, 'r').catch(() => null);
|
|
274
|
-
await directoryHandle?.sync().catch(() => undefined);
|
|
275
|
-
await directoryHandle?.close().catch(() => undefined);
|
|
276
|
-
}
|
|
277
|
-
finally {
|
|
278
|
-
await handle?.close().catch(() => undefined);
|
|
279
|
-
await nodeFs.rm(temporaryPath, { force: true }).catch(() => undefined);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
187
|
export async function atomicRemoveConfigFile(options) {
|
|
283
188
|
await prepareConfigFileParent(options.filePath, options.allowedRoot);
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
189
|
+
await assertSafeFileMutationPath({
|
|
190
|
+
filePath: options.filePath,
|
|
191
|
+
allowedRoot: options.allowedRoot,
|
|
192
|
+
label: 'Config path',
|
|
193
|
+
});
|
|
194
|
+
await assertRealFileParentWithinRoot({
|
|
195
|
+
filePath: options.filePath,
|
|
196
|
+
allowedRoot: options.allowedRoot,
|
|
197
|
+
label: 'Config path',
|
|
198
|
+
});
|
|
289
199
|
const currentTarget = await lstatIfExists(options.filePath);
|
|
290
200
|
if (!currentTarget)
|
|
291
201
|
return false;
|
|
@@ -293,12 +203,16 @@ export async function atomicRemoveConfigFile(options) {
|
|
|
293
203
|
throw new Error(`Config target must be a regular file: ${options.filePath}`);
|
|
294
204
|
}
|
|
295
205
|
await atomicConfigBeforeRenameHookForTests?.(options.filePath, options.filePath);
|
|
296
|
-
await
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
206
|
+
await assertSafeFileMutationPath({
|
|
207
|
+
filePath: options.filePath,
|
|
208
|
+
allowedRoot: options.allowedRoot,
|
|
209
|
+
label: 'Config path',
|
|
210
|
+
});
|
|
211
|
+
await assertRealFileParentWithinRoot({
|
|
212
|
+
filePath: options.filePath,
|
|
213
|
+
allowedRoot: options.allowedRoot,
|
|
214
|
+
label: 'Config path',
|
|
215
|
+
});
|
|
302
216
|
const targetBeforeRemove = await lstatIfExists(options.filePath);
|
|
303
217
|
if (!targetBeforeRemove)
|
|
304
218
|
return false;
|
|
@@ -306,18 +220,22 @@ export async function atomicRemoveConfigFile(options) {
|
|
|
306
220
|
throw new Error(`Config target must be a regular file: ${options.filePath}`);
|
|
307
221
|
}
|
|
308
222
|
await nodeFs.unlink(options.filePath);
|
|
309
|
-
markConfigDurableCommit();
|
|
310
223
|
await atomicConfigAfterRenameHookForTests?.(options.filePath).catch(() => undefined);
|
|
311
|
-
const directoryHandle = await nodeFs.open(parentPath, 'r').catch(() => null);
|
|
312
|
-
await directoryHandle?.sync().catch(() => undefined);
|
|
313
|
-
await directoryHandle?.close().catch(() => undefined);
|
|
314
224
|
return true;
|
|
315
225
|
}
|
|
316
226
|
function isRecord(value) {
|
|
317
227
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
318
228
|
}
|
|
319
229
|
function formatValue(value) {
|
|
320
|
-
|
|
230
|
+
if (typeof value === 'string')
|
|
231
|
+
return value;
|
|
232
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
233
|
+
return stablePluginConfigStringify(value);
|
|
234
|
+
}
|
|
235
|
+
if (isRecord(value) || Array.isArray(value)) {
|
|
236
|
+
return stablePluginConfigStringify(value);
|
|
237
|
+
}
|
|
238
|
+
return JSON.stringify(value);
|
|
321
239
|
}
|
|
322
240
|
async function calculateConfigManifestDigest(filePath) {
|
|
323
241
|
const hash = crypto.createHash('sha256');
|
|
@@ -341,7 +259,7 @@ function createConfigSchemaEvidenceFromCurrentCatalog(plugin, manifestDigest) {
|
|
|
341
259
|
};
|
|
342
260
|
}
|
|
343
261
|
function getStoreEntries(values) {
|
|
344
|
-
return
|
|
262
|
+
return Object.fromEntries(Object.entries(values));
|
|
345
263
|
}
|
|
346
264
|
export function assertSafePluginConfigSegment(pluginName) {
|
|
347
265
|
if (typeof pluginName !== 'string' || pluginName.trim() === '') {
|
|
@@ -375,22 +293,15 @@ export function resolvePluginConfigStorePath(options) {
|
|
|
375
293
|
const root = options.scope === 'user'
|
|
376
294
|
? resolveAwHome(options.env)
|
|
377
295
|
: path.join(resolveAwProjectRoot(options.cwd), '.agent-workflows');
|
|
378
|
-
const
|
|
379
|
-
? 'plugin-config.local'
|
|
380
|
-
: CONFIG_DIR;
|
|
381
|
-
const filePath = path.join(root, configDir, ...(sourceId ? [sourceId] : []), pluginName, CONFIG_FILE);
|
|
296
|
+
const filePath = path.join(root, CONFIG_DIR, ...(sourceId ? [sourceId] : []), pluginName, CONFIG_FILE);
|
|
382
297
|
if (options.scope !== 'project') {
|
|
383
298
|
return filePath;
|
|
384
299
|
}
|
|
385
300
|
if (options.access === 'read') {
|
|
386
|
-
return assertTestReadablePath(filePath,
|
|
387
|
-
? 'project local plugin config store (.agent-workflows/plugin-config.local)'
|
|
388
|
-
: 'project shared plugin config store (.agent-workflows/plugin-config)', '用 registerReadonlyFixtureRoot() 或 runWithRepositoryCwdAsReadOnly() 登记只读 project fixture 根;需要写入时改用 withSafeTestCwd() 或 registerWritableStateRoot()。');
|
|
301
|
+
return assertTestReadablePath(filePath, 'project plugin config store (.agent-workflows/plugin-config)', '用 registerReadonlyFixtureRoot() 或 runWithRepositoryCwdAsReadOnly() 登记只读 project fixture 根;需要写入时改用 withSafeTestCwd() 或 registerWritableStateRoot()。');
|
|
389
302
|
}
|
|
390
303
|
if (options.access === 'write') {
|
|
391
|
-
return assertTestWritablePath(filePath,
|
|
392
|
-
? 'project local plugin config store (.agent-workflows/plugin-config.local)'
|
|
393
|
-
: 'project shared plugin config store (.agent-workflows/plugin-config)', '用 withSafeTestCwd() 切到测试可写 cwd,或通过 registerWritableStateRoot() 登记 project fixture 根。');
|
|
304
|
+
return assertTestWritablePath(filePath, 'project plugin config store (.agent-workflows/plugin-config)', '用 withSafeTestCwd() 切到测试可写 cwd,或通过 registerWritableStateRoot() 登记 project fixture 根。');
|
|
394
305
|
}
|
|
395
306
|
return filePath;
|
|
396
307
|
}
|
|
@@ -401,56 +312,167 @@ export function resolvePluginConfigStoreTargets(options) {
|
|
|
401
312
|
path: resolvePluginConfigStorePath(options),
|
|
402
313
|
}];
|
|
403
314
|
}
|
|
404
|
-
return [
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
...options,
|
|
409
|
-
projectLayer: 'shared',
|
|
410
|
-
}),
|
|
411
|
-
projectLayer: 'shared',
|
|
412
|
-
},
|
|
413
|
-
{
|
|
414
|
-
storeKind: 'projectLocal',
|
|
415
|
-
path: resolvePluginConfigStorePath({
|
|
416
|
-
...options,
|
|
417
|
-
projectLayer: 'local',
|
|
418
|
-
}),
|
|
419
|
-
projectLayer: 'local',
|
|
420
|
-
},
|
|
421
|
-
];
|
|
315
|
+
return [{
|
|
316
|
+
storeKind: 'project',
|
|
317
|
+
path: resolvePluginConfigStorePath(options),
|
|
318
|
+
}];
|
|
422
319
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
320
|
+
function isPluginConfigScalar(value) {
|
|
321
|
+
if (typeof value === 'string' || typeof value === 'boolean')
|
|
322
|
+
return true;
|
|
323
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
324
|
+
}
|
|
325
|
+
function isPluginConfigObject(value) {
|
|
326
|
+
if (!isRecord(value))
|
|
327
|
+
return false;
|
|
328
|
+
return Object.values(value).every((property) => {
|
|
329
|
+
if (isPluginConfigScalar(property))
|
|
330
|
+
return true;
|
|
331
|
+
if (Array.isArray(property)) {
|
|
332
|
+
const elementType = typeof property[0];
|
|
333
|
+
return Object.keys(property).length === property.length
|
|
334
|
+
&& property.every((item, index) => Object.hasOwn(property, index)
|
|
335
|
+
&& typeof item === elementType
|
|
336
|
+
&& isPluginConfigScalar(item));
|
|
337
|
+
}
|
|
338
|
+
return isRecord(property) && Object.values(property).every(isPluginConfigScalar);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
function isPluginConfigValue(value) {
|
|
342
|
+
if (isPluginConfigScalar(value))
|
|
343
|
+
return true;
|
|
344
|
+
if (Array.isArray(value)) {
|
|
345
|
+
if (Object.keys(value).length !== value.length)
|
|
346
|
+
return false;
|
|
347
|
+
const first = value[0];
|
|
348
|
+
const elementType = isRecord(first) ? 'object' : typeof first;
|
|
349
|
+
return value.every((item, index) => Object.hasOwn(value, index)
|
|
350
|
+
&& (isRecord(item) ? 'object' : typeof item) === elementType
|
|
351
|
+
&& (isPluginConfigScalar(item) || isPluginConfigObject(item)));
|
|
427
352
|
}
|
|
428
|
-
return value;
|
|
353
|
+
return isPluginConfigObject(value);
|
|
429
354
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
355
|
+
function invalidStore(filePath, code, message, rawDigest) {
|
|
356
|
+
return {
|
|
357
|
+
path: filePath,
|
|
358
|
+
exists: true,
|
|
359
|
+
state: 'invalid',
|
|
360
|
+
values: {},
|
|
361
|
+
...(rawDigest ? { rawDigest } : {}),
|
|
362
|
+
invalidCode: code,
|
|
363
|
+
issues: [message],
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
function readyStore(filePath, values, rawDigest) {
|
|
367
|
+
return {
|
|
368
|
+
path: filePath,
|
|
369
|
+
exists: true,
|
|
370
|
+
state: 'ready',
|
|
371
|
+
values: canonicalizePluginConfigValues(values),
|
|
372
|
+
...(rawDigest ? { rawDigest } : {}),
|
|
373
|
+
issues: [],
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
export function assertReadyPluginConfigStore(store) {
|
|
377
|
+
if (store.state !== 'invalid')
|
|
378
|
+
return;
|
|
379
|
+
throw new Error(`${store.invalidCode}: Invalid plugin config store "${store.path}". ${store.issues.join(' ')}`);
|
|
380
|
+
}
|
|
381
|
+
async function parseStoreJson(filePath) {
|
|
382
|
+
const content = await fs.readFile(filePath);
|
|
383
|
+
const rawDigest = `sha256:${crypto.createHash('sha256').update(content).digest('hex')}`;
|
|
384
|
+
try {
|
|
385
|
+
return {
|
|
386
|
+
state: 'parsed',
|
|
387
|
+
value: JSON.parse(content.toString('utf8')),
|
|
388
|
+
rawDigest,
|
|
389
|
+
};
|
|
434
390
|
}
|
|
435
|
-
|
|
436
|
-
|
|
391
|
+
catch (error) {
|
|
392
|
+
if (error instanceof SyntaxError) {
|
|
393
|
+
return {
|
|
394
|
+
state: 'invalidJson',
|
|
395
|
+
store: invalidStore(filePath, 'invalidJson', 'The store is not valid JSON.', rawDigest),
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
throw error;
|
|
437
399
|
}
|
|
438
|
-
|
|
439
|
-
|
|
400
|
+
}
|
|
401
|
+
function validateStoreValues(options) {
|
|
402
|
+
if (!isRecord(options.values)) {
|
|
403
|
+
return invalidStore(options.filePath, 'invalidValues', 'The values field must be an object.', options.rawDigest);
|
|
440
404
|
}
|
|
441
|
-
|
|
405
|
+
const values = Object.create(null);
|
|
406
|
+
const issues = [];
|
|
407
|
+
for (const [key, value] of Object.entries(options.values)) {
|
|
408
|
+
if (!isPluginConfigValue(value)) {
|
|
409
|
+
issues.push(`Config "${key}" is not a supported config value.`);
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
values[key] = value;
|
|
413
|
+
}
|
|
414
|
+
return issues.length > 0
|
|
415
|
+
? invalidStore(options.filePath, 'invalidValues', issues.join(' '), options.rawDigest)
|
|
416
|
+
: readyStore(options.filePath, values, options.rawDigest);
|
|
417
|
+
}
|
|
418
|
+
async function readStoreFile(filePath) {
|
|
419
|
+
const parsed = await parseStoreJson(filePath);
|
|
420
|
+
if (parsed.state === 'invalidJson')
|
|
421
|
+
return parsed.store;
|
|
422
|
+
return validateStoreValues({ filePath, values: parsed.value, rawDigest: parsed.rawDigest });
|
|
423
|
+
}
|
|
424
|
+
async function readNamespacedStoreFile(options) {
|
|
425
|
+
const parsed = await parseStoreJson(options.filePath);
|
|
426
|
+
if (parsed.state === 'invalidJson')
|
|
427
|
+
return parsed.store;
|
|
428
|
+
const raw = parsed.value;
|
|
429
|
+
if (!isRecord(raw) || raw.schemaVersion !== 5) {
|
|
430
|
+
return invalidStore(options.filePath, 'schemaVersionUnsupported', 'Only plugin config store schemaVersion 5 is supported.', parsed.rawDigest);
|
|
431
|
+
}
|
|
432
|
+
const envelope = readPluginConfigEnvelopeV5(raw, options.scope);
|
|
433
|
+
if (!envelope) {
|
|
434
|
+
return invalidStore(options.filePath, 'schemaVersionUnsupported', 'The plugin config store schemaVersion 5 envelope is invalid.', parsed.rawDigest);
|
|
435
|
+
}
|
|
436
|
+
if (envelope.sourceId !== options.sourceId
|
|
437
|
+
|| envelope.pluginName !== options.pluginName) {
|
|
438
|
+
return invalidStore(options.filePath, 'identityMismatch', 'The store identity does not match its source and namespaced path.', parsed.rawDigest);
|
|
439
|
+
}
|
|
440
|
+
const binding = envelope.binding;
|
|
441
|
+
const matchesBinding = options.scope === 'user'
|
|
442
|
+
? binding?.kind === 'source'
|
|
443
|
+
&& typeof options.sourceIdentity === 'string'
|
|
444
|
+
&& binding.sourceIdentity === options.sourceIdentity
|
|
445
|
+
: binding?.kind === 'catalog'
|
|
446
|
+
&& typeof options.logicalIdentity === 'string'
|
|
447
|
+
&& binding.logicalIdentity === options.logicalIdentity;
|
|
448
|
+
if (!matchesBinding) {
|
|
449
|
+
return invalidStore(options.filePath, 'identityMismatch', options.scope === 'user'
|
|
450
|
+
? 'The user store is not bound to the requested source identity.'
|
|
451
|
+
: 'The project store is not bound to the requested Catalog logical identity.', parsed.rawDigest);
|
|
452
|
+
}
|
|
453
|
+
return validateStoreValues({
|
|
454
|
+
filePath: options.filePath,
|
|
455
|
+
values: envelope.values,
|
|
456
|
+
rawDigest: parsed.rawDigest,
|
|
457
|
+
});
|
|
442
458
|
}
|
|
443
459
|
function namespacedConfigContent(options) {
|
|
460
|
+
const values = canonicalizePluginConfigValues(options.values);
|
|
444
461
|
if (!options.sourceId)
|
|
445
|
-
return `${JSON.stringify(
|
|
446
|
-
|
|
447
|
-
|
|
462
|
+
return `${JSON.stringify(values, null, 2)}\n`;
|
|
463
|
+
const binding = options.scope === 'user'
|
|
464
|
+
? options.sourceIdentity
|
|
465
|
+
? { kind: 'source', sourceIdentity: options.sourceIdentity }
|
|
466
|
+
: (() => { throw new Error('sourceChanged: user plugin config requires a source identity.'); })()
|
|
467
|
+
: options.logicalIdentity
|
|
468
|
+
? { kind: 'catalog', logicalIdentity: options.logicalIdentity }
|
|
469
|
+
: (() => { throw new Error('catalogUnavailable: project plugin config requires verified Catalog logical identity evidence.'); })();
|
|
448
470
|
const envelope = {
|
|
449
|
-
schemaVersion:
|
|
471
|
+
schemaVersion: 5,
|
|
450
472
|
sourceId: options.sourceId,
|
|
451
|
-
sourceIdentity: options.sourceIdentity,
|
|
452
473
|
pluginName: options.pluginName,
|
|
453
|
-
|
|
474
|
+
binding,
|
|
475
|
+
values,
|
|
454
476
|
};
|
|
455
477
|
return `${JSON.stringify(envelope, null, 2)}\n`;
|
|
456
478
|
}
|
|
@@ -462,18 +484,12 @@ async function assertNoLegacyConfigCoexistence(options) {
|
|
|
462
484
|
scope: options.scope,
|
|
463
485
|
cwd: options.cwd,
|
|
464
486
|
env: options.env,
|
|
465
|
-
projectLayer: options.projectLayer,
|
|
466
487
|
...(options.scope === 'project' ? { access: 'read' } : {}),
|
|
467
488
|
});
|
|
468
489
|
if (await lstatIfExists(legacyPath)) {
|
|
469
490
|
throw new Error('legacyStateUnsupported: legacy and namespaced plugin config stores cannot coexist.');
|
|
470
491
|
}
|
|
471
492
|
}
|
|
472
|
-
async function withUserPluginConfigRuntimeLease(options, action) {
|
|
473
|
-
return options.scope === 'user' || options.sourceId !== undefined
|
|
474
|
-
? withActiveMarketplaceRuntimeLease(options.env, action)
|
|
475
|
-
: action();
|
|
476
|
-
}
|
|
477
493
|
export async function readPluginConfigStore(options) {
|
|
478
494
|
const filePath = resolvePluginConfigStorePath({
|
|
479
495
|
...options,
|
|
@@ -481,14 +497,11 @@ export async function readPluginConfigStore(options) {
|
|
|
481
497
|
});
|
|
482
498
|
const allowedRoot = resolvePluginConfigAllowedRoot(options);
|
|
483
499
|
if (options.sourceId) {
|
|
484
|
-
if (!options.sourceIdentity)
|
|
485
|
-
throw new Error('sourceIdentity is required for namespaced plugin config.');
|
|
486
500
|
const legacyPath = resolvePluginConfigStorePath({
|
|
487
501
|
pluginName: options.pluginName,
|
|
488
502
|
scope: options.scope,
|
|
489
503
|
cwd: options.cwd,
|
|
490
504
|
env: options.env,
|
|
491
|
-
projectLayer: options.projectLayer,
|
|
492
505
|
...(options.scope === 'project' ? { access: 'read' } : {}),
|
|
493
506
|
});
|
|
494
507
|
if (await inspectConfigReadTarget(allowedRoot, legacyPath)) {
|
|
@@ -499,21 +512,21 @@ export async function readPluginConfigStore(options) {
|
|
|
499
512
|
return {
|
|
500
513
|
path: filePath,
|
|
501
514
|
exists: false,
|
|
515
|
+
state: 'missing',
|
|
502
516
|
values: {},
|
|
517
|
+
issues: [],
|
|
503
518
|
};
|
|
504
519
|
}
|
|
505
|
-
return
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
: await readStoreFile(filePath),
|
|
516
|
-
};
|
|
520
|
+
return options.sourceId
|
|
521
|
+
? readNamespacedStoreFile({
|
|
522
|
+
filePath,
|
|
523
|
+
sourceId: options.sourceId,
|
|
524
|
+
scope: options.scope,
|
|
525
|
+
sourceIdentity: options.sourceIdentity,
|
|
526
|
+
logicalIdentity: options.logicalIdentity,
|
|
527
|
+
pluginName: options.pluginName,
|
|
528
|
+
})
|
|
529
|
+
: readStoreFile(filePath);
|
|
517
530
|
}
|
|
518
531
|
export async function readNamespacedPluginConfigStoreIdentity(options) {
|
|
519
532
|
const filePath = resolvePluginConfigStorePath({
|
|
@@ -522,15 +535,21 @@ export async function readNamespacedPluginConfigStoreIdentity(options) {
|
|
|
522
535
|
});
|
|
523
536
|
if (!await inspectConfigReadTarget(resolvePluginConfigAllowedRoot(options), filePath))
|
|
524
537
|
return null;
|
|
525
|
-
const
|
|
526
|
-
if (
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
538
|
+
const parsed = await parseStoreJson(filePath);
|
|
539
|
+
if (parsed.state === 'invalidJson') {
|
|
540
|
+
throw new Error(`${parsed.store.invalidCode}: invalid namespaced plugin config store.`);
|
|
541
|
+
}
|
|
542
|
+
const envelope = readPluginConfigEnvelopeV5(parsed.value, options.scope);
|
|
543
|
+
if (!envelope
|
|
544
|
+
|| envelope.sourceId !== options.sourceId
|
|
545
|
+
|| envelope.pluginName !== options.pluginName) {
|
|
546
|
+
throw new Error('schemaVersionUnsupported: invalid namespaced plugin config store.');
|
|
547
|
+
}
|
|
548
|
+
const store = validateStoreValues({ filePath, values: envelope.values });
|
|
549
|
+
assertReadyPluginConfigStore(store);
|
|
531
550
|
return {
|
|
532
|
-
|
|
533
|
-
store
|
|
551
|
+
binding: envelope.binding,
|
|
552
|
+
store,
|
|
534
553
|
};
|
|
535
554
|
}
|
|
536
555
|
async function listConfigStoreDirectoryPluginNames(storeRoot) {
|
|
@@ -561,15 +580,72 @@ export async function listPluginConfigStorePluginNames(options) {
|
|
|
561
580
|
const storeRoots = options.scope === 'user'
|
|
562
581
|
? [path.join(resolveAwHome(options.env), CONFIG_DIR)]
|
|
563
582
|
: options.scope === 'project'
|
|
564
|
-
? [path.join(projectRoot, '.agent-workflows',
|
|
583
|
+
? [path.join(projectRoot, '.agent-workflows', CONFIG_DIR)]
|
|
565
584
|
: [
|
|
566
585
|
path.join(resolveAwHome(options.env), CONFIG_DIR),
|
|
567
586
|
path.join(projectRoot, '.agent-workflows', CONFIG_DIR),
|
|
568
|
-
path.join(projectRoot, '.agent-workflows', 'plugin-config.local'),
|
|
569
587
|
];
|
|
570
588
|
const pluginNames = (await Promise.all(storeRoots.map((storeRoot) => listConfigStoreDirectoryPluginNames(storeRoot)))).flat();
|
|
571
589
|
return [...new Set(pluginNames)].sort((left, right) => left.localeCompare(right));
|
|
572
590
|
}
|
|
591
|
+
export async function listNamespacedPluginConfigStoreIdentities(options) {
|
|
592
|
+
const boundaryRoot = options.scope === 'user'
|
|
593
|
+
? resolveAwHome(options.env)
|
|
594
|
+
: resolveAwProjectRoot(options.cwd);
|
|
595
|
+
if (options.scope === 'user') {
|
|
596
|
+
await canonicalizePathThroughExistingAncestor(boundaryRoot);
|
|
597
|
+
}
|
|
598
|
+
else {
|
|
599
|
+
await fs.realpath(boundaryRoot);
|
|
600
|
+
}
|
|
601
|
+
const storeRoot = path.join(boundaryRoot, ...(options.scope === 'project' ? ['.agent-workflows'] : []), CONFIG_DIR);
|
|
602
|
+
if (!await inspectConfigInventoryStoreRoot(boundaryRoot, storeRoot))
|
|
603
|
+
return [];
|
|
604
|
+
const output = [];
|
|
605
|
+
for (const sourceEntry of await fs.readdir(storeRoot, { withFileTypes: true })) {
|
|
606
|
+
if (!sourceEntry.isDirectory() || sourceEntry.isSymbolicLink())
|
|
607
|
+
continue;
|
|
608
|
+
let sourceId;
|
|
609
|
+
try {
|
|
610
|
+
sourceId = assertSafePluginConfigSegment(sourceEntry.name);
|
|
611
|
+
}
|
|
612
|
+
catch {
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
const sourceRoot = path.join(storeRoot, sourceId);
|
|
616
|
+
let pluginEntries;
|
|
617
|
+
try {
|
|
618
|
+
await assertNoConfigSymlink(boundaryRoot, sourceRoot);
|
|
619
|
+
pluginEntries = await fs.readdir(sourceRoot, { withFileTypes: true });
|
|
620
|
+
}
|
|
621
|
+
catch {
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
for (const pluginEntry of pluginEntries) {
|
|
625
|
+
if (!pluginEntry.isDirectory() || pluginEntry.isSymbolicLink())
|
|
626
|
+
continue;
|
|
627
|
+
let pluginName;
|
|
628
|
+
try {
|
|
629
|
+
pluginName = assertSafePluginConfigSegment(pluginEntry.name);
|
|
630
|
+
await assertNoConfigSymlink(boundaryRoot, path.join(sourceRoot, pluginName));
|
|
631
|
+
}
|
|
632
|
+
catch {
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
let target;
|
|
636
|
+
try {
|
|
637
|
+
target = await lstatIfExists(path.join(sourceRoot, pluginName, CONFIG_FILE));
|
|
638
|
+
}
|
|
639
|
+
catch {
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
if (!target)
|
|
643
|
+
continue;
|
|
644
|
+
output.push({ sourceId, pluginName, pluginId: `${sourceId}/${pluginName}` });
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return output.sort((left, right) => left.pluginId.localeCompare(right.pluginId));
|
|
648
|
+
}
|
|
573
649
|
export async function listNamespacedPluginConfigStores(options) {
|
|
574
650
|
const boundaryRoot = options.scope === 'user'
|
|
575
651
|
? resolveAwHome(options.env)
|
|
@@ -580,9 +656,7 @@ export async function listNamespacedPluginConfigStores(options) {
|
|
|
580
656
|
else {
|
|
581
657
|
await fs.realpath(boundaryRoot);
|
|
582
658
|
}
|
|
583
|
-
const storeRoot = path.join(boundaryRoot, ...(options.scope === 'project' ? ['.agent-workflows'] : []),
|
|
584
|
-
? 'plugin-config.local'
|
|
585
|
-
: CONFIG_DIR);
|
|
659
|
+
const storeRoot = path.join(boundaryRoot, ...(options.scope === 'project' ? ['.agent-workflows'] : []), CONFIG_DIR);
|
|
586
660
|
const output = [];
|
|
587
661
|
if (!await inspectConfigInventoryStoreRoot(boundaryRoot, storeRoot))
|
|
588
662
|
return output;
|
|
@@ -604,18 +678,24 @@ export async function listNamespacedPluginConfigStores(options) {
|
|
|
604
678
|
const filePath = path.join(storeRoot, sourceId, pluginName, CONFIG_FILE);
|
|
605
679
|
if (!await inspectConfigReadTarget(storeRoot, filePath))
|
|
606
680
|
continue;
|
|
607
|
-
const
|
|
608
|
-
if (
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
681
|
+
const parsed = await parseStoreJson(filePath);
|
|
682
|
+
if (parsed.state === 'invalidJson') {
|
|
683
|
+
throw new Error(`${parsed.store.invalidCode}: invalid namespaced plugin config store "${filePath}".`);
|
|
684
|
+
}
|
|
685
|
+
const envelope = readPluginConfigEnvelopeV5(parsed.value, options.scope);
|
|
686
|
+
if (!envelope
|
|
687
|
+
|| envelope.sourceId !== sourceId
|
|
688
|
+
|| envelope.pluginName !== pluginName) {
|
|
689
|
+
throw new Error(`schemaVersionUnsupported: invalid namespaced plugin config store "${filePath}".`);
|
|
612
690
|
}
|
|
691
|
+
const store = validateStoreValues({ filePath, values: envelope.values });
|
|
692
|
+
assertReadyPluginConfigStore(store);
|
|
613
693
|
output.push({
|
|
614
694
|
sourceId,
|
|
615
|
-
|
|
695
|
+
binding: envelope.binding,
|
|
616
696
|
pluginName,
|
|
617
697
|
pluginId: `${sourceId}/${pluginName}`,
|
|
618
|
-
values:
|
|
698
|
+
values: store.values,
|
|
619
699
|
});
|
|
620
700
|
}
|
|
621
701
|
}
|
|
@@ -628,22 +708,50 @@ export async function writePluginConfigStore(options) {
|
|
|
628
708
|
...(options.scope === 'project' ? { access: 'write' } : {}),
|
|
629
709
|
});
|
|
630
710
|
const allowedRoot = resolvePluginConfigAllowedRoot(options);
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
711
|
+
const validated = validateStoreValues({ filePath, values: options.values });
|
|
712
|
+
assertReadyPluginConfigStore(validated);
|
|
713
|
+
const warnings = [];
|
|
714
|
+
await withConfigFileMutation({
|
|
715
|
+
filePath,
|
|
716
|
+
allowedRoot,
|
|
717
|
+
action: async () => {
|
|
718
|
+
if (!options.sourceId && Object.keys(validated.values).length === 0) {
|
|
719
|
+
return atomicRemoveConfigFile({ filePath, allowedRoot });
|
|
720
|
+
}
|
|
721
|
+
if (options.sourceId && await lstatIfExists(filePath)) {
|
|
722
|
+
const current = await readNamespacedStoreFile({
|
|
723
|
+
filePath,
|
|
724
|
+
sourceId: options.sourceId,
|
|
725
|
+
scope: options.scope,
|
|
726
|
+
sourceIdentity: options.sourceIdentity,
|
|
727
|
+
logicalIdentity: options.logicalIdentity,
|
|
728
|
+
pluginName: options.pluginName,
|
|
729
|
+
});
|
|
730
|
+
assertReadyPluginConfigStore(current);
|
|
731
|
+
}
|
|
732
|
+
if (options.scope === 'project') {
|
|
733
|
+
const visibility = await assertProjectSharedConfigPathVisible({
|
|
734
|
+
cwd: options.cwd,
|
|
735
|
+
targetPath: filePath,
|
|
736
|
+
env: options.env,
|
|
737
|
+
});
|
|
738
|
+
if (visibility?.state === 'unverified') {
|
|
739
|
+
warnings.push(PROJECT_CONFIG_SHAREABILITY_UNVERIFIED_WARNING);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return atomicReplaceConfigFile({
|
|
636
743
|
filePath,
|
|
637
744
|
allowedRoot,
|
|
638
|
-
content: namespacedConfigContent(options),
|
|
745
|
+
content: namespacedConfigContent({ ...options, values: validated.values }),
|
|
639
746
|
mode: 0o600,
|
|
640
|
-
})
|
|
641
|
-
}
|
|
747
|
+
});
|
|
748
|
+
},
|
|
642
749
|
});
|
|
643
750
|
return {
|
|
644
751
|
pluginName: options.pluginName,
|
|
645
752
|
scope: options.scope,
|
|
646
753
|
path: filePath,
|
|
754
|
+
warnings,
|
|
647
755
|
};
|
|
648
756
|
}
|
|
649
757
|
export async function patchPluginConfigStore(options) {
|
|
@@ -654,33 +762,50 @@ export async function patchPluginConfigStore(options) {
|
|
|
654
762
|
});
|
|
655
763
|
const allowedRoot = resolvePluginConfigAllowedRoot(options);
|
|
656
764
|
const removedKeys = options.removeKeys ?? [];
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
:
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
765
|
+
const warnings = [];
|
|
766
|
+
await withConfigFileMutation({
|
|
767
|
+
filePath,
|
|
768
|
+
allowedRoot,
|
|
769
|
+
action: async () => {
|
|
770
|
+
await options.reviewPreflight?.();
|
|
771
|
+
const current = (await lstatIfExists(filePath))
|
|
772
|
+
? options.sourceId
|
|
773
|
+
? await readNamespacedStoreFile({
|
|
774
|
+
filePath,
|
|
775
|
+
sourceId: options.sourceId,
|
|
776
|
+
scope: options.scope,
|
|
777
|
+
sourceIdentity: options.sourceIdentity,
|
|
778
|
+
logicalIdentity: options.logicalIdentity,
|
|
779
|
+
pluginName: options.pluginName,
|
|
780
|
+
})
|
|
781
|
+
: await readStoreFile(filePath)
|
|
782
|
+
: readyStore(filePath, {});
|
|
783
|
+
assertReadyPluginConfigStore(current);
|
|
784
|
+
if (options.requireExistingRemoveKeys) {
|
|
785
|
+
const missingKey = removedKeys.find((key) => !Object.hasOwn(current.values, key));
|
|
786
|
+
if (missingKey)
|
|
787
|
+
throw new Error(`Unknown config key "${missingKey}".`);
|
|
788
|
+
}
|
|
789
|
+
const next = { ...current.values };
|
|
790
|
+
for (const key of removedKeys) {
|
|
791
|
+
delete next[key];
|
|
792
|
+
}
|
|
793
|
+
for (const [key, value] of Object.entries(options.updates)) {
|
|
794
|
+
next[key] = value;
|
|
795
|
+
}
|
|
796
|
+
if (Object.keys(next).length === 0) {
|
|
797
|
+
await atomicRemoveConfigFile({ filePath, allowedRoot });
|
|
798
|
+
}
|
|
799
|
+
else {
|
|
800
|
+
if (options.scope === 'project') {
|
|
801
|
+
const visibility = await assertProjectSharedConfigPathVisible({
|
|
802
|
+
cwd: options.cwd,
|
|
803
|
+
targetPath: filePath,
|
|
804
|
+
env: options.env,
|
|
805
|
+
});
|
|
806
|
+
if (visibility?.state === 'unverified') {
|
|
807
|
+
warnings.push(PROJECT_CONFIG_SHAREABILITY_UNVERIFIED_WARNING);
|
|
808
|
+
}
|
|
684
809
|
}
|
|
685
810
|
await atomicReplaceConfigFile({
|
|
686
811
|
filePath,
|
|
@@ -688,65 +813,170 @@ export async function patchPluginConfigStore(options) {
|
|
|
688
813
|
content: namespacedConfigContent({ ...options, values: next }),
|
|
689
814
|
mode: 0o600,
|
|
690
815
|
});
|
|
691
|
-
}
|
|
692
|
-
}
|
|
816
|
+
}
|
|
817
|
+
},
|
|
693
818
|
});
|
|
694
819
|
return {
|
|
695
820
|
pluginName: options.pluginName,
|
|
696
821
|
scope: options.scope,
|
|
697
822
|
path: filePath,
|
|
823
|
+
warnings,
|
|
698
824
|
updatedKeys: Object.keys(options.updates),
|
|
699
825
|
removedKeys,
|
|
700
826
|
};
|
|
701
827
|
}
|
|
702
828
|
export async function resetPluginConfigStore(options) {
|
|
703
|
-
await assertNoLegacyConfigCoexistence(options);
|
|
704
829
|
const filePath = resolvePluginConfigStorePath({
|
|
705
830
|
...options,
|
|
706
831
|
...(options.scope === 'project' ? { access: 'write' } : {}),
|
|
707
832
|
});
|
|
708
833
|
const allowedRoot = resolvePluginConfigAllowedRoot(options);
|
|
709
|
-
const
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
834
|
+
const publicTarget = `${options.scope}/plugin-config/${[
|
|
835
|
+
options.sourceId,
|
|
836
|
+
options.pluginName,
|
|
837
|
+
CONFIG_FILE,
|
|
838
|
+
].filter(Boolean).join('/')}`;
|
|
839
|
+
const { root, candidate } = assertConfigPathBoundary(allowedRoot, filePath);
|
|
840
|
+
let current = root;
|
|
841
|
+
try {
|
|
842
|
+
for (const segment of path.relative(root, path.dirname(candidate)).split(path.sep).filter(Boolean)) {
|
|
843
|
+
const stats = await lstatIfExists(current);
|
|
844
|
+
if (!stats) {
|
|
845
|
+
return {
|
|
846
|
+
pluginName: options.pluginName,
|
|
847
|
+
scope: options.scope,
|
|
848
|
+
storeKind: options.scope,
|
|
849
|
+
path: filePath,
|
|
850
|
+
existed: false,
|
|
851
|
+
deleted: false,
|
|
852
|
+
resetDisposition: 'absent',
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
if (stats.isSymbolicLink() || !stats.isDirectory()) {
|
|
856
|
+
throw new PluginConfigResetError('configResetTargetUnsupported', `${publicTarget} has an unsafe ancestor. Repair it manually, then retry config reset.`);
|
|
857
|
+
}
|
|
858
|
+
current = path.join(current, segment);
|
|
859
|
+
}
|
|
860
|
+
const parentStats = await lstatIfExists(current);
|
|
861
|
+
if (!parentStats) {
|
|
862
|
+
return {
|
|
863
|
+
pluginName: options.pluginName,
|
|
864
|
+
scope: options.scope,
|
|
865
|
+
storeKind: options.scope,
|
|
866
|
+
path: filePath,
|
|
867
|
+
existed: false,
|
|
868
|
+
deleted: false,
|
|
869
|
+
resetDisposition: 'absent',
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
if (parentStats.isSymbolicLink() || !parentStats.isDirectory()) {
|
|
873
|
+
throw new PluginConfigResetError('configResetTargetUnsupported', `${publicTarget} has an unsafe ancestor. Repair it manually, then retry config reset.`);
|
|
874
|
+
}
|
|
875
|
+
await options.reviewPreflight?.();
|
|
876
|
+
const target = await lstatIfExists(filePath);
|
|
877
|
+
if (!target) {
|
|
878
|
+
return {
|
|
879
|
+
pluginName: options.pluginName,
|
|
880
|
+
scope: options.scope,
|
|
881
|
+
storeKind: options.scope,
|
|
882
|
+
path: filePath,
|
|
883
|
+
existed: false,
|
|
884
|
+
deleted: false,
|
|
885
|
+
resetDisposition: 'absent',
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
if (target.isFile() || target.isSymbolicLink()) {
|
|
889
|
+
await assertNoConfigSymlink(allowedRoot, path.dirname(filePath));
|
|
890
|
+
await nodeFs.unlink(filePath);
|
|
891
|
+
return {
|
|
892
|
+
pluginName: options.pluginName,
|
|
893
|
+
scope: options.scope,
|
|
894
|
+
storeKind: options.scope,
|
|
895
|
+
path: filePath,
|
|
896
|
+
existed: true,
|
|
897
|
+
deleted: true,
|
|
898
|
+
resetDisposition: target.isSymbolicLink() ? 'unlinkedSymlink' : 'deletedFile',
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
if (target.isDirectory()) {
|
|
902
|
+
const timestamp = new Date().toISOString().replace(/[-:]/gu, '').replace(/\..*$/u, '');
|
|
903
|
+
const quarantineBasename = `config.broken-${timestamp}-${crypto.randomUUID()}.json`;
|
|
904
|
+
await assertNoConfigSymlink(allowedRoot, path.dirname(filePath));
|
|
905
|
+
await nodeFs.rename(filePath, path.join(path.dirname(filePath), quarantineBasename));
|
|
906
|
+
return {
|
|
907
|
+
pluginName: options.pluginName,
|
|
908
|
+
scope: options.scope,
|
|
909
|
+
storeKind: options.scope,
|
|
910
|
+
path: filePath,
|
|
911
|
+
existed: true,
|
|
912
|
+
deleted: true,
|
|
913
|
+
resetDisposition: 'quarantinedDirectory',
|
|
914
|
+
quarantineBasename,
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
throw new PluginConfigResetError('configResetTargetUnsupported', `${publicTarget} is a special filesystem node. Move it manually, then retry config reset.`);
|
|
918
|
+
}
|
|
919
|
+
catch (error) {
|
|
920
|
+
if (error instanceof PluginConfigResetError)
|
|
921
|
+
throw error;
|
|
922
|
+
const osCode = error.code;
|
|
923
|
+
throw new PluginConfigResetError('configResetFailed', `Cannot reset ${publicTarget}${osCode ? ` (${osCode})` : ''}. Fix filesystem access, then retry config reset.`, osCode);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
export async function inspectPluginConfigResetTarget(options) {
|
|
927
|
+
const filePath = resolvePluginConfigStorePath({
|
|
928
|
+
...options,
|
|
929
|
+
...(options.scope === 'project' ? { access: 'write' } : {}),
|
|
738
930
|
});
|
|
931
|
+
const allowedRoot = resolvePluginConfigAllowedRoot(options);
|
|
932
|
+
const publicTarget = `${options.scope}/plugin-config/${[
|
|
933
|
+
options.sourceId,
|
|
934
|
+
options.pluginName,
|
|
935
|
+
CONFIG_FILE,
|
|
936
|
+
].filter(Boolean).join('/')}`;
|
|
937
|
+
const { root, candidate } = assertConfigPathBoundary(allowedRoot, filePath);
|
|
938
|
+
let current = root;
|
|
939
|
+
for (const segment of path.relative(root, path.dirname(candidate)).split(path.sep).filter(Boolean)) {
|
|
940
|
+
const stats = await lstatIfExists(current);
|
|
941
|
+
if (!stats)
|
|
942
|
+
return resetTargetInspection('absent');
|
|
943
|
+
if (stats.isSymbolicLink() || !stats.isDirectory()) {
|
|
944
|
+
throw new PluginConfigResetError('configResetTargetUnsupported', `${publicTarget} has an unsafe ancestor. Repair it manually, then retry config reset.`);
|
|
945
|
+
}
|
|
946
|
+
current = path.join(current, segment);
|
|
947
|
+
}
|
|
948
|
+
const parentStats = await lstatIfExists(current);
|
|
949
|
+
if (!parentStats)
|
|
950
|
+
return resetTargetInspection('absent');
|
|
951
|
+
if (parentStats.isSymbolicLink() || !parentStats.isDirectory()) {
|
|
952
|
+
throw new PluginConfigResetError('configResetTargetUnsupported', `${publicTarget} has an unsafe ancestor. Repair it manually, then retry config reset.`);
|
|
953
|
+
}
|
|
954
|
+
const target = await lstatIfExists(filePath);
|
|
955
|
+
if (!target)
|
|
956
|
+
return resetTargetInspection('absent');
|
|
957
|
+
const kind = target.isSymbolicLink() ? 'symbolicLink'
|
|
958
|
+
: target.isFile() ? 'regularFile'
|
|
959
|
+
: target.isDirectory() ? 'directory'
|
|
960
|
+
: 'special';
|
|
961
|
+
return resetTargetInspection(kind, target);
|
|
962
|
+
}
|
|
963
|
+
function resetTargetInspection(kind, stats) {
|
|
964
|
+
const fingerprint = `sha256:${crypto.createHash('sha256').update(JSON.stringify({
|
|
965
|
+
kind,
|
|
966
|
+
...(stats ? {
|
|
967
|
+
dev: stats.dev,
|
|
968
|
+
ino: stats.ino,
|
|
969
|
+
mode: stats.mode,
|
|
970
|
+
size: stats.size,
|
|
971
|
+
mtimeMs: stats.mtimeMs,
|
|
972
|
+
ctimeMs: stats.ctimeMs,
|
|
973
|
+
} : {}),
|
|
974
|
+
})).digest('hex')}`;
|
|
739
975
|
return {
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
: options.projectLayer === 'local'
|
|
745
|
-
? 'projectLocal'
|
|
746
|
-
: 'projectShared',
|
|
747
|
-
path: filePath,
|
|
748
|
-
existed,
|
|
749
|
-
deleted: existed,
|
|
976
|
+
exists: kind !== 'absent',
|
|
977
|
+
kind,
|
|
978
|
+
resettable: kind !== 'special',
|
|
979
|
+
fingerprint,
|
|
750
980
|
};
|
|
751
981
|
}
|
|
752
982
|
export async function resetPluginConfigStores(options) {
|
|
@@ -757,228 +987,167 @@ export async function resetPluginConfigStores(options) {
|
|
|
757
987
|
});
|
|
758
988
|
const allowedRoot = resolvePluginConfigAllowedRoot(options);
|
|
759
989
|
const receipts = [];
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
});
|
|
770
|
-
};
|
|
771
|
-
await withTargetLocks(0, async () => {
|
|
772
|
-
const existing = [];
|
|
773
|
-
for (const target of targets) {
|
|
774
|
-
const stats = await lstatIfExists(target.path);
|
|
775
|
-
existing.push(stats !== null);
|
|
776
|
-
if (!stats)
|
|
777
|
-
continue;
|
|
778
|
-
if (!stats.isFile() || stats.isSymbolicLink()) {
|
|
779
|
-
throw new Error(`Config target must be a regular file: ${target.path}`);
|
|
780
|
-
}
|
|
781
|
-
if (options.sourceId) {
|
|
782
|
-
if (!options.sourceIdentity) {
|
|
783
|
-
throw new Error('sourceIdentity is required for namespaced plugin config.');
|
|
784
|
-
}
|
|
785
|
-
await readNamespacedStoreFile({
|
|
786
|
-
filePath: target.path,
|
|
787
|
-
sourceId: options.sourceId,
|
|
788
|
-
sourceIdentity: options.sourceIdentity,
|
|
789
|
-
pluginName: options.pluginName,
|
|
790
|
-
});
|
|
791
|
-
}
|
|
792
|
-
else {
|
|
793
|
-
await readStoreFile(target.path);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
for (const [index, target] of targets.entries()) {
|
|
797
|
-
const deleted = await atomicRemoveConfigFile({
|
|
798
|
-
filePath: target.path,
|
|
799
|
-
allowedRoot,
|
|
800
|
-
});
|
|
801
|
-
receipts.push({
|
|
802
|
-
pluginName: options.pluginName,
|
|
803
|
-
scope: options.scope,
|
|
804
|
-
storeKind: target.storeKind,
|
|
805
|
-
path: target.path,
|
|
806
|
-
existed: existing[index],
|
|
807
|
-
deleted,
|
|
808
|
-
});
|
|
809
|
-
}
|
|
810
|
-
});
|
|
811
|
-
});
|
|
812
|
-
return receipts;
|
|
813
|
-
}
|
|
814
|
-
function coercePrimitiveValue(key, field, value) {
|
|
815
|
-
if (field.type === 'string') {
|
|
816
|
-
if (typeof value !== 'string') {
|
|
817
|
-
throw new Error(`Config "${key}" must be a string.`);
|
|
818
|
-
}
|
|
819
|
-
return value;
|
|
820
|
-
}
|
|
821
|
-
if (field.type === 'number') {
|
|
822
|
-
const numberValue = typeof value === 'number'
|
|
823
|
-
? value
|
|
824
|
-
: typeof value === 'string' && value.trim() !== ''
|
|
825
|
-
? Number(value)
|
|
826
|
-
: Number.NaN;
|
|
827
|
-
if (!Number.isFinite(numberValue)) {
|
|
828
|
-
throw new Error(`Config "${key}" must be a number.`);
|
|
829
|
-
}
|
|
830
|
-
if (field.min !== undefined && numberValue < field.min) {
|
|
831
|
-
throw new Error(`Config "${key}" must be greater than or equal to ${field.min}.`);
|
|
832
|
-
}
|
|
833
|
-
if (field.max !== undefined && numberValue > field.max) {
|
|
834
|
-
throw new Error(`Config "${key}" must be less than or equal to ${field.max}.`);
|
|
835
|
-
}
|
|
836
|
-
return numberValue;
|
|
837
|
-
}
|
|
838
|
-
if (field.type === 'boolean') {
|
|
839
|
-
if (typeof value === 'boolean') {
|
|
840
|
-
return value;
|
|
841
|
-
}
|
|
842
|
-
if (typeof value === 'string') {
|
|
843
|
-
const normalized = value.trim().toLowerCase();
|
|
844
|
-
if (['true', '1', 'yes', 'y'].includes(normalized)) {
|
|
845
|
-
return true;
|
|
846
|
-
}
|
|
847
|
-
if (['false', '0', 'no', 'n'].includes(normalized)) {
|
|
848
|
-
return false;
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
throw new Error(`Config "${key}" must be a boolean.`);
|
|
852
|
-
}
|
|
853
|
-
if (field.type === 'enum') {
|
|
854
|
-
if (typeof value !== 'string') {
|
|
855
|
-
throw new Error(`Config "${key}" must be an enum string.`);
|
|
856
|
-
}
|
|
857
|
-
if (!field.values?.includes(value)) {
|
|
858
|
-
throw new Error(`Config "${key}" must be one of: ${(field.values ?? []).join(', ')}.`);
|
|
990
|
+
const existing = [];
|
|
991
|
+
for (const target of targets) {
|
|
992
|
+
await prepareConfigFileParent(target.path, allowedRoot);
|
|
993
|
+
await assertNoConfigSymlink(allowedRoot, target.path);
|
|
994
|
+
const stats = await lstatIfExists(target.path);
|
|
995
|
+
existing.push(stats !== null);
|
|
996
|
+
if (!stats)
|
|
997
|
+
continue;
|
|
998
|
+
if (!stats.isFile() || stats.isSymbolicLink()) {
|
|
999
|
+
throw new Error(`Config target must be a regular file: ${target.path}`);
|
|
859
1000
|
}
|
|
860
|
-
return value;
|
|
861
1001
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
1002
|
+
for (const [index, target] of targets.entries()) {
|
|
1003
|
+
const deleted = await atomicRemoveConfigFile({
|
|
1004
|
+
filePath: target.path,
|
|
1005
|
+
allowedRoot,
|
|
1006
|
+
});
|
|
1007
|
+
receipts.push({
|
|
1008
|
+
pluginName: options.pluginName,
|
|
1009
|
+
scope: options.scope,
|
|
1010
|
+
storeKind: target.storeKind,
|
|
1011
|
+
path: target.path,
|
|
1012
|
+
existed: existing[index],
|
|
1013
|
+
deleted,
|
|
1014
|
+
resetDisposition: deleted ? 'deletedFile' : 'absent',
|
|
1015
|
+
});
|
|
867
1016
|
}
|
|
868
|
-
|
|
1017
|
+
return receipts;
|
|
869
1018
|
}
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1019
|
+
function coerceConfigValue(key, field, value) {
|
|
1020
|
+
let coerced = value;
|
|
1021
|
+
if (field.type === 'number' && typeof value === 'string' && value.trim() !== '') {
|
|
1022
|
+
coerced = Number(value);
|
|
873
1023
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
if (options.field.type === 'file' && !stats.isFile()) {
|
|
881
|
-
throw new Error(`Config "${options.key}" must point to an existing file.`);
|
|
1024
|
+
else if (field.type === 'boolean' && typeof value === 'string') {
|
|
1025
|
+
const normalized = value.trim().toLowerCase();
|
|
1026
|
+
if (['true', '1', 'yes', 'y'].includes(normalized))
|
|
1027
|
+
coerced = true;
|
|
1028
|
+
if (['false', '0', 'no', 'n'].includes(normalized))
|
|
1029
|
+
coerced = false;
|
|
882
1030
|
}
|
|
883
|
-
|
|
884
|
-
|
|
1031
|
+
const issues = validateCorePluginConfigValue(field, coerced, key);
|
|
1032
|
+
if (issues.length > 0) {
|
|
1033
|
+
throw new Error(formatPluginConfigIssue(issues[0]));
|
|
885
1034
|
}
|
|
886
|
-
return
|
|
1035
|
+
return coerced;
|
|
887
1036
|
}
|
|
888
1037
|
export async function coercePluginConfigValue(options) {
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
projectRoot: options.projectRoot,
|
|
896
|
-
});
|
|
1038
|
+
return coerceConfigValue(options.key, options.field, options.value);
|
|
1039
|
+
}
|
|
1040
|
+
function validateConfigReadValue(key, field, value) {
|
|
1041
|
+
const issues = validateCorePluginConfigValue(field, value, key);
|
|
1042
|
+
if (issues.length > 0) {
|
|
1043
|
+
throw new Error(formatPluginConfigIssue(issues[0]));
|
|
897
1044
|
}
|
|
898
|
-
return
|
|
1045
|
+
return value;
|
|
899
1046
|
}
|
|
900
1047
|
function redactValue(field, value) {
|
|
901
|
-
return field
|
|
902
|
-
}
|
|
903
|
-
function sourceForStoreLayer(scope, projectLayer = 'shared') {
|
|
904
|
-
if (scope === 'user') {
|
|
905
|
-
return 'user';
|
|
906
|
-
}
|
|
907
|
-
return projectLayer === 'local' ? 'projectLocal' : 'project';
|
|
1048
|
+
return redactPluginConfigValue(field, value);
|
|
908
1049
|
}
|
|
909
|
-
function
|
|
910
|
-
return
|
|
1050
|
+
function sourceForStoreLayer(scope) {
|
|
1051
|
+
return scope === 'user' ? 'user' : 'project';
|
|
911
1052
|
}
|
|
912
1053
|
async function applyConfigLayer(options) {
|
|
913
1054
|
for (const [key, field] of Object.entries(options.schema)) {
|
|
914
1055
|
if (!Object.hasOwn(options.entries, key)) {
|
|
915
1056
|
continue;
|
|
916
1057
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
});
|
|
924
|
-
options.values[key] = coerced;
|
|
925
|
-
options.redactedValues[key] = redactValue(field, coerced);
|
|
1058
|
+
const value = options.entries[key];
|
|
1059
|
+
const validationIssues = validateCorePluginConfigValue(field, value, key);
|
|
1060
|
+
if (validationIssues.length === 0) {
|
|
1061
|
+
const redactedValue = redactValue(field, value);
|
|
1062
|
+
options.values[key] = value;
|
|
1063
|
+
options.redactedValues[key] = redactedValue;
|
|
926
1064
|
options.sources[key] = options.source;
|
|
927
|
-
options.
|
|
1065
|
+
options.fieldStates[key] = {
|
|
1066
|
+
effective: { value, redactedValue, source: options.source },
|
|
1067
|
+
};
|
|
928
1068
|
}
|
|
929
|
-
|
|
1069
|
+
else {
|
|
1070
|
+
const fallback = options.fieldStates[key]?.effective;
|
|
930
1071
|
delete options.values[key];
|
|
931
1072
|
delete options.redactedValues[key];
|
|
932
1073
|
options.sources[key] = options.source;
|
|
933
|
-
|
|
1074
|
+
const issues = validationIssues.map((issue) => ({
|
|
934
1075
|
type: 'invalid-config',
|
|
935
1076
|
key,
|
|
936
1077
|
source: options.source,
|
|
937
|
-
message:
|
|
938
|
-
});
|
|
1078
|
+
message: formatPluginConfigIssue(issue),
|
|
1079
|
+
}));
|
|
1080
|
+
options.fieldStates[key] = {
|
|
1081
|
+
invalidOverride: { source: options.source, issues },
|
|
1082
|
+
...(fallback ? { fallback } : {}),
|
|
1083
|
+
};
|
|
939
1084
|
}
|
|
940
1085
|
}
|
|
941
1086
|
}
|
|
942
1087
|
export async function resolveEffectivePluginConfig(options) {
|
|
943
1088
|
const pluginName = assertSafePluginConfigSegment(options.pluginName ?? options.manifest.name);
|
|
944
|
-
const schema = options.manifest.config?.
|
|
1089
|
+
const schema = options.manifest.config?.fields;
|
|
945
1090
|
if (!schema) {
|
|
946
1091
|
throw new Error(`Plugin "${pluginName}" does not declare an AW config schema.`);
|
|
947
1092
|
}
|
|
948
1093
|
const projectRoot = resolveAwProjectRoot(options.cwd);
|
|
949
|
-
const
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
pluginName,
|
|
962
|
-
scope: 'project',
|
|
963
|
-
cwd: options.cwd,
|
|
964
|
-
env: options.env,
|
|
965
|
-
}),
|
|
966
|
-
readPluginConfigStore({
|
|
1094
|
+
const userStore = await readPluginConfigStore({
|
|
1095
|
+
sourceId: options.sourceId,
|
|
1096
|
+
sourceIdentity: options.sourceIdentity,
|
|
1097
|
+
logicalIdentity: options.logicalIdentity,
|
|
1098
|
+
pluginName,
|
|
1099
|
+
scope: 'user',
|
|
1100
|
+
cwd: options.cwd,
|
|
1101
|
+
env: options.env,
|
|
1102
|
+
});
|
|
1103
|
+
const projectStore = options.targetScope === 'user'
|
|
1104
|
+
? undefined
|
|
1105
|
+
: await readPluginConfigStore({
|
|
967
1106
|
sourceId: options.sourceId,
|
|
968
1107
|
sourceIdentity: options.sourceIdentity,
|
|
1108
|
+
logicalIdentity: options.logicalIdentity,
|
|
969
1109
|
pluginName,
|
|
970
1110
|
scope: 'project',
|
|
971
1111
|
cwd: options.cwd,
|
|
972
1112
|
env: options.env,
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1113
|
+
});
|
|
1114
|
+
assertReadyPluginConfigStore(userStore);
|
|
1115
|
+
if (projectStore)
|
|
1116
|
+
assertReadyPluginConfigStore(projectStore);
|
|
1117
|
+
return resolveEffectivePluginConfigFromStores({
|
|
1118
|
+
pluginName,
|
|
1119
|
+
manifest: options.manifest,
|
|
1120
|
+
targetScope: options.targetScope,
|
|
1121
|
+
projectRoot,
|
|
1122
|
+
stores: {
|
|
1123
|
+
user: userStore,
|
|
1124
|
+
...(projectStore ? { project: projectStore } : {}),
|
|
1125
|
+
},
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
export async function resolveEffectivePluginConfigFromStores(options) {
|
|
1129
|
+
const pluginName = assertSafePluginConfigSegment(options.pluginName ?? options.manifest.name);
|
|
1130
|
+
const schema = options.manifest.config?.fields;
|
|
1131
|
+
if (!schema) {
|
|
1132
|
+
throw new Error(`Plugin "${pluginName}" does not declare an AW config schema.`);
|
|
1133
|
+
}
|
|
1134
|
+
const userStore = options.stores.user;
|
|
1135
|
+
const projectStore = options.targetScope === 'user' ? undefined : options.stores.project;
|
|
1136
|
+
assertReadyPluginConfigStore(userStore);
|
|
1137
|
+
if (!projectStore && options.targetScope !== 'user') {
|
|
1138
|
+
throw new Error(`Project plugin config store is required for ${pluginName}.`);
|
|
1139
|
+
}
|
|
1140
|
+
if (projectStore)
|
|
1141
|
+
assertReadyPluginConfigStore(projectStore);
|
|
976
1142
|
const values = {};
|
|
977
1143
|
const redactedValues = {};
|
|
978
1144
|
const sources = {};
|
|
979
|
-
const
|
|
1145
|
+
const fieldStates = {};
|
|
980
1146
|
const ignoredUnknownKeys = [];
|
|
1147
|
+
const unsupportedLayerOverrides = [];
|
|
1148
|
+
const dangerousKeyIssues = [];
|
|
981
1149
|
const schemaKeys = new Set(Object.keys(schema));
|
|
1150
|
+
const userOnlyKeys = codexNativeRefreshKeys(options.manifest);
|
|
982
1151
|
for (const key of schemaKeys) {
|
|
983
1152
|
sources[key] = 'missing';
|
|
984
1153
|
}
|
|
@@ -990,43 +1159,131 @@ export async function resolveEffectivePluginConfig(options) {
|
|
|
990
1159
|
values,
|
|
991
1160
|
redactedValues,
|
|
992
1161
|
sources,
|
|
993
|
-
|
|
1162
|
+
fieldStates,
|
|
994
1163
|
schema: { [key]: field },
|
|
995
1164
|
entries: { [key]: field.default },
|
|
996
1165
|
source: 'default',
|
|
997
|
-
projectRoot,
|
|
998
1166
|
});
|
|
999
1167
|
}
|
|
1000
|
-
|
|
1001
|
-
['user', userStore
|
|
1002
|
-
[
|
|
1003
|
-
|
|
1004
|
-
|
|
1168
|
+
const layers = options.targetScope === 'user'
|
|
1169
|
+
? [['user', userStore]]
|
|
1170
|
+
: [
|
|
1171
|
+
['user', userStore],
|
|
1172
|
+
['project', projectStore],
|
|
1173
|
+
];
|
|
1174
|
+
for (const [scope, store] of layers) {
|
|
1005
1175
|
const plainEntries = getStoreEntries(store.values);
|
|
1176
|
+
const source = sourceForStoreLayer(scope);
|
|
1177
|
+
const dangerousIssues = new Map(validatePluginConfigDocument(options.manifest.config, plainEntries).filter((issue) => issue.code === 'dangerousKey')
|
|
1178
|
+
.map((issue) => [issue.path.field, issue]));
|
|
1006
1179
|
for (const key of Object.keys(plainEntries)) {
|
|
1007
1180
|
if (!schemaKeys.has(key)) {
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1181
|
+
const dangerousIssue = dangerousIssues.get(key);
|
|
1182
|
+
if (dangerousIssue) {
|
|
1183
|
+
dangerousKeyIssues.push({
|
|
1184
|
+
type: 'invalid-config',
|
|
1185
|
+
code: dangerousIssue.code,
|
|
1186
|
+
key,
|
|
1187
|
+
source,
|
|
1188
|
+
message: formatPluginConfigIssue(dangerousIssue),
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
else {
|
|
1192
|
+
ignoredUnknownKeys.push({ scope, source, key });
|
|
1193
|
+
}
|
|
1014
1194
|
}
|
|
1015
1195
|
}
|
|
1196
|
+
const supportedEntries = scope === 'project'
|
|
1197
|
+
? Object.fromEntries(Object.entries(plainEntries).filter(([key]) => {
|
|
1198
|
+
if (!schemaKeys.has(key) || !userOnlyKeys.has(key))
|
|
1199
|
+
return true;
|
|
1200
|
+
unsupportedLayerOverrides.push({
|
|
1201
|
+
type: 'configLayerUnsupported',
|
|
1202
|
+
key,
|
|
1203
|
+
scope: 'project',
|
|
1204
|
+
source: 'project',
|
|
1205
|
+
});
|
|
1206
|
+
return false;
|
|
1207
|
+
}))
|
|
1208
|
+
: plainEntries;
|
|
1016
1209
|
await applyConfigLayer({
|
|
1017
1210
|
values,
|
|
1018
1211
|
redactedValues,
|
|
1019
1212
|
sources,
|
|
1020
|
-
|
|
1213
|
+
fieldStates,
|
|
1021
1214
|
schema,
|
|
1022
|
-
entries:
|
|
1023
|
-
source
|
|
1024
|
-
projectRoot,
|
|
1215
|
+
entries: supportedEntries,
|
|
1216
|
+
source,
|
|
1025
1217
|
});
|
|
1026
1218
|
}
|
|
1027
|
-
const
|
|
1219
|
+
const candidateValues = {};
|
|
1220
|
+
for (const [key, field] of Object.entries(schema)) {
|
|
1221
|
+
const source = sources[key];
|
|
1222
|
+
if (source === 'default')
|
|
1223
|
+
candidateValues[key] = field.default;
|
|
1224
|
+
if (source === 'user')
|
|
1225
|
+
candidateValues[key] = ownStoreValue(userStore.values, key);
|
|
1226
|
+
if (source === 'project' && projectStore)
|
|
1227
|
+
candidateValues[key] = ownStoreValue(projectStore.values, key);
|
|
1228
|
+
}
|
|
1229
|
+
const referenceIssues = validatePluginConfigCandidate(options.manifest.config, candidateValues).filter((issue) => issue.code === 'valueRef' || issue.code === 'valueRefSource' || issue.code === 'valueRefReserved');
|
|
1230
|
+
const missingReferenceIssues = [];
|
|
1231
|
+
for (const issue of referenceIssues) {
|
|
1232
|
+
const key = issue.path.field;
|
|
1233
|
+
const source = sources[key];
|
|
1234
|
+
const state = fieldStates[key];
|
|
1235
|
+
const configIssue = {
|
|
1236
|
+
type: 'invalid-config', code: issue.code, key, source,
|
|
1237
|
+
message: formatPluginConfigIssue(issue),
|
|
1238
|
+
};
|
|
1239
|
+
if (source === 'missing') {
|
|
1240
|
+
missingReferenceIssues.push(configIssue);
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
delete values[key];
|
|
1244
|
+
delete redactedValues[key];
|
|
1245
|
+
fieldStates[key] = {
|
|
1246
|
+
invalidOverride: { source, issues: [...(state?.invalidOverride?.issues ?? []), configIssue] },
|
|
1247
|
+
...(state?.fallback ? { fallback: state.fallback } : {}),
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
// Consumers must retain dependency failures after invalid source values are omitted.
|
|
1251
|
+
const invalidKeys = new Set([
|
|
1252
|
+
...Object.entries(fieldStates).filter(([, state]) => state.invalidOverride !== undefined).map(([key]) => key),
|
|
1253
|
+
...missingReferenceIssues.map((issue) => issue.key),
|
|
1254
|
+
]);
|
|
1255
|
+
for (const key of schemaKeys) {
|
|
1256
|
+
if (invalidKeys.has(key))
|
|
1257
|
+
continue;
|
|
1258
|
+
const dependency = getPluginConfigDependencyKeys(options.manifest.config, [key])
|
|
1259
|
+
.find((dependencyKey) => dependencyKey !== key && invalidKeys.has(dependencyKey));
|
|
1260
|
+
if (dependency === undefined)
|
|
1261
|
+
continue;
|
|
1262
|
+
const source = sources[key];
|
|
1263
|
+
const configIssue = {
|
|
1264
|
+
type: 'invalid-config', code: 'valueRefSource', key, source,
|
|
1265
|
+
message: `Config "${key}" depends on invalid config "${dependency}". Fix the dependency before using this field.`,
|
|
1266
|
+
};
|
|
1267
|
+
delete values[key];
|
|
1268
|
+
delete redactedValues[key];
|
|
1269
|
+
if (source === 'missing') {
|
|
1270
|
+
missingReferenceIssues.push(configIssue);
|
|
1271
|
+
}
|
|
1272
|
+
else {
|
|
1273
|
+
const state = fieldStates[key];
|
|
1274
|
+
fieldStates[key] = {
|
|
1275
|
+
invalidOverride: { source, issues: [configIssue] },
|
|
1276
|
+
...(state?.fallback ? { fallback: state.fallback } : {}),
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
const issues = [
|
|
1281
|
+
...Object.values(fieldStates).flatMap((state) => state.invalidOverride?.issues ?? []),
|
|
1282
|
+
...dangerousKeyIssues,
|
|
1283
|
+
...missingReferenceIssues,
|
|
1284
|
+
];
|
|
1028
1285
|
for (const [key, field] of Object.entries(schema)) {
|
|
1029
|
-
if (field.required && values[key] === undefined) {
|
|
1286
|
+
if (field.required && values[key] === undefined && !fieldStates[key]?.invalidOverride) {
|
|
1030
1287
|
issues.push({
|
|
1031
1288
|
type: 'missing-required',
|
|
1032
1289
|
key,
|
|
@@ -1037,314 +1294,135 @@ export async function resolveEffectivePluginConfig(options) {
|
|
|
1037
1294
|
}
|
|
1038
1295
|
return {
|
|
1039
1296
|
pluginName,
|
|
1040
|
-
projectRoot,
|
|
1297
|
+
projectRoot: options.projectRoot,
|
|
1041
1298
|
values,
|
|
1042
1299
|
redactedValues,
|
|
1043
1300
|
sources,
|
|
1301
|
+
fieldStates,
|
|
1044
1302
|
issues,
|
|
1303
|
+
unsupportedLayerOverrides,
|
|
1045
1304
|
ignoredUnknownKeys,
|
|
1046
1305
|
schema,
|
|
1047
1306
|
stores: {
|
|
1048
1307
|
user: userStore,
|
|
1049
|
-
project: projectStore,
|
|
1050
|
-
projectLocal: projectLocalStore,
|
|
1308
|
+
...(projectStore ? { project: projectStore } : {}),
|
|
1051
1309
|
},
|
|
1052
1310
|
};
|
|
1053
1311
|
}
|
|
1312
|
+
function ownStoreValue(values, key) {
|
|
1313
|
+
return Object.hasOwn(values, key) ? values[key] : undefined;
|
|
1314
|
+
}
|
|
1315
|
+
/** Check cross-field relations before saving one complete layer candidate. */
|
|
1316
|
+
export async function assertPluginConfigMutationCandidate(options) {
|
|
1317
|
+
const user = await readPluginConfigStore({ ...options, scope: 'user' });
|
|
1318
|
+
const project = options.scope === 'project'
|
|
1319
|
+
? await readPluginConfigStore({ ...options, scope: 'project' })
|
|
1320
|
+
: undefined;
|
|
1321
|
+
assertReadyPluginConfigStore(user);
|
|
1322
|
+
if (project)
|
|
1323
|
+
assertReadyPluginConfigStore(project);
|
|
1324
|
+
const edited = options.scope === 'user' ? user : project;
|
|
1325
|
+
const values = { ...edited.values };
|
|
1326
|
+
for (const key of options.removeKeys)
|
|
1327
|
+
delete values[key];
|
|
1328
|
+
for (const [key, value] of Object.entries(options.updates))
|
|
1329
|
+
values[key] = value;
|
|
1330
|
+
const candidateStore = { ...edited, values };
|
|
1331
|
+
const effective = await resolveEffectivePluginConfigFromStores({
|
|
1332
|
+
pluginName: options.pluginName,
|
|
1333
|
+
manifest: options.manifest,
|
|
1334
|
+
targetScope: options.scope,
|
|
1335
|
+
projectRoot: resolveAwProjectRoot(options.cwd),
|
|
1336
|
+
stores: {
|
|
1337
|
+
user: options.scope === 'user' ? candidateStore : user,
|
|
1338
|
+
...(project ? { project: candidateStore } : {}),
|
|
1339
|
+
},
|
|
1340
|
+
});
|
|
1341
|
+
const issues = effective.issues.filter((issue) => issue.code === 'valueRef'
|
|
1342
|
+
|| issue.code === 'valueRefSource' || issue.code === 'valueRefReserved');
|
|
1343
|
+
if (issues.length > 0) {
|
|
1344
|
+
throw Object.assign(new Error(`configReferenceInvalid: ${issues.map((issue) => issue.message).join(' ')}`), {
|
|
1345
|
+
code: 'configReferenceInvalid',
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1054
1349
|
export async function coercePluginConfigUpdates(options) {
|
|
1055
|
-
const projectRoot = resolveAwProjectRoot(options.cwd);
|
|
1056
1350
|
const output = {};
|
|
1057
1351
|
for (const [key, value] of Object.entries(options.updates)) {
|
|
1058
1352
|
const field = options.schema[key];
|
|
1059
1353
|
if (!field) {
|
|
1060
1354
|
throw new Error(`Unknown config key "${key}".`);
|
|
1061
1355
|
}
|
|
1062
|
-
|
|
1356
|
+
const coerced = await coercePluginConfigValue({
|
|
1063
1357
|
key,
|
|
1064
1358
|
field,
|
|
1065
1359
|
value,
|
|
1066
|
-
projectRoot,
|
|
1067
1360
|
});
|
|
1361
|
+
output[key] = coerced;
|
|
1068
1362
|
}
|
|
1069
1363
|
return output;
|
|
1070
1364
|
}
|
|
1071
|
-
function createProjectArtifactBoundaryIssue(options) {
|
|
1072
|
-
if (options.reason === 'pathTypeDefault') {
|
|
1073
|
-
return {
|
|
1074
|
-
key: options.key,
|
|
1075
|
-
source: options.source,
|
|
1076
|
-
reason: options.reason,
|
|
1077
|
-
message: `Config "${options.key}" uses file/directory default value, ` +
|
|
1078
|
-
'which cannot participate in repo-visible project artifact truth.',
|
|
1079
|
-
};
|
|
1080
|
-
}
|
|
1081
|
-
if (options.reason === 'pathTypeRequired') {
|
|
1082
|
-
return {
|
|
1083
|
-
key: options.key,
|
|
1084
|
-
source: options.source,
|
|
1085
|
-
reason: options.reason,
|
|
1086
|
-
message: `Config "${options.key}" is a required file/directory key, ` +
|
|
1087
|
-
'which is not portable enough for repo-visible project artifacts.',
|
|
1088
|
-
};
|
|
1089
|
-
}
|
|
1090
|
-
if (options.reason === 'sharedPathValue') {
|
|
1091
|
-
return {
|
|
1092
|
-
key: options.key,
|
|
1093
|
-
source: options.source,
|
|
1094
|
-
reason: options.reason,
|
|
1095
|
-
message: `Config "${options.key}" is stored in project shared config as a file/directory path. ` +
|
|
1096
|
-
'Use user or project local config for machine-local paths.',
|
|
1097
|
-
};
|
|
1098
|
-
}
|
|
1099
|
-
return {
|
|
1100
|
-
key: options.key,
|
|
1101
|
-
source: options.source,
|
|
1102
|
-
reason: options.reason,
|
|
1103
|
-
message: `Config "${options.key}" is declared as a refresh key for repo-visible project artifacts, ` +
|
|
1104
|
-
'but file/directory config is machine-local and cannot drive shared artifact refresh.',
|
|
1105
|
-
};
|
|
1106
|
-
}
|
|
1107
|
-
export function collectProjectArtifactConfigBoundaryIssues(options) {
|
|
1108
|
-
const issues = [];
|
|
1109
|
-
const refreshKeySet = new Set(options.refreshKeys ?? []);
|
|
1110
|
-
for (const [key, field] of Object.entries(options.schema)) {
|
|
1111
|
-
if (!isPathTypeField(field)) {
|
|
1112
|
-
continue;
|
|
1113
|
-
}
|
|
1114
|
-
if (field.default !== undefined) {
|
|
1115
|
-
issues.push(createProjectArtifactBoundaryIssue({
|
|
1116
|
-
key,
|
|
1117
|
-
source: 'default',
|
|
1118
|
-
reason: 'pathTypeDefault',
|
|
1119
|
-
}));
|
|
1120
|
-
}
|
|
1121
|
-
if (field.required) {
|
|
1122
|
-
issues.push(createProjectArtifactBoundaryIssue({
|
|
1123
|
-
key,
|
|
1124
|
-
source: 'missing',
|
|
1125
|
-
reason: 'pathTypeRequired',
|
|
1126
|
-
}));
|
|
1127
|
-
}
|
|
1128
|
-
if (options.sharedEntries && Object.hasOwn(options.sharedEntries, key)) {
|
|
1129
|
-
issues.push(createProjectArtifactBoundaryIssue({
|
|
1130
|
-
key,
|
|
1131
|
-
source: 'project',
|
|
1132
|
-
reason: 'sharedPathValue',
|
|
1133
|
-
}));
|
|
1134
|
-
}
|
|
1135
|
-
if (refreshKeySet.has(key)) {
|
|
1136
|
-
issues.push(createProjectArtifactBoundaryIssue({
|
|
1137
|
-
key,
|
|
1138
|
-
source: 'project',
|
|
1139
|
-
reason: 'pathTypeRefreshKey',
|
|
1140
|
-
}));
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
return issues;
|
|
1144
|
-
}
|
|
1145
1365
|
export async function resolveProjectArtifactRenderConfig(options) {
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
scope: 'project',
|
|
1171
|
-
cwd: options.cwd,
|
|
1172
|
-
env: options.env,
|
|
1173
|
-
projectLayer: 'local',
|
|
1174
|
-
}),
|
|
1175
|
-
]);
|
|
1176
|
-
if (!schema) {
|
|
1366
|
+
if (!options.manifest.config?.fields) {
|
|
1367
|
+
const pluginName = assertSafePluginConfigSegment(options.pluginName ?? options.manifest.name);
|
|
1368
|
+
const [userStore, projectStore] = await Promise.all([
|
|
1369
|
+
readPluginConfigStore({
|
|
1370
|
+
sourceId: options.sourceId,
|
|
1371
|
+
sourceIdentity: options.sourceIdentity,
|
|
1372
|
+
logicalIdentity: options.logicalIdentity,
|
|
1373
|
+
pluginName,
|
|
1374
|
+
scope: 'user',
|
|
1375
|
+
cwd: options.cwd,
|
|
1376
|
+
env: options.env,
|
|
1377
|
+
}),
|
|
1378
|
+
readPluginConfigStore({
|
|
1379
|
+
sourceId: options.sourceId,
|
|
1380
|
+
sourceIdentity: options.sourceIdentity,
|
|
1381
|
+
logicalIdentity: options.logicalIdentity,
|
|
1382
|
+
pluginName,
|
|
1383
|
+
scope: 'project',
|
|
1384
|
+
cwd: options.cwd,
|
|
1385
|
+
env: options.env,
|
|
1386
|
+
}),
|
|
1387
|
+
]);
|
|
1388
|
+
assertReadyPluginConfigStore(userStore);
|
|
1389
|
+
assertReadyPluginConfigStore(projectStore);
|
|
1177
1390
|
return {
|
|
1178
1391
|
pluginName,
|
|
1179
|
-
projectRoot,
|
|
1392
|
+
projectRoot: resolveAwProjectRoot(options.cwd),
|
|
1180
1393
|
values: {},
|
|
1181
1394
|
sources: {},
|
|
1182
1395
|
issues: [],
|
|
1396
|
+
unsupportedLayerOverrides: [],
|
|
1183
1397
|
ignoredUnknownKeys: [],
|
|
1184
|
-
boundaryIssues: [],
|
|
1185
1398
|
schema: {},
|
|
1186
|
-
stores: {
|
|
1187
|
-
user: userStore,
|
|
1188
|
-
project: projectStore,
|
|
1189
|
-
projectLocal: projectLocalStore,
|
|
1190
|
-
},
|
|
1399
|
+
stores: { user: userStore, project: projectStore },
|
|
1191
1400
|
};
|
|
1192
1401
|
}
|
|
1193
|
-
const
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
const ignoredUnknownKeys = [];
|
|
1197
|
-
const schemaKeys = new Set(Object.keys(schema));
|
|
1198
|
-
const boundaryIssues = collectProjectArtifactConfigBoundaryIssues({
|
|
1199
|
-
schema,
|
|
1200
|
-
sharedEntries: projectStore.values,
|
|
1201
|
-
refreshKeys: options.refreshKeys,
|
|
1202
|
-
});
|
|
1203
|
-
for (const key of Object.keys(schema)) {
|
|
1204
|
-
sources[key] = 'missing';
|
|
1205
|
-
}
|
|
1206
|
-
const projectPlainEntries = getStoreEntries(projectStore.values);
|
|
1207
|
-
const userPlainEntries = getStoreEntries(userStore.values);
|
|
1208
|
-
const projectLocalPlainEntries = getStoreEntries(projectLocalStore.values);
|
|
1209
|
-
for (const key of Object.keys(projectPlainEntries)) {
|
|
1210
|
-
if (!schemaKeys.has(key)) {
|
|
1211
|
-
ignoredUnknownKeys.push({ scope: 'project', projectLayer: 'shared', source: 'project', key });
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
for (const key of Object.keys(userPlainEntries)) {
|
|
1215
|
-
if (!schemaKeys.has(key)) {
|
|
1216
|
-
ignoredUnknownKeys.push({ scope: 'user', projectLayer: 'shared', source: 'user', key });
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
for (const key of Object.keys(projectLocalPlainEntries)) {
|
|
1220
|
-
if (!schemaKeys.has(key)) {
|
|
1221
|
-
ignoredUnknownKeys.push({ scope: 'project', projectLayer: 'local', source: 'projectLocal', key });
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
for (const [key, field] of Object.entries(schema)) {
|
|
1225
|
-
if (!isPathTypeField(field) && field.default !== undefined) {
|
|
1226
|
-
await applyConfigLayer({
|
|
1227
|
-
values,
|
|
1228
|
-
redactedValues: {},
|
|
1229
|
-
sources,
|
|
1230
|
-
invalidIssues,
|
|
1231
|
-
schema: { [key]: field },
|
|
1232
|
-
entries: { [key]: field.default },
|
|
1233
|
-
source: 'default',
|
|
1234
|
-
projectRoot,
|
|
1235
|
-
});
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
const sharedSchema = Object.fromEntries(Object.entries(schema).filter(([, field]) => !isPathTypeField(field)));
|
|
1239
|
-
await applyConfigLayer({
|
|
1240
|
-
values,
|
|
1241
|
-
redactedValues: {},
|
|
1242
|
-
sources,
|
|
1243
|
-
invalidIssues,
|
|
1244
|
-
schema: sharedSchema,
|
|
1245
|
-
entries: projectPlainEntries,
|
|
1246
|
-
source: 'project',
|
|
1247
|
-
projectRoot,
|
|
1402
|
+
const effective = await resolveEffectivePluginConfig({
|
|
1403
|
+
...options,
|
|
1404
|
+
targetScope: 'project',
|
|
1248
1405
|
});
|
|
1249
|
-
const issues = [
|
|
1250
|
-
...invalidIssues.values(),
|
|
1251
|
-
...boundaryIssues.map((issue) => ({
|
|
1252
|
-
type: 'invalid-config',
|
|
1253
|
-
key: issue.key,
|
|
1254
|
-
source: issue.source,
|
|
1255
|
-
message: issue.message,
|
|
1256
|
-
})),
|
|
1257
|
-
];
|
|
1258
|
-
for (const [key, field] of Object.entries(schema)) {
|
|
1259
|
-
if (isPathTypeField(field)) {
|
|
1260
|
-
continue;
|
|
1261
|
-
}
|
|
1262
|
-
if (field.required && values[key] === undefined) {
|
|
1263
|
-
issues.push({
|
|
1264
|
-
type: 'missing-required',
|
|
1265
|
-
key,
|
|
1266
|
-
source: sources[key],
|
|
1267
|
-
message: `Config "${key}" is required.`,
|
|
1268
|
-
});
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
return {
|
|
1272
|
-
pluginName,
|
|
1273
|
-
projectRoot,
|
|
1274
|
-
values,
|
|
1275
|
-
sources,
|
|
1276
|
-
issues,
|
|
1277
|
-
ignoredUnknownKeys,
|
|
1278
|
-
boundaryIssues,
|
|
1279
|
-
schema,
|
|
1280
|
-
stores: {
|
|
1281
|
-
user: userStore,
|
|
1282
|
-
project: projectStore,
|
|
1283
|
-
projectLocal: projectLocalStore,
|
|
1284
|
-
},
|
|
1285
|
-
};
|
|
1286
|
-
}
|
|
1287
|
-
export async function resolveProjectLocalPluginConfigView(options) {
|
|
1288
|
-
const effective = await resolveEffectivePluginConfig(options);
|
|
1289
|
-
const artifactConfig = await resolveProjectArtifactRenderConfig(options);
|
|
1290
|
-
const values = {};
|
|
1291
|
-
const redactedValues = {};
|
|
1292
|
-
const sources = {};
|
|
1293
|
-
const issues = [];
|
|
1294
|
-
for (const [key, field] of Object.entries(effective.schema)) {
|
|
1295
|
-
sources[key] = 'missing';
|
|
1296
|
-
const localPlainEntries = getStoreEntries(effective.stores.projectLocal.values);
|
|
1297
|
-
if (!Object.hasOwn(localPlainEntries, key)) {
|
|
1298
|
-
continue;
|
|
1299
|
-
}
|
|
1300
|
-
sources[key] = 'projectLocal';
|
|
1301
|
-
try {
|
|
1302
|
-
const coerced = await coercePluginConfigValue({
|
|
1303
|
-
key,
|
|
1304
|
-
field,
|
|
1305
|
-
value: localPlainEntries[key],
|
|
1306
|
-
projectRoot: effective.projectRoot,
|
|
1307
|
-
});
|
|
1308
|
-
values[key] = coerced;
|
|
1309
|
-
redactedValues[key] = redactValue(field, coerced);
|
|
1310
|
-
}
|
|
1311
|
-
catch (error) {
|
|
1312
|
-
issues.push({
|
|
1313
|
-
type: 'invalid-config',
|
|
1314
|
-
key,
|
|
1315
|
-
source: 'projectLocal',
|
|
1316
|
-
message: error instanceof Error ? error.message : String(error),
|
|
1317
|
-
});
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
1406
|
return {
|
|
1321
1407
|
pluginName: effective.pluginName,
|
|
1322
1408
|
projectRoot: effective.projectRoot,
|
|
1409
|
+
values: effective.values,
|
|
1410
|
+
sources: effective.sources,
|
|
1411
|
+
issues: effective.issues,
|
|
1412
|
+
unsupportedLayerOverrides: effective.unsupportedLayerOverrides,
|
|
1413
|
+
ignoredUnknownKeys: effective.ignoredUnknownKeys,
|
|
1323
1414
|
schema: effective.schema,
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
sources,
|
|
1328
|
-
issues,
|
|
1329
|
-
ignoredUnknownKeys: effective.ignoredUnknownKeys.filter((entry) => entry.source === 'projectLocal'),
|
|
1330
|
-
repoVisibleArtifactConfig: {
|
|
1331
|
-
values: artifactConfig.values,
|
|
1332
|
-
sources: artifactConfig.sources,
|
|
1333
|
-
issues: artifactConfig.issues,
|
|
1334
|
-
boundaryIssues: artifactConfig.boundaryIssues,
|
|
1415
|
+
stores: {
|
|
1416
|
+
user: effective.stores.user,
|
|
1417
|
+
project: effective.stores.project,
|
|
1335
1418
|
},
|
|
1336
1419
|
};
|
|
1337
1420
|
}
|
|
1338
1421
|
async function resolveRedactedLayerValue(options) {
|
|
1339
1422
|
try {
|
|
1340
|
-
const
|
|
1341
|
-
key: options.key,
|
|
1342
|
-
field: options.field,
|
|
1343
|
-
value: options.value,
|
|
1344
|
-
projectRoot: options.projectRoot,
|
|
1345
|
-
});
|
|
1423
|
+
const value = validateConfigReadValue(options.key, options.field, options.value);
|
|
1346
1424
|
return {
|
|
1347
|
-
value: redactValue(options.field,
|
|
1425
|
+
value: redactValue(options.field, value),
|
|
1348
1426
|
};
|
|
1349
1427
|
}
|
|
1350
1428
|
catch (error) {
|
|
@@ -1360,24 +1438,17 @@ async function resolveRedactedLayerValue(options) {
|
|
|
1360
1438
|
}
|
|
1361
1439
|
export async function resolveProjectScopedPluginConfigView(options) {
|
|
1362
1440
|
const effective = await resolveEffectivePluginConfig(options);
|
|
1363
|
-
const artifactConfig = await resolveProjectArtifactRenderConfig(options);
|
|
1364
1441
|
const entries = {};
|
|
1365
1442
|
for (const [key, field] of Object.entries(effective.schema)) {
|
|
1366
1443
|
const entry = {
|
|
1367
1444
|
key,
|
|
1368
1445
|
effectiveValue: effective.redactedValues[key],
|
|
1369
1446
|
effectiveSource: effective.sources[key] ?? 'missing',
|
|
1370
|
-
repoVisibleArtifactValue: artifactConfig.values[key] === undefined
|
|
1371
|
-
? undefined
|
|
1372
|
-
: redactValue(field, artifactConfig.values[key]),
|
|
1373
|
-
repoVisibleArtifactSource: artifactConfig.sources[key] ?? 'missing',
|
|
1374
|
-
repoVisibleArtifactUsesSharedTruth: true,
|
|
1375
1447
|
};
|
|
1376
1448
|
if (field.default !== undefined) {
|
|
1377
1449
|
const resolvedDefault = await resolveRedactedLayerValue({
|
|
1378
1450
|
key,
|
|
1379
1451
|
field,
|
|
1380
|
-
projectRoot: effective.projectRoot,
|
|
1381
1452
|
source: 'default',
|
|
1382
1453
|
value: field.default,
|
|
1383
1454
|
});
|
|
@@ -1390,7 +1461,6 @@ export async function resolveProjectScopedPluginConfigView(options) {
|
|
|
1390
1461
|
const resolvedUser = await resolveRedactedLayerValue({
|
|
1391
1462
|
key,
|
|
1392
1463
|
field,
|
|
1393
|
-
projectRoot: effective.projectRoot,
|
|
1394
1464
|
source: 'user',
|
|
1395
1465
|
value: userPlainEntries[key],
|
|
1396
1466
|
});
|
|
@@ -1403,25 +1473,11 @@ export async function resolveProjectScopedPluginConfigView(options) {
|
|
|
1403
1473
|
const resolvedProject = await resolveRedactedLayerValue({
|
|
1404
1474
|
key,
|
|
1405
1475
|
field,
|
|
1406
|
-
projectRoot: effective.projectRoot,
|
|
1407
1476
|
source: 'project',
|
|
1408
1477
|
value: projectPlainEntries[key],
|
|
1409
1478
|
});
|
|
1410
1479
|
if (resolvedProject.value !== undefined) {
|
|
1411
|
-
entry.
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
|
-
const localPlainEntries = getStoreEntries(effective.stores.projectLocal.values);
|
|
1415
|
-
if (Object.hasOwn(localPlainEntries, key)) {
|
|
1416
|
-
const resolvedLocal = await resolveRedactedLayerValue({
|
|
1417
|
-
key,
|
|
1418
|
-
field,
|
|
1419
|
-
projectRoot: effective.projectRoot,
|
|
1420
|
-
source: 'projectLocal',
|
|
1421
|
-
value: localPlainEntries[key],
|
|
1422
|
-
});
|
|
1423
|
-
if (resolvedLocal.value !== undefined) {
|
|
1424
|
-
entry.projectLocalValue = resolvedLocal.value;
|
|
1480
|
+
entry.projectValue = resolvedProject.value;
|
|
1425
1481
|
}
|
|
1426
1482
|
}
|
|
1427
1483
|
entries[key] = entry;
|
|
@@ -1430,18 +1486,16 @@ export async function resolveProjectScopedPluginConfigView(options) {
|
|
|
1430
1486
|
pluginName: effective.pluginName,
|
|
1431
1487
|
projectRoot: effective.projectRoot,
|
|
1432
1488
|
schema: effective.schema,
|
|
1433
|
-
stores:
|
|
1489
|
+
stores: {
|
|
1490
|
+
user: effective.stores.user,
|
|
1491
|
+
project: effective.stores.project,
|
|
1492
|
+
},
|
|
1434
1493
|
redactedValues: effective.redactedValues,
|
|
1435
1494
|
sources: effective.sources,
|
|
1436
1495
|
entries,
|
|
1437
1496
|
issues: effective.issues,
|
|
1497
|
+
unsupportedLayerOverrides: effective.unsupportedLayerOverrides,
|
|
1438
1498
|
ignoredUnknownKeys: effective.ignoredUnknownKeys,
|
|
1439
|
-
repoVisibleArtifactConfig: {
|
|
1440
|
-
values: artifactConfig.values,
|
|
1441
|
-
sources: artifactConfig.sources,
|
|
1442
|
-
issues: artifactConfig.issues,
|
|
1443
|
-
boundaryIssues: artifactConfig.boundaryIssues,
|
|
1444
|
-
},
|
|
1445
1499
|
};
|
|
1446
1500
|
}
|
|
1447
1501
|
function toConfigSourceLayer(source) {
|
|
@@ -1454,102 +1508,187 @@ function toConfigSourceLayer(source) {
|
|
|
1454
1508
|
if (source === 'project') {
|
|
1455
1509
|
return 'project';
|
|
1456
1510
|
}
|
|
1457
|
-
if (source === 'projectLocal') {
|
|
1458
|
-
return 'local';
|
|
1459
|
-
}
|
|
1460
1511
|
return null;
|
|
1461
1512
|
}
|
|
1513
|
+
function configFieldProjection(options) {
|
|
1514
|
+
const source = options.source ?? 'missing';
|
|
1515
|
+
const issues = options.issues ?? [];
|
|
1516
|
+
const presetSensitive = options.field.sensitive === true
|
|
1517
|
+
|| (options.field.type === 'object'
|
|
1518
|
+
&& (Object.values(options.field.properties ?? {}).some((child) => child.sensitive === true)
|
|
1519
|
+
|| (options.field.additionalProperties !== false
|
|
1520
|
+
&& options.field.additionalProperties?.sensitive === true)));
|
|
1521
|
+
return {
|
|
1522
|
+
value: options.value === undefined ? null : redactValue(options.field, options.value),
|
|
1523
|
+
source,
|
|
1524
|
+
matchedPreset: options.value === undefined || presetSensitive
|
|
1525
|
+
? null
|
|
1526
|
+
: matchPluginConfigPreset(options.field, options.value)?.id ?? null,
|
|
1527
|
+
valid: issues.length === 0,
|
|
1528
|
+
issues,
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1462
1531
|
async function buildManifestFieldEntries(options) {
|
|
1463
|
-
const schema = options.plugin.manifest.config?.
|
|
1532
|
+
const schema = options.plugin.manifest.config?.fields;
|
|
1464
1533
|
if (!schema) {
|
|
1465
1534
|
return [];
|
|
1466
1535
|
}
|
|
1467
|
-
const
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
pluginName: options.plugin.manifest.name,
|
|
1481
|
-
scope: 'project',
|
|
1482
|
-
projectLayer: 'shared',
|
|
1483
|
-
cwd: options.cwd,
|
|
1484
|
-
env: options.env,
|
|
1485
|
-
}),
|
|
1486
|
-
readPluginConfigStore({
|
|
1536
|
+
const userStore = await readPluginConfigStore({
|
|
1537
|
+
sourceId: options.plugin.sourceId,
|
|
1538
|
+
sourceIdentity: options.plugin.sourceIdentity,
|
|
1539
|
+
logicalIdentity: options.plugin.catalogLogicalIdentity,
|
|
1540
|
+
pluginName: options.plugin.manifest.name,
|
|
1541
|
+
scope: 'user',
|
|
1542
|
+
cwd: options.cwd,
|
|
1543
|
+
env: options.env,
|
|
1544
|
+
schema,
|
|
1545
|
+
});
|
|
1546
|
+
const projectStore = options.scope === 'user'
|
|
1547
|
+
? undefined
|
|
1548
|
+
: await readPluginConfigStore({
|
|
1487
1549
|
sourceId: options.plugin.sourceId,
|
|
1488
1550
|
sourceIdentity: options.plugin.sourceIdentity,
|
|
1551
|
+
logicalIdentity: options.plugin.catalogLogicalIdentity,
|
|
1489
1552
|
pluginName: options.plugin.manifest.name,
|
|
1490
1553
|
scope: 'project',
|
|
1491
|
-
projectLayer: 'local',
|
|
1492
1554
|
cwd: options.cwd,
|
|
1493
1555
|
env: options.env,
|
|
1494
|
-
|
|
1495
|
-
|
|
1556
|
+
schema,
|
|
1557
|
+
});
|
|
1558
|
+
assertReadyPluginConfigStore(userStore);
|
|
1559
|
+
if (projectStore)
|
|
1560
|
+
assertReadyPluginConfigStore(projectStore);
|
|
1496
1561
|
const userPlainEntries = getStoreEntries(userStore.values);
|
|
1497
|
-
const projectPlainEntries = getStoreEntries(projectStore.values);
|
|
1498
|
-
const localPlainEntries = getStoreEntries(projectLocalStore.values);
|
|
1562
|
+
const projectPlainEntries = projectStore ? getStoreEntries(projectStore.values) : {};
|
|
1499
1563
|
const effective = await resolveEffectivePluginConfig({
|
|
1500
1564
|
sourceId: options.plugin.sourceId,
|
|
1501
1565
|
sourceIdentity: options.plugin.sourceIdentity,
|
|
1566
|
+
logicalIdentity: options.plugin.catalogLogicalIdentity,
|
|
1502
1567
|
pluginName: options.plugin.manifest.name,
|
|
1503
1568
|
manifest: options.plugin.manifest,
|
|
1569
|
+
targetScope: options.scope === 'user' ? 'user' : 'project',
|
|
1504
1570
|
cwd: options.cwd,
|
|
1505
1571
|
env: options.env,
|
|
1506
1572
|
});
|
|
1507
|
-
const
|
|
1573
|
+
const userOnlyKeys = codexNativeRefreshKeys(options.plugin.manifest);
|
|
1574
|
+
const projectRemovalFallback = (key) => {
|
|
1508
1575
|
const field = schema[key];
|
|
1576
|
+
if (Object.hasOwn(userPlainEntries, key)) {
|
|
1577
|
+
const value = userPlainEntries[key];
|
|
1578
|
+
const validationIssues = validateCorePluginConfigValue(field, value, key);
|
|
1579
|
+
if (validationIssues.length > 0) {
|
|
1580
|
+
return {
|
|
1581
|
+
state: 'invalid',
|
|
1582
|
+
sourceLayer: 'user',
|
|
1583
|
+
issues: validationIssues.map((issue) => ({
|
|
1584
|
+
type: 'invalid-config',
|
|
1585
|
+
key,
|
|
1586
|
+
source: 'user',
|
|
1587
|
+
message: formatPluginConfigIssue(issue),
|
|
1588
|
+
})),
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
return {
|
|
1592
|
+
state: 'valid',
|
|
1593
|
+
effectiveValue: redactValue(field, value),
|
|
1594
|
+
sourceLayer: 'user',
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
if (field.default === undefined)
|
|
1598
|
+
return { state: 'missing' };
|
|
1599
|
+
const validationIssues = validateCorePluginConfigValue(field, field.default, key);
|
|
1600
|
+
if (validationIssues.length > 0) {
|
|
1601
|
+
return {
|
|
1602
|
+
state: 'invalid',
|
|
1603
|
+
sourceLayer: 'default',
|
|
1604
|
+
issues: validationIssues.map((issue) => ({
|
|
1605
|
+
type: 'invalid-config',
|
|
1606
|
+
key,
|
|
1607
|
+
source: 'default',
|
|
1608
|
+
message: formatPluginConfigIssue(issue),
|
|
1609
|
+
})),
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
return {
|
|
1613
|
+
state: 'valid',
|
|
1614
|
+
effectiveValue: redactValue(field, field.default),
|
|
1615
|
+
sourceLayer: 'default',
|
|
1616
|
+
};
|
|
1617
|
+
};
|
|
1618
|
+
const effectiveFieldState = (key) => {
|
|
1509
1619
|
const source = effective.sources[key];
|
|
1510
1620
|
const sourceLayer = toConfigSourceLayer(source);
|
|
1621
|
+
const field = schema[key];
|
|
1622
|
+
const fieldIssues = effective.issues.filter((entry) => entry.key === key);
|
|
1623
|
+
const fieldState = effective.fieldStates[key];
|
|
1624
|
+
const fallback = options.scope === 'project'
|
|
1625
|
+
? projectRemovalFallback(key)
|
|
1626
|
+
: fieldState?.fallback
|
|
1627
|
+
? {
|
|
1628
|
+
state: 'valid',
|
|
1629
|
+
effectiveValue: fieldState.fallback.redactedValue,
|
|
1630
|
+
sourceLayer: fieldState.fallback.source,
|
|
1631
|
+
}
|
|
1632
|
+
: undefined;
|
|
1511
1633
|
if (source === undefined || source === 'missing') {
|
|
1512
|
-
return {
|
|
1634
|
+
return {
|
|
1635
|
+
valueState: fieldIssues.length > 0 ? 'invalid' : 'default',
|
|
1636
|
+
effectiveValue: null,
|
|
1637
|
+
sourceLayer: null,
|
|
1638
|
+
...configFieldProjection({ field, source: 'missing', issues: fieldIssues }),
|
|
1639
|
+
};
|
|
1513
1640
|
}
|
|
1514
|
-
const issue =
|
|
1641
|
+
const issue = fieldIssues.find((entry) => entry.source === source);
|
|
1515
1642
|
return {
|
|
1516
1643
|
valueState: issue ? 'invalid' : source === 'default' ? 'default' : 'configured',
|
|
1517
|
-
effectiveValue: issue
|
|
1518
|
-
? null
|
|
1519
|
-
: isPathTypeField(field) && effective.redactedValues[key] !== undefined
|
|
1520
|
-
? '[redacted]'
|
|
1521
|
-
: effective.redactedValues[key] ?? null,
|
|
1644
|
+
effectiveValue: issue ? null : effective.redactedValues[key] ?? null,
|
|
1522
1645
|
sourceLayer,
|
|
1646
|
+
...(fieldState?.invalidOverride ? {
|
|
1647
|
+
invalidOverride: {
|
|
1648
|
+
sourceLayer: fieldState.invalidOverride.source,
|
|
1649
|
+
issues: fieldState.invalidOverride.issues,
|
|
1650
|
+
},
|
|
1651
|
+
} : {}),
|
|
1652
|
+
...(fallback ? { fallback } : {}),
|
|
1653
|
+
...configFieldProjection({
|
|
1654
|
+
field,
|
|
1655
|
+
value: issue ? undefined : effective.values[key],
|
|
1656
|
+
source,
|
|
1657
|
+
issues: fieldIssues,
|
|
1658
|
+
}),
|
|
1523
1659
|
};
|
|
1524
1660
|
};
|
|
1525
1661
|
const fields = [];
|
|
1526
1662
|
for (const [key, field] of Object.entries(schema)) {
|
|
1663
|
+
const canSet = options.scope !== 'project'
|
|
1664
|
+
|| (!userOnlyKeys.has(key) && field.sensitive !== true);
|
|
1527
1665
|
const base = {
|
|
1528
1666
|
key,
|
|
1529
1667
|
title: field.title ?? key,
|
|
1530
1668
|
kind: 'manifest',
|
|
1531
1669
|
reserved: false,
|
|
1532
|
-
writable:
|
|
1670
|
+
writable: canSet,
|
|
1671
|
+
canSet,
|
|
1672
|
+
canUnset: options.scope === 'user'
|
|
1673
|
+
? Object.hasOwn(userPlainEntries, key)
|
|
1674
|
+
: options.scope === 'project' && Object.hasOwn(projectPlainEntries, key),
|
|
1533
1675
|
valueState: 'default',
|
|
1534
1676
|
effectiveValue: null,
|
|
1535
1677
|
sourceLayer: null,
|
|
1678
|
+
...configFieldProjection({ field }),
|
|
1536
1679
|
};
|
|
1537
1680
|
if (options.scope === 'user') {
|
|
1538
1681
|
if (Object.hasOwn(userPlainEntries, key)) {
|
|
1539
1682
|
try {
|
|
1540
|
-
const value =
|
|
1541
|
-
key,
|
|
1542
|
-
field,
|
|
1543
|
-
value: userPlainEntries[key],
|
|
1544
|
-
projectRoot,
|
|
1545
|
-
});
|
|
1683
|
+
const value = validateConfigReadValue(key, field, userPlainEntries[key]);
|
|
1546
1684
|
fields.push({
|
|
1547
1685
|
...base,
|
|
1548
1686
|
valueState: 'configured',
|
|
1549
|
-
effectiveValue:
|
|
1687
|
+
effectiveValue: redactValue(field, value),
|
|
1550
1688
|
sourceLayer: 'user',
|
|
1551
|
-
storedValue:
|
|
1689
|
+
storedValue: redactValue(field, value),
|
|
1552
1690
|
storedSourceLayer: 'user',
|
|
1691
|
+
...configFieldProjection({ field, value, source: 'user' }),
|
|
1553
1692
|
});
|
|
1554
1693
|
}
|
|
1555
1694
|
catch {
|
|
@@ -1557,23 +1696,22 @@ async function buildManifestFieldEntries(options) {
|
|
|
1557
1696
|
...base,
|
|
1558
1697
|
valueState: 'invalid',
|
|
1559
1698
|
sourceLayer: 'user',
|
|
1699
|
+
source: 'user',
|
|
1560
1700
|
storedSourceLayer: 'user',
|
|
1701
|
+
valid: false,
|
|
1702
|
+
issues: [{ type: 'invalid-config', key, source: 'user' }],
|
|
1561
1703
|
});
|
|
1562
1704
|
}
|
|
1563
1705
|
continue;
|
|
1564
1706
|
}
|
|
1565
1707
|
if (field.default !== undefined) {
|
|
1566
1708
|
try {
|
|
1567
|
-
const value =
|
|
1568
|
-
key,
|
|
1569
|
-
field,
|
|
1570
|
-
value: field.default,
|
|
1571
|
-
projectRoot,
|
|
1572
|
-
});
|
|
1709
|
+
const value = validateConfigReadValue(key, field, field.default);
|
|
1573
1710
|
fields.push({
|
|
1574
1711
|
...base,
|
|
1575
|
-
effectiveValue:
|
|
1712
|
+
effectiveValue: redactValue(field, value),
|
|
1576
1713
|
sourceLayer: 'default',
|
|
1714
|
+
...configFieldProjection({ field, value, source: 'default' }),
|
|
1577
1715
|
});
|
|
1578
1716
|
}
|
|
1579
1717
|
catch {
|
|
@@ -1581,6 +1719,9 @@ async function buildManifestFieldEntries(options) {
|
|
|
1581
1719
|
...base,
|
|
1582
1720
|
valueState: 'invalid',
|
|
1583
1721
|
sourceLayer: 'default',
|
|
1722
|
+
source: 'default',
|
|
1723
|
+
valid: false,
|
|
1724
|
+
issues: [{ type: 'invalid-config', key, source: 'default' }],
|
|
1584
1725
|
});
|
|
1585
1726
|
}
|
|
1586
1727
|
continue;
|
|
@@ -1588,49 +1729,12 @@ async function buildManifestFieldEntries(options) {
|
|
|
1588
1729
|
fields.push(base);
|
|
1589
1730
|
continue;
|
|
1590
1731
|
}
|
|
1591
|
-
if (options.projectLayer === 'local') {
|
|
1592
|
-
if (Object.hasOwn(localPlainEntries, key)) {
|
|
1593
|
-
try {
|
|
1594
|
-
const value = await coercePluginConfigValue({
|
|
1595
|
-
key,
|
|
1596
|
-
field,
|
|
1597
|
-
value: localPlainEntries[key],
|
|
1598
|
-
projectRoot,
|
|
1599
|
-
});
|
|
1600
|
-
fields.push({
|
|
1601
|
-
...base,
|
|
1602
|
-
valueState: 'configured',
|
|
1603
|
-
effectiveValue: isPathTypeField(field) ? '[redacted]' : redactValue(field, value),
|
|
1604
|
-
sourceLayer: 'local',
|
|
1605
|
-
storedValue: isPathTypeField(field) ? '[redacted]' : redactValue(field, value),
|
|
1606
|
-
storedSourceLayer: 'local',
|
|
1607
|
-
});
|
|
1608
|
-
}
|
|
1609
|
-
catch {
|
|
1610
|
-
fields.push({
|
|
1611
|
-
...base,
|
|
1612
|
-
valueState: 'invalid',
|
|
1613
|
-
sourceLayer: 'local',
|
|
1614
|
-
storedSourceLayer: 'local',
|
|
1615
|
-
});
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
else {
|
|
1619
|
-
fields.push(base);
|
|
1620
|
-
}
|
|
1621
|
-
continue;
|
|
1622
|
-
}
|
|
1623
1732
|
let storedProjectValue;
|
|
1624
1733
|
if (options.scope === 'project' && Object.hasOwn(projectPlainEntries, key)) {
|
|
1625
1734
|
try {
|
|
1626
|
-
const value =
|
|
1627
|
-
key,
|
|
1628
|
-
field,
|
|
1629
|
-
value: projectPlainEntries[key],
|
|
1630
|
-
projectRoot,
|
|
1631
|
-
});
|
|
1735
|
+
const value = validateConfigReadValue(key, field, projectPlainEntries[key]);
|
|
1632
1736
|
storedProjectValue = {
|
|
1633
|
-
storedValue:
|
|
1737
|
+
storedValue: redactValue(field, value),
|
|
1634
1738
|
storedSourceLayer: 'project',
|
|
1635
1739
|
};
|
|
1636
1740
|
}
|
|
@@ -1645,25 +1749,21 @@ async function buildManifestFieldEntries(options) {
|
|
|
1645
1749
|
async function buildLegacyStoreFieldEntries(options) {
|
|
1646
1750
|
const locations = options.scope === 'effective'
|
|
1647
1751
|
? [
|
|
1648
|
-
{ scope: 'user',
|
|
1649
|
-
{ scope: 'project',
|
|
1650
|
-
{ scope: 'project', projectLayer: 'local', sourceLayer: 'local' },
|
|
1752
|
+
{ scope: 'user', sourceLayer: 'user' },
|
|
1753
|
+
{ scope: 'project', sourceLayer: 'project' },
|
|
1651
1754
|
]
|
|
1652
1755
|
: [{
|
|
1653
1756
|
scope: options.scope,
|
|
1654
|
-
|
|
1655
|
-
sourceLayer: options.scope === 'user'
|
|
1656
|
-
? 'user'
|
|
1657
|
-
: options.projectLayer === 'local' ? 'local' : 'project',
|
|
1757
|
+
sourceLayer: options.scope === 'user' ? 'user' : 'project',
|
|
1658
1758
|
}];
|
|
1659
1759
|
const fields = new Map();
|
|
1660
1760
|
for (const location of locations) {
|
|
1661
1761
|
const store = await readPluginConfigStore({
|
|
1662
1762
|
sourceId: options.sourceId,
|
|
1663
1763
|
sourceIdentity: options.sourceIdentity,
|
|
1764
|
+
logicalIdentity: options.logicalIdentity,
|
|
1664
1765
|
pluginName: options.pluginName,
|
|
1665
1766
|
scope: location.scope,
|
|
1666
|
-
...(location.scope === 'project' ? { projectLayer: location.projectLayer } : {}),
|
|
1667
1767
|
cwd: options.cwd,
|
|
1668
1768
|
env: options.env,
|
|
1669
1769
|
});
|
|
@@ -1674,9 +1774,16 @@ async function buildLegacyStoreFieldEntries(options) {
|
|
|
1674
1774
|
kind: 'manifest',
|
|
1675
1775
|
reserved: false,
|
|
1676
1776
|
writable: false,
|
|
1777
|
+
canSet: false,
|
|
1778
|
+
canUnset: false,
|
|
1677
1779
|
valueState: 'configured',
|
|
1678
1780
|
effectiveValue: '[redacted]',
|
|
1679
1781
|
sourceLayer: location.sourceLayer,
|
|
1782
|
+
value: '[redacted]',
|
|
1783
|
+
source: location.sourceLayer,
|
|
1784
|
+
matchedPreset: null,
|
|
1785
|
+
valid: true,
|
|
1786
|
+
issues: [],
|
|
1680
1787
|
});
|
|
1681
1788
|
}
|
|
1682
1789
|
}
|
|
@@ -1706,7 +1813,6 @@ export async function resolveConfigRead(options) {
|
|
|
1706
1813
|
currentPlugin: options.plugin,
|
|
1707
1814
|
currentPluginError: options.pluginError,
|
|
1708
1815
|
scope: options.scope,
|
|
1709
|
-
projectLayer: options.projectLayer,
|
|
1710
1816
|
cwd: options.cwd,
|
|
1711
1817
|
env: options.env,
|
|
1712
1818
|
});
|
|
@@ -1714,24 +1820,25 @@ export async function resolveConfigRead(options) {
|
|
|
1714
1820
|
? await buildManifestFieldEntries({
|
|
1715
1821
|
plugin: schemaAccess.plugin,
|
|
1716
1822
|
scope: options.scope,
|
|
1717
|
-
projectLayer: options.projectLayer,
|
|
1718
1823
|
cwd: options.cwd,
|
|
1719
1824
|
env: options.env,
|
|
1720
1825
|
})
|
|
1721
1826
|
: await buildLegacyStoreFieldEntries({
|
|
1722
1827
|
sourceId: options.sourceId,
|
|
1723
1828
|
sourceIdentity: options.sourceIdentity,
|
|
1829
|
+
logicalIdentity: options.logicalIdentity,
|
|
1724
1830
|
pluginName,
|
|
1725
1831
|
scope: options.scope,
|
|
1726
|
-
projectLayer: options.projectLayer,
|
|
1727
1832
|
cwd: options.cwd,
|
|
1728
1833
|
env: options.env,
|
|
1729
1834
|
});
|
|
1730
1835
|
return {
|
|
1731
1836
|
pluginName,
|
|
1732
1837
|
scope: options.scope,
|
|
1733
|
-
projectLayer: options.projectLayer,
|
|
1734
1838
|
...(schemaAccess.schema ? { schema: schemaAccess.schema } : {}),
|
|
1839
|
+
...(schemaAccess.plugin?.manifest.config?.fields
|
|
1840
|
+
? { fieldDefinitions: schemaAccess.plugin.manifest.config.fields }
|
|
1841
|
+
: {}),
|
|
1735
1842
|
fields: manifestFields,
|
|
1736
1843
|
};
|
|
1737
1844
|
}
|
|
@@ -1745,6 +1852,15 @@ export function summarizeEffectivePluginConfig(config) {
|
|
|
1745
1852
|
return {
|
|
1746
1853
|
values: config.redactedValues,
|
|
1747
1854
|
sources: config.sources,
|
|
1855
|
+
fieldStates: Object.fromEntries(Object.entries(config.fieldStates).map(([key, state]) => [key, {
|
|
1856
|
+
...(state.effective ? {
|
|
1857
|
+
effective: { value: state.effective.redactedValue, source: state.effective.source },
|
|
1858
|
+
} : {}),
|
|
1859
|
+
...(state.invalidOverride ? { invalidOverride: state.invalidOverride } : {}),
|
|
1860
|
+
...(state.fallback ? {
|
|
1861
|
+
fallback: { value: state.fallback.redactedValue, source: state.fallback.source },
|
|
1862
|
+
} : {}),
|
|
1863
|
+
}])),
|
|
1748
1864
|
issues: config.issues,
|
|
1749
1865
|
ignoredUnknownKeys: config.ignoredUnknownKeys,
|
|
1750
1866
|
};
|
|
@@ -1760,52 +1876,75 @@ export function createPluginConfigSchemaIdentity(manifest) {
|
|
|
1760
1876
|
.digest('hex');
|
|
1761
1877
|
return `${manifest.name}:${hash}`;
|
|
1762
1878
|
}
|
|
1879
|
+
function cliConfigPromptEnumValues(field) {
|
|
1880
|
+
if (!field.choices)
|
|
1881
|
+
return undefined;
|
|
1882
|
+
const values = field.choices.presets
|
|
1883
|
+
.map((preset) => preset.value);
|
|
1884
|
+
if (field.type !== 'boolean') {
|
|
1885
|
+
return field.choices.allowCustom ? undefined : values;
|
|
1886
|
+
}
|
|
1887
|
+
if (field.choices.allowCustom) {
|
|
1888
|
+
for (const value of [true, false]) {
|
|
1889
|
+
if (!values.includes(value))
|
|
1890
|
+
values.push(value);
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
return values;
|
|
1894
|
+
}
|
|
1763
1895
|
export function toCliConfigPromptSchema(schema) {
|
|
1764
1896
|
const promptSchema = {};
|
|
1765
1897
|
for (const [key, field] of Object.entries(schema)) {
|
|
1898
|
+
if (field.type === 'object' || field.type === 'array')
|
|
1899
|
+
continue;
|
|
1766
1900
|
promptSchema[key] = {
|
|
1767
|
-
type: field.type
|
|
1901
|
+
type: field.type,
|
|
1768
1902
|
label: field.title ?? key,
|
|
1769
1903
|
description: field.description,
|
|
1770
1904
|
required: field.required,
|
|
1771
1905
|
sensitive: field.sensitive,
|
|
1772
1906
|
default: field.default,
|
|
1773
|
-
enum: field
|
|
1774
|
-
min: field.
|
|
1775
|
-
max: field.
|
|
1907
|
+
enum: cliConfigPromptEnumValues(field),
|
|
1908
|
+
min: field.type === 'number' ? field.validation?.minimum : undefined,
|
|
1909
|
+
max: field.type === 'number' ? field.validation?.maximum : undefined,
|
|
1776
1910
|
};
|
|
1777
1911
|
}
|
|
1778
1912
|
return promptSchema;
|
|
1779
1913
|
}
|
|
1780
1914
|
export function createPluginConfigFormModel(options) {
|
|
1781
|
-
const schema = options.manifest.config?.
|
|
1915
|
+
const schema = options.manifest.config?.fields;
|
|
1782
1916
|
if (!schema) {
|
|
1783
1917
|
throw new Error(`Plugin "${options.pluginName ?? options.manifest.name}" does not declare an AW config schema.`);
|
|
1784
1918
|
}
|
|
1785
1919
|
const requiredKeys = [...new Set(options.effective.issues
|
|
1786
1920
|
.filter((issue) => issue.type === 'missing-required' || issue.type === 'invalid-config')
|
|
1787
1921
|
.map((issue) => issue.key))];
|
|
1922
|
+
const fields = Object.entries(schema).flatMap(([key, field]) => {
|
|
1923
|
+
if (field.type === 'object' || field.type === 'array')
|
|
1924
|
+
return [];
|
|
1925
|
+
return [{
|
|
1926
|
+
key,
|
|
1927
|
+
type: field.type,
|
|
1928
|
+
label: field.title ?? key,
|
|
1929
|
+
description: field.description,
|
|
1930
|
+
required: field.required === true,
|
|
1931
|
+
sensitive: field.sensitive === true,
|
|
1932
|
+
defaultValue: field.default,
|
|
1933
|
+
enumValues: cliConfigPromptEnumValues(field),
|
|
1934
|
+
min: field.type === 'number' ? field.validation?.minimum : undefined,
|
|
1935
|
+
max: field.type === 'number' ? field.validation?.maximum : undefined,
|
|
1936
|
+
configured: options.effective.values[key] !== undefined,
|
|
1937
|
+
currentValue: field.sensitive
|
|
1938
|
+
? undefined
|
|
1939
|
+
: options.effective.values[key],
|
|
1940
|
+
}];
|
|
1941
|
+
});
|
|
1788
1942
|
return {
|
|
1789
1943
|
pluginName: options.pluginName ?? options.manifest.name,
|
|
1790
1944
|
allowedScopes: options.allowedScopes ?? ['user', 'project'],
|
|
1791
1945
|
initialScope: options.initialScope,
|
|
1792
1946
|
requiredKeys,
|
|
1793
|
-
fields
|
|
1794
|
-
key,
|
|
1795
|
-
type: field.type,
|
|
1796
|
-
label: field.title ?? key,
|
|
1797
|
-
description: field.description,
|
|
1798
|
-
required: field.required === true,
|
|
1799
|
-
sensitive: field.sensitive === true,
|
|
1800
|
-
defaultValue: field.default,
|
|
1801
|
-
enumValues: field.type === 'enum' ? field.values : undefined,
|
|
1802
|
-
min: field.min,
|
|
1803
|
-
max: field.max,
|
|
1804
|
-
configured: options.effective.values[key] !== undefined,
|
|
1805
|
-
currentValue: field.sensitive || isPathTypeField(field)
|
|
1806
|
-
? undefined
|
|
1807
|
-
: options.effective.values[key],
|
|
1808
|
-
})),
|
|
1947
|
+
fields,
|
|
1809
1948
|
};
|
|
1810
1949
|
}
|
|
1811
1950
|
export function toCliConfigPromptOptions(model) {
|
|
@@ -1824,7 +1963,7 @@ export function toCliConfigPromptOptions(model) {
|
|
|
1824
1963
|
schema: Object.fromEntries(model.fields.map((field) => [
|
|
1825
1964
|
field.key,
|
|
1826
1965
|
{
|
|
1827
|
-
type: field.type
|
|
1966
|
+
type: field.type,
|
|
1828
1967
|
label: field.label,
|
|
1829
1968
|
description: field.description,
|
|
1830
1969
|
required: field.required,
|
|
@@ -1842,13 +1981,21 @@ export function toCliConfigPromptOptions(model) {
|
|
|
1842
1981
|
requiredKeys: [...model.requiredKeys],
|
|
1843
1982
|
};
|
|
1844
1983
|
}
|
|
1845
|
-
function
|
|
1984
|
+
export function formatPluginConfigSetRecoveryCommand(pluginName, key, field, scope) {
|
|
1985
|
+
return field.sensitive === true
|
|
1986
|
+
? `aw config set ${pluginName} ${key} --scope user --value-stdin`
|
|
1987
|
+
: `aw config set ${pluginName} ${key} --scope ${scope} --value-json '<json>'`;
|
|
1988
|
+
}
|
|
1989
|
+
function formatRequiredConfigSetCommands(pluginName, issues, defaultScope, schema) {
|
|
1846
1990
|
const commands = new Set();
|
|
1847
1991
|
for (const issue of issues) {
|
|
1848
1992
|
const scope = issue.source === 'user' || issue.source === 'project'
|
|
1849
1993
|
? issue.source
|
|
1850
1994
|
: defaultScope;
|
|
1851
|
-
|
|
1995
|
+
const field = schema[issue.key];
|
|
1996
|
+
if (!field)
|
|
1997
|
+
continue;
|
|
1998
|
+
commands.add(formatPluginConfigSetRecoveryCommand(pluginName, issue.key, field, scope));
|
|
1852
1999
|
}
|
|
1853
2000
|
return [...commands].map((command) => `\`${command}\``).join(' 或 ');
|
|
1854
2001
|
}
|
|
@@ -1868,12 +2015,13 @@ export async function preflightRequiredPluginConfigs(options) {
|
|
|
1868
2015
|
}
|
|
1869
2016
|
visited.add(dedupeKey);
|
|
1870
2017
|
result.checked += 1;
|
|
1871
|
-
if (!target.manifest.config?.
|
|
2018
|
+
if (!target.manifest.config?.fields) {
|
|
1872
2019
|
continue;
|
|
1873
2020
|
}
|
|
1874
2021
|
let effective = await resolveEffectivePluginConfig({
|
|
1875
2022
|
sourceId: target.sourceId,
|
|
1876
2023
|
sourceIdentity: target.sourceIdentity,
|
|
2024
|
+
logicalIdentity: target.logicalIdentity,
|
|
1877
2025
|
pluginName,
|
|
1878
2026
|
manifest: target.manifest,
|
|
1879
2027
|
cwd: options.cwd,
|
|
@@ -1884,7 +2032,7 @@ export async function preflightRequiredPluginConfigs(options) {
|
|
|
1884
2032
|
continue;
|
|
1885
2033
|
}
|
|
1886
2034
|
const details = blockingIssues.map((issue) => issue.key).join(', ');
|
|
1887
|
-
const setCommands = formatRequiredConfigSetCommands(pluginName, blockingIssues, options.defaultScope);
|
|
2035
|
+
const setCommands = formatRequiredConfigSetCommands(pluginName, blockingIssues, options.defaultScope, target.manifest.config.fields);
|
|
1888
2036
|
throw new Error(`${pluginName} requires valid AW config before install/upgrade: ${details}. ` +
|
|
1889
2037
|
`Run ${setCommands}, or open \`aw\` to complete setup.`);
|
|
1890
2038
|
}
|