@lesliechan721/aw 0.1.0-beta.3 → 0.1.0-beta.31
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/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-version-check.d.mts +15 -0
- package/dist/authoring/scripts/lib/plugin-version-check.mjs +239 -232
- package/dist/authoring/scripts/lib/plugin-version-check.mjs.map +1 -1
- package/dist/authoring/scripts/lib/skill-scaffold.d.mts +77 -90
- package/dist/authoring/scripts/lib/skill-scaffold.mjs +99 -120
- 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 +367 -0
- package/dist/authoring/scripts/remove-plugin.mjs.map +1 -0
- package/dist/authoring/scripts/validate-plugin.mjs +307 -1173
- package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
- 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 -2
- package/dist/scripts/bundle-plugins.js +24 -7
- package/dist/scripts/bundle-plugins.js.map +1 -1
- package/dist/scripts/plugin-staging.d.ts +7 -24
- package/dist/scripts/plugin-staging.js +22 -142
- package/dist/scripts/plugin-staging.js.map +1 -1
- package/dist/scripts/test-plugin-scripts.d.ts +2 -0
- package/dist/scripts/test-plugin-scripts.js +27 -75
- package/dist/scripts/test-plugin-scripts.js.map +1 -1
- 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 +26 -2
- package/dist/src/authoring/workspace-commands.js +44 -19
- 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 +256 -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 +138 -0
- package/dist/src/commands/catalog-publication-inspection.js +757 -0
- package/dist/src/commands/catalog-publication-inspection.js.map +1 -0
- 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 +77 -7
- package/dist/src/commands/catalog-publish.js +746 -316
- 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 +56 -0
- package/dist/src/commands/catalog-status.js +217 -0
- package/dist/src/commands/catalog-status.js.map +1 -0
- 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/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 +127 -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 +46 -0
- package/dist/src/commands/operation-planner.js +141 -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.d.ts +22 -0
- package/dist/src/commands/plugin-capabilities.js +159 -0
- package/dist/src/commands/plugin-capabilities.js.map +1 -0
- package/dist/src/commands/rebuild-command.d.ts +77 -30
- package/dist/src/commands/rebuild-command.js +3993 -711
- package/dist/src/commands/rebuild-command.js.map +1 -1
- package/dist/src/commands/rebuild-integration.d.ts +120 -21
- package/dist/src/commands/rebuild-integration.js +2765 -1326
- package/dist/src/commands/rebuild-integration.js.map +1 -1
- package/dist/src/commands/rebuild-operations.d.ts +58 -30
- package/dist/src/commands/rebuild-operations.js +260 -319
- 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 +228 -0
- package/dist/src/commands/review-inspection.js +1341 -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 +24 -50
- package/dist/src/commands/runtime.js.map +1 -1
- package/dist/src/commands/skill-eval.d.ts +38 -0
- package/dist/src/commands/skill-eval.js +388 -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 +2697 -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 +250 -0
- package/dist/src/commands/sync-command.js.map +1 -0
- package/dist/src/commands/wizard.d.ts +3 -4
- package/dist/src/commands/wizard.js +2 -2
- 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 +124 -31
- package/dist/src/commands/write-commands.js.map +1 -1
- package/dist/src/constants.d.ts +7 -1
- package/dist/src/constants.js +7 -1
- package/dist/src/constants.js.map +1 -1
- package/dist/src/eval/manifest-v1.d.ts +71 -0
- package/dist/src/eval/manifest-v1.js +370 -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 +225 -104
- package/dist/src/generators/local-plugin.js.map +1 -1
- package/dist/src/index.d.ts +10 -4
- package/dist/src/index.js +1787 -476
- package/dist/src/index.js.map +1 -1
- package/dist/src/output/command-receipt.d.ts +43 -46
- package/dist/src/output/command-receipt.js +256 -80
- 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 +1 -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 +160 -38
- 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 +66 -19
- 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 +177 -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 +13 -0
- package/dist/src/review/skill-materialization.js +75 -0
- package/dist/src/review/skill-materialization.js.map +1 -0
- package/dist/src/review/source-binding.d.ts +182 -0
- package/dist/src/review/source-binding.js +682 -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 +28 -0
- package/dist/src/tui/catalog-sources/session.js +295 -0
- package/dist/src/tui/catalog-sources/session.js.map +1 -0
- package/dist/src/tui/catalog-sources/types.d.ts +53 -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/components.d.ts +128 -0
- package/dist/src/tui/components.js +251 -7
- package/dist/src/tui/components.js.map +1 -1
- package/dist/src/tui/console-app-v4.d.ts +14 -4
- package/dist/src/tui/console-app-v4.js +9248 -1461
- package/dist/src/tui/console-app-v4.js.map +1 -1
- package/dist/src/tui/controller-runtime.d.ts +13 -1
- package/dist/src/tui/controller-runtime.js +3338 -410
- package/dist/src/tui/controller-runtime.js.map +1 -1
- package/dist/src/tui/controller.d.ts +614 -40
- package/dist/src/tui/controller.js +3478 -708
- package/dist/src/tui/controller.js.map +1 -1
- package/dist/src/tui/dependency-graph.d.ts +81 -0
- package/dist/src/tui/dependency-graph.js +455 -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 +567 -0
- package/dist/src/tui/diagnostic-projection.js.map +1 -0
- package/dist/src/tui/diagnostics.d.ts +30 -0
- package/dist/src/tui/diagnostics.js +76 -0
- package/dist/src/tui/diagnostics.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 +11 -4
- package/dist/src/tui/index.js +8 -2
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/tui/input-display.d.ts +8 -0
- package/dist/src/tui/input-display.js +36 -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 +235 -27
- package/dist/src/tui/model.js +1652 -304
- package/dist/src/tui/model.js.map +1 -1
- package/dist/src/tui/operation-diagnostic-screens.d.ts +21 -0
- package/dist/src/tui/operation-diagnostic-screens.js +102 -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 +135 -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 +1254 -0
- package/dist/src/tui/operation-review-planner.js.map +1 -0
- package/dist/src/tui/plugin-content.d.ts +23 -0
- package/dist/src/tui/plugin-content.js +336 -0
- package/dist/src/tui/plugin-content.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/prompt.js +4 -15
- package/dist/src/tui/prompt.js.map +1 -1
- package/dist/src/tui/router.d.ts +183 -11
- package/dist/src/tui/router.js +352 -29
- package/dist/src/tui/router.js.map +1 -1
- package/dist/src/tui/screen-registry.d.ts +702 -0
- package/dist/src/tui/screen-registry.js +231 -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-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 +18 -10
- package/dist/src/tui/theme.js +18 -10
- package/dist/src/tui/theme.js.map +1 -1
- package/dist/src/tui/viewport.d.ts +20 -0
- package/dist/src/tui/viewport.js +29 -0
- package/dist/src/tui/viewport.js.map +1 -0
- package/dist/src/tui/views.js +31 -3
- package/dist/src/tui/views.js.map +1 -1
- package/dist/src/tui/wizard.js +11 -9
- package/dist/src/tui/wizard.js.map +1 -1
- package/dist/src/types.d.ts +242 -20
- 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 +41 -8
- package/dist/src/utils/agent-version.js.map +1 -1
- package/dist/src/utils/artifact-plan.d.ts +5 -4
- package/dist/src/utils/artifact-plan.js +18 -7
- package/dist/src/utils/artifact-plan.js.map +1 -1
- package/dist/src/utils/artifact-renderer.d.ts +13 -7
- package/dist/src/utils/artifact-renderer.js +196 -219
- package/dist/src/utils/artifact-renderer.js.map +1 -1
- package/dist/src/utils/artifact-request.d.ts +21 -0
- package/dist/src/utils/artifact-request.js +82 -0
- package/dist/src/utils/artifact-request.js.map +1 -0
- 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 +14 -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 +50 -2
- 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 +4 -7
- package/dist/src/utils/catalog-runtime-projection.js +7 -102
- 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/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 +745 -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-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 +4 -0
- package/dist/src/utils/dynamic-skill-tree.js +36 -6
- package/dist/src/utils/dynamic-skill-tree.js.map +1 -1
- package/dist/src/utils/git-catalog-provider.d.ts +18 -0
- package/dist/src/utils/git-catalog-provider.js +170 -144
- package/dist/src/utils/git-catalog-provider.js.map +1 -1
- package/dist/src/utils/local-marketplace.d.ts +15 -1
- package/dist/src/utils/local-marketplace.js +307 -84
- package/dist/src/utils/local-marketplace.js.map +1 -1
- package/dist/src/utils/manifest.d.ts +1 -4
- package/dist/src/utils/manifest.js +1 -4
- 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 +55 -7
- package/dist/src/utils/native-cli.js +386 -57
- package/dist/src/utils/native-cli.js.map +1 -1
- 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 +84 -20
- package/dist/src/utils/ownership.js +179 -222
- 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 +33 -0
- package/dist/src/utils/plugin-config-impact.js +298 -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 +990 -298
- package/dist/src/utils/plugin-config-write.js.map +1 -1
- package/dist/src/utils/plugin-config.d.ts +140 -93
- package/dist/src/utils/plugin-config.js +992 -980
- 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 +62 -125
- package/dist/src/utils/plugin-scripts.js +468 -525
- package/dist/src/utils/plugin-scripts.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/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/skill-validation.d.ts +1 -1
- package/dist/src/utils/skill-validation.js +1 -1
- package/dist/src/utils/skill-validation.js.map +1 -1
- package/dist/src/utils/state-paths.d.ts +1 -0
- package/dist/src/utils/state-paths.js +12 -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 +9 -0
- package/dist/src/utils/windows-claude-shim.js +141 -0
- package/dist/src/utils/windows-claude-shim.js.map +1 -0
- package/dist/vitest.config.js +91 -10
- package/dist/vitest.config.js.map +1 -1
- package/package.json +22 -19
- 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/status-v4.d.ts +0 -102
- package/dist/src/commands/status-v4.js +0 -844
- 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 -61
- package/dist/src/utils/catalog-source-cache.js +0 -276
- 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-authoring-capabilities.d.ts +0 -23
- package/dist/src/utils/plugin-authoring-capabilities.js +0 -37
- package/dist/src/utils/plugin-authoring-capabilities.js.map +0 -1
- package/dist/src/utils/plugin-authoring-capabilities.json +0 -92
- 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
- package/dist/src/utils/skill-frontmatter-capabilities.d.ts +0 -201
- package/dist/src/utils/skill-frontmatter-capabilities.js +0 -5
- package/dist/src/utils/skill-frontmatter-capabilities.js.map +0 -1
- package/dist/src/utils/skill-frontmatter-capabilities.json +0 -200
- /package/dist/authoring/scripts/{check-plugin-versions.d.mts → remove-plugin.d.mts} +0 -0
|
@@ -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, matchPluginConfigPreset, redactPluginConfigValue, stablePluginConfigStringify, 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_V4_KEYS = new Set([
|
|
36
|
+
'schemaVersion',
|
|
37
|
+
'sourceId',
|
|
38
|
+
'pluginName',
|
|
39
|
+
'binding',
|
|
40
|
+
'values',
|
|
41
|
+
]);
|
|
42
|
+
function readPluginConfigEnvelopeV4(value, scope) {
|
|
43
|
+
if (!isRecord(value)
|
|
44
|
+
|| value.schemaVersion !== 4
|
|
45
|
+
|| typeof value.sourceId !== 'string'
|
|
46
|
+
|| typeof value.pluginName !== 'string'
|
|
47
|
+
|| !isRecord(value.values)
|
|
48
|
+
|| Object.keys(value).some((key) => !PLUGIN_CONFIG_ENVELOPE_V4_KEYS.has(key))) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const binding = readPluginConfigBinding(value.binding, scope);
|
|
52
|
+
if (!binding)
|
|
53
|
+
return null;
|
|
54
|
+
return {
|
|
55
|
+
schemaVersion: 4,
|
|
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)) {
|
|
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,143 @@ export function resolvePluginConfigStoreTargets(options) {
|
|
|
401
312
|
path: resolvePluginConfigStorePath(options),
|
|
402
313
|
}];
|
|
403
314
|
}
|
|
404
|
-
return [
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
projectLayer: 'local',
|
|
420
|
-
},
|
|
421
|
-
];
|
|
315
|
+
return [{
|
|
316
|
+
storeKind: 'project',
|
|
317
|
+
path: resolvePluginConfigStorePath(options),
|
|
318
|
+
}];
|
|
319
|
+
}
|
|
320
|
+
function isPluginConfigValue(value) {
|
|
321
|
+
if (typeof value === 'string' || typeof value === 'boolean')
|
|
322
|
+
return true;
|
|
323
|
+
if (typeof value === 'number')
|
|
324
|
+
return Number.isFinite(value);
|
|
325
|
+
if (!isRecord(value))
|
|
326
|
+
return false;
|
|
327
|
+
return Object.values(value).every((child) => typeof child === 'string'
|
|
328
|
+
|| typeof child === 'boolean'
|
|
329
|
+
|| (typeof child === 'number' && Number.isFinite(child)));
|
|
422
330
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
331
|
+
function invalidStore(filePath, code, message, rawDigest) {
|
|
332
|
+
return {
|
|
333
|
+
path: filePath,
|
|
334
|
+
exists: true,
|
|
335
|
+
state: 'invalid',
|
|
336
|
+
values: {},
|
|
337
|
+
...(rawDigest ? { rawDigest } : {}),
|
|
338
|
+
invalidCode: code,
|
|
339
|
+
issues: [message],
|
|
340
|
+
};
|
|
429
341
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
342
|
+
function readyStore(filePath, values, rawDigest) {
|
|
343
|
+
return {
|
|
344
|
+
path: filePath,
|
|
345
|
+
exists: true,
|
|
346
|
+
state: 'ready',
|
|
347
|
+
values: canonicalizePluginConfigValues(values),
|
|
348
|
+
...(rawDigest ? { rawDigest } : {}),
|
|
349
|
+
issues: [],
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
export function assertReadyPluginConfigStore(store) {
|
|
353
|
+
if (store.state !== 'invalid')
|
|
354
|
+
return;
|
|
355
|
+
throw new Error(`${store.invalidCode}: Invalid plugin config store "${store.path}". ${store.issues.join(' ')}`);
|
|
356
|
+
}
|
|
357
|
+
async function parseStoreJson(filePath) {
|
|
358
|
+
const content = await fs.readFile(filePath);
|
|
359
|
+
const rawDigest = `sha256:${crypto.createHash('sha256').update(content).digest('hex')}`;
|
|
360
|
+
try {
|
|
361
|
+
return {
|
|
362
|
+
state: 'parsed',
|
|
363
|
+
value: JSON.parse(content.toString('utf8')),
|
|
364
|
+
rawDigest,
|
|
365
|
+
};
|
|
434
366
|
}
|
|
435
|
-
|
|
436
|
-
|
|
367
|
+
catch (error) {
|
|
368
|
+
if (error instanceof SyntaxError) {
|
|
369
|
+
return {
|
|
370
|
+
state: 'invalidJson',
|
|
371
|
+
store: invalidStore(filePath, 'invalidJson', 'The store is not valid JSON.', rawDigest),
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
throw error;
|
|
437
375
|
}
|
|
438
|
-
|
|
439
|
-
|
|
376
|
+
}
|
|
377
|
+
function validateStoreValues(options) {
|
|
378
|
+
if (!isRecord(options.values)) {
|
|
379
|
+
return invalidStore(options.filePath, 'invalidValues', 'The values field must be an object.', options.rawDigest);
|
|
440
380
|
}
|
|
441
|
-
|
|
381
|
+
const values = Object.create(null);
|
|
382
|
+
const issues = [];
|
|
383
|
+
for (const [key, value] of Object.entries(options.values)) {
|
|
384
|
+
if (!isPluginConfigValue(value)) {
|
|
385
|
+
issues.push(`Config "${key}" is not a supported config value.`);
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
values[key] = value;
|
|
389
|
+
}
|
|
390
|
+
return issues.length > 0
|
|
391
|
+
? invalidStore(options.filePath, 'invalidValues', issues.join(' '), options.rawDigest)
|
|
392
|
+
: readyStore(options.filePath, values, options.rawDigest);
|
|
393
|
+
}
|
|
394
|
+
async function readStoreFile(filePath) {
|
|
395
|
+
const parsed = await parseStoreJson(filePath);
|
|
396
|
+
if (parsed.state === 'invalidJson')
|
|
397
|
+
return parsed.store;
|
|
398
|
+
return validateStoreValues({ filePath, values: parsed.value, rawDigest: parsed.rawDigest });
|
|
399
|
+
}
|
|
400
|
+
async function readNamespacedStoreFile(options) {
|
|
401
|
+
const parsed = await parseStoreJson(options.filePath);
|
|
402
|
+
if (parsed.state === 'invalidJson')
|
|
403
|
+
return parsed.store;
|
|
404
|
+
const raw = parsed.value;
|
|
405
|
+
if (!isRecord(raw) || raw.schemaVersion !== 4) {
|
|
406
|
+
return invalidStore(options.filePath, 'schemaVersionUnsupported', 'Only plugin config store schemaVersion 4 is supported.', parsed.rawDigest);
|
|
407
|
+
}
|
|
408
|
+
const envelope = readPluginConfigEnvelopeV4(raw, options.scope);
|
|
409
|
+
if (!envelope) {
|
|
410
|
+
return invalidStore(options.filePath, 'schemaVersionUnsupported', 'The plugin config store schemaVersion 4 envelope is invalid.', parsed.rawDigest);
|
|
411
|
+
}
|
|
412
|
+
if (envelope.sourceId !== options.sourceId
|
|
413
|
+
|| envelope.pluginName !== options.pluginName) {
|
|
414
|
+
return invalidStore(options.filePath, 'identityMismatch', 'The store identity does not match its source and namespaced path.', parsed.rawDigest);
|
|
415
|
+
}
|
|
416
|
+
const binding = envelope.binding;
|
|
417
|
+
const matchesBinding = options.scope === 'user'
|
|
418
|
+
? binding?.kind === 'source'
|
|
419
|
+
&& typeof options.sourceIdentity === 'string'
|
|
420
|
+
&& binding.sourceIdentity === options.sourceIdentity
|
|
421
|
+
: binding?.kind === 'catalog'
|
|
422
|
+
&& typeof options.logicalIdentity === 'string'
|
|
423
|
+
&& binding.logicalIdentity === options.logicalIdentity;
|
|
424
|
+
if (!matchesBinding) {
|
|
425
|
+
return invalidStore(options.filePath, 'identityMismatch', options.scope === 'user'
|
|
426
|
+
? 'The user store is not bound to the requested source identity.'
|
|
427
|
+
: 'The project store is not bound to the requested Catalog logical identity.', parsed.rawDigest);
|
|
428
|
+
}
|
|
429
|
+
return validateStoreValues({
|
|
430
|
+
filePath: options.filePath,
|
|
431
|
+
values: envelope.values,
|
|
432
|
+
rawDigest: parsed.rawDigest,
|
|
433
|
+
});
|
|
442
434
|
}
|
|
443
435
|
function namespacedConfigContent(options) {
|
|
436
|
+
const values = canonicalizePluginConfigValues(options.values);
|
|
444
437
|
if (!options.sourceId)
|
|
445
|
-
return `${JSON.stringify(
|
|
446
|
-
|
|
447
|
-
|
|
438
|
+
return `${JSON.stringify(values, null, 2)}\n`;
|
|
439
|
+
const binding = options.scope === 'user'
|
|
440
|
+
? options.sourceIdentity
|
|
441
|
+
? { kind: 'source', sourceIdentity: options.sourceIdentity }
|
|
442
|
+
: (() => { throw new Error('sourceChanged: user plugin config requires a source identity.'); })()
|
|
443
|
+
: options.logicalIdentity
|
|
444
|
+
? { kind: 'catalog', logicalIdentity: options.logicalIdentity }
|
|
445
|
+
: (() => { throw new Error('catalogUnavailable: project plugin config requires verified Catalog logical identity evidence.'); })();
|
|
448
446
|
const envelope = {
|
|
449
|
-
schemaVersion:
|
|
447
|
+
schemaVersion: 4,
|
|
450
448
|
sourceId: options.sourceId,
|
|
451
|
-
sourceIdentity: options.sourceIdentity,
|
|
452
449
|
pluginName: options.pluginName,
|
|
453
|
-
|
|
450
|
+
binding,
|
|
451
|
+
values,
|
|
454
452
|
};
|
|
455
453
|
return `${JSON.stringify(envelope, null, 2)}\n`;
|
|
456
454
|
}
|
|
@@ -462,18 +460,12 @@ async function assertNoLegacyConfigCoexistence(options) {
|
|
|
462
460
|
scope: options.scope,
|
|
463
461
|
cwd: options.cwd,
|
|
464
462
|
env: options.env,
|
|
465
|
-
projectLayer: options.projectLayer,
|
|
466
463
|
...(options.scope === 'project' ? { access: 'read' } : {}),
|
|
467
464
|
});
|
|
468
465
|
if (await lstatIfExists(legacyPath)) {
|
|
469
466
|
throw new Error('legacyStateUnsupported: legacy and namespaced plugin config stores cannot coexist.');
|
|
470
467
|
}
|
|
471
468
|
}
|
|
472
|
-
async function withUserPluginConfigRuntimeLease(options, action) {
|
|
473
|
-
return options.scope === 'user' || options.sourceId !== undefined
|
|
474
|
-
? withActiveMarketplaceRuntimeLease(options.env, action)
|
|
475
|
-
: action();
|
|
476
|
-
}
|
|
477
469
|
export async function readPluginConfigStore(options) {
|
|
478
470
|
const filePath = resolvePluginConfigStorePath({
|
|
479
471
|
...options,
|
|
@@ -481,14 +473,11 @@ export async function readPluginConfigStore(options) {
|
|
|
481
473
|
});
|
|
482
474
|
const allowedRoot = resolvePluginConfigAllowedRoot(options);
|
|
483
475
|
if (options.sourceId) {
|
|
484
|
-
if (!options.sourceIdentity)
|
|
485
|
-
throw new Error('sourceIdentity is required for namespaced plugin config.');
|
|
486
476
|
const legacyPath = resolvePluginConfigStorePath({
|
|
487
477
|
pluginName: options.pluginName,
|
|
488
478
|
scope: options.scope,
|
|
489
479
|
cwd: options.cwd,
|
|
490
480
|
env: options.env,
|
|
491
|
-
projectLayer: options.projectLayer,
|
|
492
481
|
...(options.scope === 'project' ? { access: 'read' } : {}),
|
|
493
482
|
});
|
|
494
483
|
if (await inspectConfigReadTarget(allowedRoot, legacyPath)) {
|
|
@@ -499,21 +488,21 @@ export async function readPluginConfigStore(options) {
|
|
|
499
488
|
return {
|
|
500
489
|
path: filePath,
|
|
501
490
|
exists: false,
|
|
491
|
+
state: 'missing',
|
|
502
492
|
values: {},
|
|
493
|
+
issues: [],
|
|
503
494
|
};
|
|
504
495
|
}
|
|
505
|
-
return
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
: await readStoreFile(filePath),
|
|
516
|
-
};
|
|
496
|
+
return options.sourceId
|
|
497
|
+
? readNamespacedStoreFile({
|
|
498
|
+
filePath,
|
|
499
|
+
sourceId: options.sourceId,
|
|
500
|
+
scope: options.scope,
|
|
501
|
+
sourceIdentity: options.sourceIdentity,
|
|
502
|
+
logicalIdentity: options.logicalIdentity,
|
|
503
|
+
pluginName: options.pluginName,
|
|
504
|
+
})
|
|
505
|
+
: readStoreFile(filePath);
|
|
517
506
|
}
|
|
518
507
|
export async function readNamespacedPluginConfigStoreIdentity(options) {
|
|
519
508
|
const filePath = resolvePluginConfigStorePath({
|
|
@@ -522,15 +511,21 @@ export async function readNamespacedPluginConfigStoreIdentity(options) {
|
|
|
522
511
|
});
|
|
523
512
|
if (!await inspectConfigReadTarget(resolvePluginConfigAllowedRoot(options), filePath))
|
|
524
513
|
return null;
|
|
525
|
-
const
|
|
526
|
-
if (
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
514
|
+
const parsed = await parseStoreJson(filePath);
|
|
515
|
+
if (parsed.state === 'invalidJson') {
|
|
516
|
+
throw new Error(`${parsed.store.invalidCode}: invalid namespaced plugin config store.`);
|
|
517
|
+
}
|
|
518
|
+
const envelope = readPluginConfigEnvelopeV4(parsed.value, options.scope);
|
|
519
|
+
if (!envelope
|
|
520
|
+
|| envelope.sourceId !== options.sourceId
|
|
521
|
+
|| envelope.pluginName !== options.pluginName) {
|
|
522
|
+
throw new Error('schemaVersionUnsupported: invalid namespaced plugin config store.');
|
|
523
|
+
}
|
|
524
|
+
const store = validateStoreValues({ filePath, values: envelope.values });
|
|
525
|
+
assertReadyPluginConfigStore(store);
|
|
531
526
|
return {
|
|
532
|
-
|
|
533
|
-
store
|
|
527
|
+
binding: envelope.binding,
|
|
528
|
+
store,
|
|
534
529
|
};
|
|
535
530
|
}
|
|
536
531
|
async function listConfigStoreDirectoryPluginNames(storeRoot) {
|
|
@@ -561,15 +556,72 @@ export async function listPluginConfigStorePluginNames(options) {
|
|
|
561
556
|
const storeRoots = options.scope === 'user'
|
|
562
557
|
? [path.join(resolveAwHome(options.env), CONFIG_DIR)]
|
|
563
558
|
: options.scope === 'project'
|
|
564
|
-
? [path.join(projectRoot, '.agent-workflows',
|
|
559
|
+
? [path.join(projectRoot, '.agent-workflows', CONFIG_DIR)]
|
|
565
560
|
: [
|
|
566
561
|
path.join(resolveAwHome(options.env), CONFIG_DIR),
|
|
567
562
|
path.join(projectRoot, '.agent-workflows', CONFIG_DIR),
|
|
568
|
-
path.join(projectRoot, '.agent-workflows', 'plugin-config.local'),
|
|
569
563
|
];
|
|
570
564
|
const pluginNames = (await Promise.all(storeRoots.map((storeRoot) => listConfigStoreDirectoryPluginNames(storeRoot)))).flat();
|
|
571
565
|
return [...new Set(pluginNames)].sort((left, right) => left.localeCompare(right));
|
|
572
566
|
}
|
|
567
|
+
export async function listNamespacedPluginConfigStoreIdentities(options) {
|
|
568
|
+
const boundaryRoot = options.scope === 'user'
|
|
569
|
+
? resolveAwHome(options.env)
|
|
570
|
+
: resolveAwProjectRoot(options.cwd);
|
|
571
|
+
if (options.scope === 'user') {
|
|
572
|
+
await canonicalizePathThroughExistingAncestor(boundaryRoot);
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
await fs.realpath(boundaryRoot);
|
|
576
|
+
}
|
|
577
|
+
const storeRoot = path.join(boundaryRoot, ...(options.scope === 'project' ? ['.agent-workflows'] : []), CONFIG_DIR);
|
|
578
|
+
if (!await inspectConfigInventoryStoreRoot(boundaryRoot, storeRoot))
|
|
579
|
+
return [];
|
|
580
|
+
const output = [];
|
|
581
|
+
for (const sourceEntry of await fs.readdir(storeRoot, { withFileTypes: true })) {
|
|
582
|
+
if (!sourceEntry.isDirectory() || sourceEntry.isSymbolicLink())
|
|
583
|
+
continue;
|
|
584
|
+
let sourceId;
|
|
585
|
+
try {
|
|
586
|
+
sourceId = assertSafePluginConfigSegment(sourceEntry.name);
|
|
587
|
+
}
|
|
588
|
+
catch {
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
const sourceRoot = path.join(storeRoot, sourceId);
|
|
592
|
+
let pluginEntries;
|
|
593
|
+
try {
|
|
594
|
+
await assertNoConfigSymlink(boundaryRoot, sourceRoot);
|
|
595
|
+
pluginEntries = await fs.readdir(sourceRoot, { withFileTypes: true });
|
|
596
|
+
}
|
|
597
|
+
catch {
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
for (const pluginEntry of pluginEntries) {
|
|
601
|
+
if (!pluginEntry.isDirectory() || pluginEntry.isSymbolicLink())
|
|
602
|
+
continue;
|
|
603
|
+
let pluginName;
|
|
604
|
+
try {
|
|
605
|
+
pluginName = assertSafePluginConfigSegment(pluginEntry.name);
|
|
606
|
+
await assertNoConfigSymlink(boundaryRoot, path.join(sourceRoot, pluginName));
|
|
607
|
+
}
|
|
608
|
+
catch {
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
let target;
|
|
612
|
+
try {
|
|
613
|
+
target = await lstatIfExists(path.join(sourceRoot, pluginName, CONFIG_FILE));
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
if (!target)
|
|
619
|
+
continue;
|
|
620
|
+
output.push({ sourceId, pluginName, pluginId: `${sourceId}/${pluginName}` });
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return output.sort((left, right) => left.pluginId.localeCompare(right.pluginId));
|
|
624
|
+
}
|
|
573
625
|
export async function listNamespacedPluginConfigStores(options) {
|
|
574
626
|
const boundaryRoot = options.scope === 'user'
|
|
575
627
|
? resolveAwHome(options.env)
|
|
@@ -580,9 +632,7 @@ export async function listNamespacedPluginConfigStores(options) {
|
|
|
580
632
|
else {
|
|
581
633
|
await fs.realpath(boundaryRoot);
|
|
582
634
|
}
|
|
583
|
-
const storeRoot = path.join(boundaryRoot, ...(options.scope === 'project' ? ['.agent-workflows'] : []),
|
|
584
|
-
? 'plugin-config.local'
|
|
585
|
-
: CONFIG_DIR);
|
|
635
|
+
const storeRoot = path.join(boundaryRoot, ...(options.scope === 'project' ? ['.agent-workflows'] : []), CONFIG_DIR);
|
|
586
636
|
const output = [];
|
|
587
637
|
if (!await inspectConfigInventoryStoreRoot(boundaryRoot, storeRoot))
|
|
588
638
|
return output;
|
|
@@ -604,18 +654,24 @@ export async function listNamespacedPluginConfigStores(options) {
|
|
|
604
654
|
const filePath = path.join(storeRoot, sourceId, pluginName, CONFIG_FILE);
|
|
605
655
|
if (!await inspectConfigReadTarget(storeRoot, filePath))
|
|
606
656
|
continue;
|
|
607
|
-
const
|
|
608
|
-
if (
|
|
609
|
-
|
|
610
|
-
|| typeof raw.sourceIdentity !== 'string') {
|
|
611
|
-
throw new Error(`legacyStateUnsupported: invalid namespaced plugin config store "${filePath}".`);
|
|
657
|
+
const parsed = await parseStoreJson(filePath);
|
|
658
|
+
if (parsed.state === 'invalidJson') {
|
|
659
|
+
throw new Error(`${parsed.store.invalidCode}: invalid namespaced plugin config store "${filePath}".`);
|
|
612
660
|
}
|
|
661
|
+
const envelope = readPluginConfigEnvelopeV4(parsed.value, options.scope);
|
|
662
|
+
if (!envelope
|
|
663
|
+
|| envelope.sourceId !== sourceId
|
|
664
|
+
|| envelope.pluginName !== pluginName) {
|
|
665
|
+
throw new Error(`schemaVersionUnsupported: invalid namespaced plugin config store "${filePath}".`);
|
|
666
|
+
}
|
|
667
|
+
const store = validateStoreValues({ filePath, values: envelope.values });
|
|
668
|
+
assertReadyPluginConfigStore(store);
|
|
613
669
|
output.push({
|
|
614
670
|
sourceId,
|
|
615
|
-
|
|
671
|
+
binding: envelope.binding,
|
|
616
672
|
pluginName,
|
|
617
673
|
pluginId: `${sourceId}/${pluginName}`,
|
|
618
|
-
values:
|
|
674
|
+
values: store.values,
|
|
619
675
|
});
|
|
620
676
|
}
|
|
621
677
|
}
|
|
@@ -628,22 +684,39 @@ export async function writePluginConfigStore(options) {
|
|
|
628
684
|
...(options.scope === 'project' ? { access: 'write' } : {}),
|
|
629
685
|
});
|
|
630
686
|
const allowedRoot = resolvePluginConfigAllowedRoot(options);
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
687
|
+
const validated = validateStoreValues({ filePath, values: options.values });
|
|
688
|
+
assertReadyPluginConfigStore(validated);
|
|
689
|
+
const warnings = [];
|
|
690
|
+
await withConfigFileMutation({
|
|
691
|
+
filePath,
|
|
692
|
+
allowedRoot,
|
|
693
|
+
action: async () => {
|
|
694
|
+
if (Object.keys(validated.values).length === 0) {
|
|
695
|
+
return atomicRemoveConfigFile({ filePath, allowedRoot });
|
|
696
|
+
}
|
|
697
|
+
if (options.scope === 'project') {
|
|
698
|
+
const visibility = await assertProjectSharedConfigPathVisible({
|
|
699
|
+
cwd: options.cwd,
|
|
700
|
+
targetPath: filePath,
|
|
701
|
+
env: options.env,
|
|
702
|
+
});
|
|
703
|
+
if (visibility?.state === 'unverified') {
|
|
704
|
+
warnings.push(PROJECT_CONFIG_SHAREABILITY_UNVERIFIED_WARNING);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return atomicReplaceConfigFile({
|
|
636
708
|
filePath,
|
|
637
709
|
allowedRoot,
|
|
638
|
-
content: namespacedConfigContent(options),
|
|
710
|
+
content: namespacedConfigContent({ ...options, values: validated.values }),
|
|
639
711
|
mode: 0o600,
|
|
640
|
-
})
|
|
641
|
-
}
|
|
712
|
+
});
|
|
713
|
+
},
|
|
642
714
|
});
|
|
643
715
|
return {
|
|
644
716
|
pluginName: options.pluginName,
|
|
645
717
|
scope: options.scope,
|
|
646
718
|
path: filePath,
|
|
719
|
+
warnings,
|
|
647
720
|
};
|
|
648
721
|
}
|
|
649
722
|
export async function patchPluginConfigStore(options) {
|
|
@@ -654,33 +727,50 @@ export async function patchPluginConfigStore(options) {
|
|
|
654
727
|
});
|
|
655
728
|
const allowedRoot = resolvePluginConfigAllowedRoot(options);
|
|
656
729
|
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
|
-
|
|
730
|
+
const warnings = [];
|
|
731
|
+
await withConfigFileMutation({
|
|
732
|
+
filePath,
|
|
733
|
+
allowedRoot,
|
|
734
|
+
action: async () => {
|
|
735
|
+
await options.reviewPreflight?.();
|
|
736
|
+
const current = (await lstatIfExists(filePath))
|
|
737
|
+
? options.sourceId
|
|
738
|
+
? await readNamespacedStoreFile({
|
|
739
|
+
filePath,
|
|
740
|
+
sourceId: options.sourceId,
|
|
741
|
+
scope: options.scope,
|
|
742
|
+
sourceIdentity: options.sourceIdentity,
|
|
743
|
+
logicalIdentity: options.logicalIdentity,
|
|
744
|
+
pluginName: options.pluginName,
|
|
745
|
+
})
|
|
746
|
+
: await readStoreFile(filePath)
|
|
747
|
+
: readyStore(filePath, {});
|
|
748
|
+
assertReadyPluginConfigStore(current);
|
|
749
|
+
if (options.requireExistingRemoveKeys) {
|
|
750
|
+
const missingKey = removedKeys.find((key) => !Object.hasOwn(current.values, key));
|
|
751
|
+
if (missingKey)
|
|
752
|
+
throw new Error(`Unknown config key "${missingKey}".`);
|
|
753
|
+
}
|
|
754
|
+
const next = { ...current.values };
|
|
755
|
+
for (const key of removedKeys) {
|
|
756
|
+
delete next[key];
|
|
757
|
+
}
|
|
758
|
+
for (const [key, value] of Object.entries(options.updates)) {
|
|
759
|
+
next[key] = value;
|
|
760
|
+
}
|
|
761
|
+
if (Object.keys(next).length === 0) {
|
|
762
|
+
await atomicRemoveConfigFile({ filePath, allowedRoot });
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
if (options.scope === 'project') {
|
|
766
|
+
const visibility = await assertProjectSharedConfigPathVisible({
|
|
767
|
+
cwd: options.cwd,
|
|
768
|
+
targetPath: filePath,
|
|
769
|
+
env: options.env,
|
|
770
|
+
});
|
|
771
|
+
if (visibility?.state === 'unverified') {
|
|
772
|
+
warnings.push(PROJECT_CONFIG_SHAREABILITY_UNVERIFIED_WARNING);
|
|
773
|
+
}
|
|
684
774
|
}
|
|
685
775
|
await atomicReplaceConfigFile({
|
|
686
776
|
filePath,
|
|
@@ -688,65 +778,170 @@ export async function patchPluginConfigStore(options) {
|
|
|
688
778
|
content: namespacedConfigContent({ ...options, values: next }),
|
|
689
779
|
mode: 0o600,
|
|
690
780
|
});
|
|
691
|
-
}
|
|
692
|
-
}
|
|
781
|
+
}
|
|
782
|
+
},
|
|
693
783
|
});
|
|
694
784
|
return {
|
|
695
785
|
pluginName: options.pluginName,
|
|
696
786
|
scope: options.scope,
|
|
697
787
|
path: filePath,
|
|
788
|
+
warnings,
|
|
698
789
|
updatedKeys: Object.keys(options.updates),
|
|
699
790
|
removedKeys,
|
|
700
791
|
};
|
|
701
792
|
}
|
|
702
793
|
export async function resetPluginConfigStore(options) {
|
|
703
|
-
await assertNoLegacyConfigCoexistence(options);
|
|
704
794
|
const filePath = resolvePluginConfigStorePath({
|
|
705
795
|
...options,
|
|
706
796
|
...(options.scope === 'project' ? { access: 'write' } : {}),
|
|
707
797
|
});
|
|
708
798
|
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
|
-
|
|
799
|
+
const publicTarget = `${options.scope}/plugin-config/${[
|
|
800
|
+
options.sourceId,
|
|
801
|
+
options.pluginName,
|
|
802
|
+
CONFIG_FILE,
|
|
803
|
+
].filter(Boolean).join('/')}`;
|
|
804
|
+
const { root, candidate } = assertConfigPathBoundary(allowedRoot, filePath);
|
|
805
|
+
let current = root;
|
|
806
|
+
try {
|
|
807
|
+
for (const segment of path.relative(root, path.dirname(candidate)).split(path.sep).filter(Boolean)) {
|
|
808
|
+
const stats = await lstatIfExists(current);
|
|
809
|
+
if (!stats) {
|
|
810
|
+
return {
|
|
811
|
+
pluginName: options.pluginName,
|
|
812
|
+
scope: options.scope,
|
|
813
|
+
storeKind: options.scope,
|
|
814
|
+
path: filePath,
|
|
815
|
+
existed: false,
|
|
816
|
+
deleted: false,
|
|
817
|
+
resetDisposition: 'absent',
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
if (stats.isSymbolicLink() || !stats.isDirectory()) {
|
|
821
|
+
throw new PluginConfigResetError('configResetTargetUnsupported', `${publicTarget} has an unsafe ancestor. Repair it manually, then retry config reset.`);
|
|
822
|
+
}
|
|
823
|
+
current = path.join(current, segment);
|
|
824
|
+
}
|
|
825
|
+
const parentStats = await lstatIfExists(current);
|
|
826
|
+
if (!parentStats) {
|
|
827
|
+
return {
|
|
828
|
+
pluginName: options.pluginName,
|
|
829
|
+
scope: options.scope,
|
|
830
|
+
storeKind: options.scope,
|
|
831
|
+
path: filePath,
|
|
832
|
+
existed: false,
|
|
833
|
+
deleted: false,
|
|
834
|
+
resetDisposition: 'absent',
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
if (parentStats.isSymbolicLink() || !parentStats.isDirectory()) {
|
|
838
|
+
throw new PluginConfigResetError('configResetTargetUnsupported', `${publicTarget} has an unsafe ancestor. Repair it manually, then retry config reset.`);
|
|
839
|
+
}
|
|
840
|
+
await options.reviewPreflight?.();
|
|
841
|
+
const target = await lstatIfExists(filePath);
|
|
842
|
+
if (!target) {
|
|
843
|
+
return {
|
|
844
|
+
pluginName: options.pluginName,
|
|
845
|
+
scope: options.scope,
|
|
846
|
+
storeKind: options.scope,
|
|
847
|
+
path: filePath,
|
|
848
|
+
existed: false,
|
|
849
|
+
deleted: false,
|
|
850
|
+
resetDisposition: 'absent',
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
if (target.isFile() || target.isSymbolicLink()) {
|
|
854
|
+
await assertNoConfigSymlink(allowedRoot, path.dirname(filePath));
|
|
855
|
+
await nodeFs.unlink(filePath);
|
|
856
|
+
return {
|
|
857
|
+
pluginName: options.pluginName,
|
|
858
|
+
scope: options.scope,
|
|
859
|
+
storeKind: options.scope,
|
|
860
|
+
path: filePath,
|
|
861
|
+
existed: true,
|
|
862
|
+
deleted: true,
|
|
863
|
+
resetDisposition: target.isSymbolicLink() ? 'unlinkedSymlink' : 'deletedFile',
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
if (target.isDirectory()) {
|
|
867
|
+
const timestamp = new Date().toISOString().replace(/[-:]/gu, '').replace(/\..*$/u, '');
|
|
868
|
+
const quarantineBasename = `config.broken-${timestamp}-${crypto.randomUUID()}.json`;
|
|
869
|
+
await assertNoConfigSymlink(allowedRoot, path.dirname(filePath));
|
|
870
|
+
await nodeFs.rename(filePath, path.join(path.dirname(filePath), quarantineBasename));
|
|
871
|
+
return {
|
|
872
|
+
pluginName: options.pluginName,
|
|
873
|
+
scope: options.scope,
|
|
874
|
+
storeKind: options.scope,
|
|
875
|
+
path: filePath,
|
|
876
|
+
existed: true,
|
|
877
|
+
deleted: true,
|
|
878
|
+
resetDisposition: 'quarantinedDirectory',
|
|
879
|
+
quarantineBasename,
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
throw new PluginConfigResetError('configResetTargetUnsupported', `${publicTarget} is a special filesystem node. Move it manually, then retry config reset.`);
|
|
883
|
+
}
|
|
884
|
+
catch (error) {
|
|
885
|
+
if (error instanceof PluginConfigResetError)
|
|
886
|
+
throw error;
|
|
887
|
+
const osCode = error.code;
|
|
888
|
+
throw new PluginConfigResetError('configResetFailed', `Cannot reset ${publicTarget}${osCode ? ` (${osCode})` : ''}. Fix filesystem access, then retry config reset.`, osCode);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
export async function inspectPluginConfigResetTarget(options) {
|
|
892
|
+
const filePath = resolvePluginConfigStorePath({
|
|
893
|
+
...options,
|
|
894
|
+
...(options.scope === 'project' ? { access: 'write' } : {}),
|
|
738
895
|
});
|
|
896
|
+
const allowedRoot = resolvePluginConfigAllowedRoot(options);
|
|
897
|
+
const publicTarget = `${options.scope}/plugin-config/${[
|
|
898
|
+
options.sourceId,
|
|
899
|
+
options.pluginName,
|
|
900
|
+
CONFIG_FILE,
|
|
901
|
+
].filter(Boolean).join('/')}`;
|
|
902
|
+
const { root, candidate } = assertConfigPathBoundary(allowedRoot, filePath);
|
|
903
|
+
let current = root;
|
|
904
|
+
for (const segment of path.relative(root, path.dirname(candidate)).split(path.sep).filter(Boolean)) {
|
|
905
|
+
const stats = await lstatIfExists(current);
|
|
906
|
+
if (!stats)
|
|
907
|
+
return resetTargetInspection('absent');
|
|
908
|
+
if (stats.isSymbolicLink() || !stats.isDirectory()) {
|
|
909
|
+
throw new PluginConfigResetError('configResetTargetUnsupported', `${publicTarget} has an unsafe ancestor. Repair it manually, then retry config reset.`);
|
|
910
|
+
}
|
|
911
|
+
current = path.join(current, segment);
|
|
912
|
+
}
|
|
913
|
+
const parentStats = await lstatIfExists(current);
|
|
914
|
+
if (!parentStats)
|
|
915
|
+
return resetTargetInspection('absent');
|
|
916
|
+
if (parentStats.isSymbolicLink() || !parentStats.isDirectory()) {
|
|
917
|
+
throw new PluginConfigResetError('configResetTargetUnsupported', `${publicTarget} has an unsafe ancestor. Repair it manually, then retry config reset.`);
|
|
918
|
+
}
|
|
919
|
+
const target = await lstatIfExists(filePath);
|
|
920
|
+
if (!target)
|
|
921
|
+
return resetTargetInspection('absent');
|
|
922
|
+
const kind = target.isSymbolicLink() ? 'symbolicLink'
|
|
923
|
+
: target.isFile() ? 'regularFile'
|
|
924
|
+
: target.isDirectory() ? 'directory'
|
|
925
|
+
: 'special';
|
|
926
|
+
return resetTargetInspection(kind, target);
|
|
927
|
+
}
|
|
928
|
+
function resetTargetInspection(kind, stats) {
|
|
929
|
+
const fingerprint = `sha256:${crypto.createHash('sha256').update(JSON.stringify({
|
|
930
|
+
kind,
|
|
931
|
+
...(stats ? {
|
|
932
|
+
dev: stats.dev,
|
|
933
|
+
ino: stats.ino,
|
|
934
|
+
mode: stats.mode,
|
|
935
|
+
size: stats.size,
|
|
936
|
+
mtimeMs: stats.mtimeMs,
|
|
937
|
+
ctimeMs: stats.ctimeMs,
|
|
938
|
+
} : {}),
|
|
939
|
+
})).digest('hex')}`;
|
|
739
940
|
return {
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
: options.projectLayer === 'local'
|
|
745
|
-
? 'projectLocal'
|
|
746
|
-
: 'projectShared',
|
|
747
|
-
path: filePath,
|
|
748
|
-
existed,
|
|
749
|
-
deleted: existed,
|
|
941
|
+
exists: kind !== 'absent',
|
|
942
|
+
kind,
|
|
943
|
+
resettable: kind !== 'special',
|
|
944
|
+
fingerprint,
|
|
750
945
|
};
|
|
751
946
|
}
|
|
752
947
|
export async function resetPluginConfigStores(options) {
|
|
@@ -757,228 +952,167 @@ export async function resetPluginConfigStores(options) {
|
|
|
757
952
|
});
|
|
758
953
|
const allowedRoot = resolvePluginConfigAllowedRoot(options);
|
|
759
954
|
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(', ')}.`);
|
|
955
|
+
const existing = [];
|
|
956
|
+
for (const target of targets) {
|
|
957
|
+
await prepareConfigFileParent(target.path, allowedRoot);
|
|
958
|
+
await assertNoConfigSymlink(allowedRoot, target.path);
|
|
959
|
+
const stats = await lstatIfExists(target.path);
|
|
960
|
+
existing.push(stats !== null);
|
|
961
|
+
if (!stats)
|
|
962
|
+
continue;
|
|
963
|
+
if (!stats.isFile() || stats.isSymbolicLink()) {
|
|
964
|
+
throw new Error(`Config target must be a regular file: ${target.path}`);
|
|
859
965
|
}
|
|
860
|
-
return value;
|
|
861
966
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
967
|
+
for (const [index, target] of targets.entries()) {
|
|
968
|
+
const deleted = await atomicRemoveConfigFile({
|
|
969
|
+
filePath: target.path,
|
|
970
|
+
allowedRoot,
|
|
971
|
+
});
|
|
972
|
+
receipts.push({
|
|
973
|
+
pluginName: options.pluginName,
|
|
974
|
+
scope: options.scope,
|
|
975
|
+
storeKind: target.storeKind,
|
|
976
|
+
path: target.path,
|
|
977
|
+
existed: existing[index],
|
|
978
|
+
deleted,
|
|
979
|
+
resetDisposition: deleted ? 'deletedFile' : 'absent',
|
|
980
|
+
});
|
|
867
981
|
}
|
|
868
|
-
|
|
982
|
+
return receipts;
|
|
869
983
|
}
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
984
|
+
function coerceConfigValue(key, field, value) {
|
|
985
|
+
let coerced = value;
|
|
986
|
+
if (field.type === 'number' && typeof value === 'string' && value.trim() !== '') {
|
|
987
|
+
coerced = Number(value);
|
|
873
988
|
}
|
|
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.`);
|
|
989
|
+
else if (field.type === 'boolean' && typeof value === 'string') {
|
|
990
|
+
const normalized = value.trim().toLowerCase();
|
|
991
|
+
if (['true', '1', 'yes', 'y'].includes(normalized))
|
|
992
|
+
coerced = true;
|
|
993
|
+
if (['false', '0', 'no', 'n'].includes(normalized))
|
|
994
|
+
coerced = false;
|
|
882
995
|
}
|
|
883
|
-
|
|
884
|
-
|
|
996
|
+
const issues = validateCorePluginConfigValue(field, coerced, key);
|
|
997
|
+
if (issues.length > 0) {
|
|
998
|
+
throw new Error(formatPluginConfigIssue(issues[0]));
|
|
885
999
|
}
|
|
886
|
-
return
|
|
1000
|
+
return coerced;
|
|
887
1001
|
}
|
|
888
1002
|
export async function coercePluginConfigValue(options) {
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
projectRoot: options.projectRoot,
|
|
896
|
-
});
|
|
1003
|
+
return coerceConfigValue(options.key, options.field, options.value);
|
|
1004
|
+
}
|
|
1005
|
+
function validateConfigReadValue(key, field, value) {
|
|
1006
|
+
const issues = validateCorePluginConfigValue(field, value, key);
|
|
1007
|
+
if (issues.length > 0) {
|
|
1008
|
+
throw new Error(formatPluginConfigIssue(issues[0]));
|
|
897
1009
|
}
|
|
898
|
-
return
|
|
1010
|
+
return value;
|
|
899
1011
|
}
|
|
900
1012
|
function redactValue(field, value) {
|
|
901
|
-
return field
|
|
1013
|
+
return redactPluginConfigValue(field, value);
|
|
902
1014
|
}
|
|
903
|
-
function sourceForStoreLayer(scope
|
|
904
|
-
|
|
905
|
-
return 'user';
|
|
906
|
-
}
|
|
907
|
-
return projectLayer === 'local' ? 'projectLocal' : 'project';
|
|
908
|
-
}
|
|
909
|
-
function isPathTypeField(field) {
|
|
910
|
-
return field.type === 'file' || field.type === 'directory';
|
|
1015
|
+
function sourceForStoreLayer(scope) {
|
|
1016
|
+
return scope === 'user' ? 'user' : 'project';
|
|
911
1017
|
}
|
|
912
1018
|
async function applyConfigLayer(options) {
|
|
913
1019
|
for (const [key, field] of Object.entries(options.schema)) {
|
|
914
1020
|
if (!Object.hasOwn(options.entries, key)) {
|
|
915
1021
|
continue;
|
|
916
1022
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
});
|
|
924
|
-
options.values[key] = coerced;
|
|
925
|
-
options.redactedValues[key] = redactValue(field, coerced);
|
|
1023
|
+
const value = options.entries[key];
|
|
1024
|
+
const validationIssues = validateCorePluginConfigValue(field, value, key);
|
|
1025
|
+
if (validationIssues.length === 0) {
|
|
1026
|
+
const redactedValue = redactValue(field, value);
|
|
1027
|
+
options.values[key] = value;
|
|
1028
|
+
options.redactedValues[key] = redactedValue;
|
|
926
1029
|
options.sources[key] = options.source;
|
|
927
|
-
options.
|
|
1030
|
+
options.fieldStates[key] = {
|
|
1031
|
+
effective: { value, redactedValue, source: options.source },
|
|
1032
|
+
};
|
|
928
1033
|
}
|
|
929
|
-
|
|
1034
|
+
else {
|
|
1035
|
+
const fallback = options.fieldStates[key]?.effective;
|
|
930
1036
|
delete options.values[key];
|
|
931
1037
|
delete options.redactedValues[key];
|
|
932
1038
|
options.sources[key] = options.source;
|
|
933
|
-
|
|
1039
|
+
const issues = validationIssues.map((issue) => ({
|
|
934
1040
|
type: 'invalid-config',
|
|
935
1041
|
key,
|
|
936
1042
|
source: options.source,
|
|
937
|
-
message:
|
|
938
|
-
});
|
|
1043
|
+
message: formatPluginConfigIssue(issue),
|
|
1044
|
+
}));
|
|
1045
|
+
options.fieldStates[key] = {
|
|
1046
|
+
invalidOverride: { source: options.source, issues },
|
|
1047
|
+
...(fallback ? { fallback } : {}),
|
|
1048
|
+
};
|
|
939
1049
|
}
|
|
940
1050
|
}
|
|
941
1051
|
}
|
|
942
1052
|
export async function resolveEffectivePluginConfig(options) {
|
|
943
1053
|
const pluginName = assertSafePluginConfigSegment(options.pluginName ?? options.manifest.name);
|
|
944
|
-
const schema = options.manifest.config?.
|
|
1054
|
+
const schema = options.manifest.config?.fields;
|
|
945
1055
|
if (!schema) {
|
|
946
1056
|
throw new Error(`Plugin "${pluginName}" does not declare an AW config schema.`);
|
|
947
1057
|
}
|
|
948
1058
|
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({
|
|
1059
|
+
const userStore = await readPluginConfigStore({
|
|
1060
|
+
sourceId: options.sourceId,
|
|
1061
|
+
sourceIdentity: options.sourceIdentity,
|
|
1062
|
+
logicalIdentity: options.logicalIdentity,
|
|
1063
|
+
pluginName,
|
|
1064
|
+
scope: 'user',
|
|
1065
|
+
cwd: options.cwd,
|
|
1066
|
+
env: options.env,
|
|
1067
|
+
});
|
|
1068
|
+
const projectStore = options.targetScope === 'user'
|
|
1069
|
+
? undefined
|
|
1070
|
+
: await readPluginConfigStore({
|
|
967
1071
|
sourceId: options.sourceId,
|
|
968
1072
|
sourceIdentity: options.sourceIdentity,
|
|
1073
|
+
logicalIdentity: options.logicalIdentity,
|
|
969
1074
|
pluginName,
|
|
970
1075
|
scope: 'project',
|
|
971
1076
|
cwd: options.cwd,
|
|
972
1077
|
env: options.env,
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1078
|
+
});
|
|
1079
|
+
assertReadyPluginConfigStore(userStore);
|
|
1080
|
+
if (projectStore)
|
|
1081
|
+
assertReadyPluginConfigStore(projectStore);
|
|
1082
|
+
return resolveEffectivePluginConfigFromStores({
|
|
1083
|
+
pluginName,
|
|
1084
|
+
manifest: options.manifest,
|
|
1085
|
+
targetScope: options.targetScope,
|
|
1086
|
+
projectRoot,
|
|
1087
|
+
stores: {
|
|
1088
|
+
user: userStore,
|
|
1089
|
+
...(projectStore ? { project: projectStore } : {}),
|
|
1090
|
+
},
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
export async function resolveEffectivePluginConfigFromStores(options) {
|
|
1094
|
+
const pluginName = assertSafePluginConfigSegment(options.pluginName ?? options.manifest.name);
|
|
1095
|
+
const schema = options.manifest.config?.fields;
|
|
1096
|
+
if (!schema) {
|
|
1097
|
+
throw new Error(`Plugin "${pluginName}" does not declare an AW config schema.`);
|
|
1098
|
+
}
|
|
1099
|
+
const userStore = options.stores.user;
|
|
1100
|
+
const projectStore = options.targetScope === 'user' ? undefined : options.stores.project;
|
|
1101
|
+
assertReadyPluginConfigStore(userStore);
|
|
1102
|
+
if (!projectStore && options.targetScope !== 'user') {
|
|
1103
|
+
throw new Error(`Project plugin config store is required for ${pluginName}.`);
|
|
1104
|
+
}
|
|
1105
|
+
if (projectStore)
|
|
1106
|
+
assertReadyPluginConfigStore(projectStore);
|
|
976
1107
|
const values = {};
|
|
977
1108
|
const redactedValues = {};
|
|
978
1109
|
const sources = {};
|
|
979
|
-
const
|
|
1110
|
+
const fieldStates = {};
|
|
980
1111
|
const ignoredUnknownKeys = [];
|
|
1112
|
+
const unsupportedLayerOverrides = [];
|
|
1113
|
+
const dangerousKeyIssues = [];
|
|
981
1114
|
const schemaKeys = new Set(Object.keys(schema));
|
|
1115
|
+
const userOnlyKeys = codexNativeRefreshKeys(options.manifest);
|
|
982
1116
|
for (const key of schemaKeys) {
|
|
983
1117
|
sources[key] = 'missing';
|
|
984
1118
|
}
|
|
@@ -990,43 +1124,69 @@ export async function resolveEffectivePluginConfig(options) {
|
|
|
990
1124
|
values,
|
|
991
1125
|
redactedValues,
|
|
992
1126
|
sources,
|
|
993
|
-
|
|
1127
|
+
fieldStates,
|
|
994
1128
|
schema: { [key]: field },
|
|
995
1129
|
entries: { [key]: field.default },
|
|
996
1130
|
source: 'default',
|
|
997
|
-
projectRoot,
|
|
998
1131
|
});
|
|
999
1132
|
}
|
|
1000
|
-
|
|
1001
|
-
['user', userStore
|
|
1002
|
-
[
|
|
1003
|
-
|
|
1004
|
-
|
|
1133
|
+
const layers = options.targetScope === 'user'
|
|
1134
|
+
? [['user', userStore]]
|
|
1135
|
+
: [
|
|
1136
|
+
['user', userStore],
|
|
1137
|
+
['project', projectStore],
|
|
1138
|
+
];
|
|
1139
|
+
for (const [scope, store] of layers) {
|
|
1005
1140
|
const plainEntries = getStoreEntries(store.values);
|
|
1141
|
+
const source = sourceForStoreLayer(scope);
|
|
1142
|
+
const dangerousIssues = new Map(validatePluginConfigDocument({ fields: schema }, plainEntries).filter((issue) => issue.code === 'dangerousKey')
|
|
1143
|
+
.map((issue) => [issue.path.field, issue]));
|
|
1006
1144
|
for (const key of Object.keys(plainEntries)) {
|
|
1007
1145
|
if (!schemaKeys.has(key)) {
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1146
|
+
const dangerousIssue = dangerousIssues.get(key);
|
|
1147
|
+
if (dangerousIssue) {
|
|
1148
|
+
dangerousKeyIssues.push({
|
|
1149
|
+
type: 'invalid-config',
|
|
1150
|
+
code: dangerousIssue.code,
|
|
1151
|
+
key,
|
|
1152
|
+
source,
|
|
1153
|
+
message: formatPluginConfigIssue(dangerousIssue),
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
else {
|
|
1157
|
+
ignoredUnknownKeys.push({ scope, source, key });
|
|
1158
|
+
}
|
|
1014
1159
|
}
|
|
1015
1160
|
}
|
|
1161
|
+
const supportedEntries = scope === 'project'
|
|
1162
|
+
? Object.fromEntries(Object.entries(plainEntries).filter(([key]) => {
|
|
1163
|
+
if (!schemaKeys.has(key) || !userOnlyKeys.has(key))
|
|
1164
|
+
return true;
|
|
1165
|
+
unsupportedLayerOverrides.push({
|
|
1166
|
+
type: 'configLayerUnsupported',
|
|
1167
|
+
key,
|
|
1168
|
+
scope: 'project',
|
|
1169
|
+
source: 'project',
|
|
1170
|
+
});
|
|
1171
|
+
return false;
|
|
1172
|
+
}))
|
|
1173
|
+
: plainEntries;
|
|
1016
1174
|
await applyConfigLayer({
|
|
1017
1175
|
values,
|
|
1018
1176
|
redactedValues,
|
|
1019
1177
|
sources,
|
|
1020
|
-
|
|
1178
|
+
fieldStates,
|
|
1021
1179
|
schema,
|
|
1022
|
-
entries:
|
|
1023
|
-
source
|
|
1024
|
-
projectRoot,
|
|
1180
|
+
entries: supportedEntries,
|
|
1181
|
+
source,
|
|
1025
1182
|
});
|
|
1026
1183
|
}
|
|
1027
|
-
const issues = [
|
|
1184
|
+
const issues = [
|
|
1185
|
+
...Object.values(fieldStates).flatMap((state) => state.invalidOverride?.issues ?? []),
|
|
1186
|
+
...dangerousKeyIssues,
|
|
1187
|
+
];
|
|
1028
1188
|
for (const [key, field] of Object.entries(schema)) {
|
|
1029
|
-
if (field.required && values[key] === undefined) {
|
|
1189
|
+
if (field.required && values[key] === undefined && !fieldStates[key]?.invalidOverride) {
|
|
1030
1190
|
issues.push({
|
|
1031
1191
|
type: 'missing-required',
|
|
1032
1192
|
key,
|
|
@@ -1037,314 +1197,98 @@ export async function resolveEffectivePluginConfig(options) {
|
|
|
1037
1197
|
}
|
|
1038
1198
|
return {
|
|
1039
1199
|
pluginName,
|
|
1040
|
-
projectRoot,
|
|
1200
|
+
projectRoot: options.projectRoot,
|
|
1041
1201
|
values,
|
|
1042
1202
|
redactedValues,
|
|
1043
1203
|
sources,
|
|
1204
|
+
fieldStates,
|
|
1044
1205
|
issues,
|
|
1206
|
+
unsupportedLayerOverrides,
|
|
1045
1207
|
ignoredUnknownKeys,
|
|
1046
1208
|
schema,
|
|
1047
1209
|
stores: {
|
|
1048
1210
|
user: userStore,
|
|
1049
|
-
project: projectStore,
|
|
1050
|
-
projectLocal: projectLocalStore,
|
|
1211
|
+
...(projectStore ? { project: projectStore } : {}),
|
|
1051
1212
|
},
|
|
1052
1213
|
};
|
|
1053
1214
|
}
|
|
1054
1215
|
export async function coercePluginConfigUpdates(options) {
|
|
1055
|
-
const projectRoot = resolveAwProjectRoot(options.cwd);
|
|
1056
1216
|
const output = {};
|
|
1057
1217
|
for (const [key, value] of Object.entries(options.updates)) {
|
|
1058
1218
|
const field = options.schema[key];
|
|
1059
1219
|
if (!field) {
|
|
1060
1220
|
throw new Error(`Unknown config key "${key}".`);
|
|
1061
1221
|
}
|
|
1062
|
-
|
|
1222
|
+
const coerced = await coercePluginConfigValue({
|
|
1063
1223
|
key,
|
|
1064
1224
|
field,
|
|
1065
1225
|
value,
|
|
1066
|
-
projectRoot,
|
|
1067
1226
|
});
|
|
1227
|
+
output[key] = coerced;
|
|
1068
1228
|
}
|
|
1069
1229
|
return output;
|
|
1070
1230
|
}
|
|
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
1231
|
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) {
|
|
1232
|
+
if (!options.manifest.config?.fields) {
|
|
1233
|
+
const pluginName = assertSafePluginConfigSegment(options.pluginName ?? options.manifest.name);
|
|
1234
|
+
const [userStore, projectStore] = await Promise.all([
|
|
1235
|
+
readPluginConfigStore({
|
|
1236
|
+
sourceId: options.sourceId,
|
|
1237
|
+
sourceIdentity: options.sourceIdentity,
|
|
1238
|
+
logicalIdentity: options.logicalIdentity,
|
|
1239
|
+
pluginName,
|
|
1240
|
+
scope: 'user',
|
|
1241
|
+
cwd: options.cwd,
|
|
1242
|
+
env: options.env,
|
|
1243
|
+
}),
|
|
1244
|
+
readPluginConfigStore({
|
|
1245
|
+
sourceId: options.sourceId,
|
|
1246
|
+
sourceIdentity: options.sourceIdentity,
|
|
1247
|
+
logicalIdentity: options.logicalIdentity,
|
|
1248
|
+
pluginName,
|
|
1249
|
+
scope: 'project',
|
|
1250
|
+
cwd: options.cwd,
|
|
1251
|
+
env: options.env,
|
|
1252
|
+
}),
|
|
1253
|
+
]);
|
|
1254
|
+
assertReadyPluginConfigStore(userStore);
|
|
1255
|
+
assertReadyPluginConfigStore(projectStore);
|
|
1177
1256
|
return {
|
|
1178
1257
|
pluginName,
|
|
1179
|
-
projectRoot,
|
|
1258
|
+
projectRoot: resolveAwProjectRoot(options.cwd),
|
|
1180
1259
|
values: {},
|
|
1181
1260
|
sources: {},
|
|
1182
1261
|
issues: [],
|
|
1262
|
+
unsupportedLayerOverrides: [],
|
|
1183
1263
|
ignoredUnknownKeys: [],
|
|
1184
|
-
boundaryIssues: [],
|
|
1185
1264
|
schema: {},
|
|
1186
|
-
stores: {
|
|
1187
|
-
user: userStore,
|
|
1188
|
-
project: projectStore,
|
|
1189
|
-
projectLocal: projectLocalStore,
|
|
1190
|
-
},
|
|
1265
|
+
stores: { user: userStore, project: projectStore },
|
|
1191
1266
|
};
|
|
1192
1267
|
}
|
|
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,
|
|
1268
|
+
const effective = await resolveEffectivePluginConfig({
|
|
1269
|
+
...options,
|
|
1270
|
+
targetScope: 'project',
|
|
1248
1271
|
});
|
|
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
1272
|
return {
|
|
1321
1273
|
pluginName: effective.pluginName,
|
|
1322
1274
|
projectRoot: effective.projectRoot,
|
|
1275
|
+
values: effective.values,
|
|
1276
|
+
sources: effective.sources,
|
|
1277
|
+
issues: effective.issues,
|
|
1278
|
+
unsupportedLayerOverrides: effective.unsupportedLayerOverrides,
|
|
1279
|
+
ignoredUnknownKeys: effective.ignoredUnknownKeys,
|
|
1323
1280
|
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,
|
|
1281
|
+
stores: {
|
|
1282
|
+
user: effective.stores.user,
|
|
1283
|
+
project: effective.stores.project,
|
|
1335
1284
|
},
|
|
1336
1285
|
};
|
|
1337
1286
|
}
|
|
1338
1287
|
async function resolveRedactedLayerValue(options) {
|
|
1339
1288
|
try {
|
|
1340
|
-
const
|
|
1341
|
-
key: options.key,
|
|
1342
|
-
field: options.field,
|
|
1343
|
-
value: options.value,
|
|
1344
|
-
projectRoot: options.projectRoot,
|
|
1345
|
-
});
|
|
1289
|
+
const value = validateConfigReadValue(options.key, options.field, options.value);
|
|
1346
1290
|
return {
|
|
1347
|
-
value: redactValue(options.field,
|
|
1291
|
+
value: redactValue(options.field, value),
|
|
1348
1292
|
};
|
|
1349
1293
|
}
|
|
1350
1294
|
catch (error) {
|
|
@@ -1360,24 +1304,17 @@ async function resolveRedactedLayerValue(options) {
|
|
|
1360
1304
|
}
|
|
1361
1305
|
export async function resolveProjectScopedPluginConfigView(options) {
|
|
1362
1306
|
const effective = await resolveEffectivePluginConfig(options);
|
|
1363
|
-
const artifactConfig = await resolveProjectArtifactRenderConfig(options);
|
|
1364
1307
|
const entries = {};
|
|
1365
1308
|
for (const [key, field] of Object.entries(effective.schema)) {
|
|
1366
1309
|
const entry = {
|
|
1367
1310
|
key,
|
|
1368
1311
|
effectiveValue: effective.redactedValues[key],
|
|
1369
1312
|
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
1313
|
};
|
|
1376
1314
|
if (field.default !== undefined) {
|
|
1377
1315
|
const resolvedDefault = await resolveRedactedLayerValue({
|
|
1378
1316
|
key,
|
|
1379
1317
|
field,
|
|
1380
|
-
projectRoot: effective.projectRoot,
|
|
1381
1318
|
source: 'default',
|
|
1382
1319
|
value: field.default,
|
|
1383
1320
|
});
|
|
@@ -1390,7 +1327,6 @@ export async function resolveProjectScopedPluginConfigView(options) {
|
|
|
1390
1327
|
const resolvedUser = await resolveRedactedLayerValue({
|
|
1391
1328
|
key,
|
|
1392
1329
|
field,
|
|
1393
|
-
projectRoot: effective.projectRoot,
|
|
1394
1330
|
source: 'user',
|
|
1395
1331
|
value: userPlainEntries[key],
|
|
1396
1332
|
});
|
|
@@ -1403,25 +1339,11 @@ export async function resolveProjectScopedPluginConfigView(options) {
|
|
|
1403
1339
|
const resolvedProject = await resolveRedactedLayerValue({
|
|
1404
1340
|
key,
|
|
1405
1341
|
field,
|
|
1406
|
-
projectRoot: effective.projectRoot,
|
|
1407
1342
|
source: 'project',
|
|
1408
1343
|
value: projectPlainEntries[key],
|
|
1409
1344
|
});
|
|
1410
1345
|
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;
|
|
1346
|
+
entry.projectValue = resolvedProject.value;
|
|
1425
1347
|
}
|
|
1426
1348
|
}
|
|
1427
1349
|
entries[key] = entry;
|
|
@@ -1430,18 +1352,16 @@ export async function resolveProjectScopedPluginConfigView(options) {
|
|
|
1430
1352
|
pluginName: effective.pluginName,
|
|
1431
1353
|
projectRoot: effective.projectRoot,
|
|
1432
1354
|
schema: effective.schema,
|
|
1433
|
-
stores:
|
|
1355
|
+
stores: {
|
|
1356
|
+
user: effective.stores.user,
|
|
1357
|
+
project: effective.stores.project,
|
|
1358
|
+
},
|
|
1434
1359
|
redactedValues: effective.redactedValues,
|
|
1435
1360
|
sources: effective.sources,
|
|
1436
1361
|
entries,
|
|
1437
1362
|
issues: effective.issues,
|
|
1363
|
+
unsupportedLayerOverrides: effective.unsupportedLayerOverrides,
|
|
1438
1364
|
ignoredUnknownKeys: effective.ignoredUnknownKeys,
|
|
1439
|
-
repoVisibleArtifactConfig: {
|
|
1440
|
-
values: artifactConfig.values,
|
|
1441
|
-
sources: artifactConfig.sources,
|
|
1442
|
-
issues: artifactConfig.issues,
|
|
1443
|
-
boundaryIssues: artifactConfig.boundaryIssues,
|
|
1444
|
-
},
|
|
1445
1365
|
};
|
|
1446
1366
|
}
|
|
1447
1367
|
function toConfigSourceLayer(source) {
|
|
@@ -1454,77 +1374,179 @@ function toConfigSourceLayer(source) {
|
|
|
1454
1374
|
if (source === 'project') {
|
|
1455
1375
|
return 'project';
|
|
1456
1376
|
}
|
|
1457
|
-
if (source === 'projectLocal') {
|
|
1458
|
-
return 'local';
|
|
1459
|
-
}
|
|
1460
1377
|
return null;
|
|
1461
1378
|
}
|
|
1379
|
+
function configFieldProjection(options) {
|
|
1380
|
+
const source = options.source ?? 'missing';
|
|
1381
|
+
const issues = options.issues ?? [];
|
|
1382
|
+
const presetSensitive = options.field.sensitive === true
|
|
1383
|
+
|| (options.field.type === 'object'
|
|
1384
|
+
&& (Object.values(options.field.properties ?? {}).some((child) => child.sensitive === true)
|
|
1385
|
+
|| (options.field.additionalProperties !== false
|
|
1386
|
+
&& options.field.additionalProperties?.sensitive === true)));
|
|
1387
|
+
return {
|
|
1388
|
+
value: options.value === undefined ? null : redactValue(options.field, options.value),
|
|
1389
|
+
source,
|
|
1390
|
+
matchedPreset: options.value === undefined || presetSensitive
|
|
1391
|
+
? null
|
|
1392
|
+
: matchPluginConfigPreset(options.field, options.value)?.id ?? null,
|
|
1393
|
+
valid: issues.length === 0,
|
|
1394
|
+
issues,
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1462
1397
|
async function buildManifestFieldEntries(options) {
|
|
1463
|
-
const schema = options.plugin.manifest.config?.
|
|
1398
|
+
const schema = options.plugin.manifest.config?.fields;
|
|
1464
1399
|
if (!schema) {
|
|
1465
1400
|
return [];
|
|
1466
1401
|
}
|
|
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({
|
|
1402
|
+
const userStore = await readPluginConfigStore({
|
|
1403
|
+
sourceId: options.plugin.sourceId,
|
|
1404
|
+
sourceIdentity: options.plugin.sourceIdentity,
|
|
1405
|
+
logicalIdentity: options.plugin.catalogLogicalIdentity,
|
|
1406
|
+
pluginName: options.plugin.manifest.name,
|
|
1407
|
+
scope: 'user',
|
|
1408
|
+
cwd: options.cwd,
|
|
1409
|
+
env: options.env,
|
|
1410
|
+
schema,
|
|
1411
|
+
});
|
|
1412
|
+
const projectStore = options.scope === 'user'
|
|
1413
|
+
? undefined
|
|
1414
|
+
: await readPluginConfigStore({
|
|
1487
1415
|
sourceId: options.plugin.sourceId,
|
|
1488
1416
|
sourceIdentity: options.plugin.sourceIdentity,
|
|
1417
|
+
logicalIdentity: options.plugin.catalogLogicalIdentity,
|
|
1489
1418
|
pluginName: options.plugin.manifest.name,
|
|
1490
1419
|
scope: 'project',
|
|
1491
|
-
projectLayer: 'local',
|
|
1492
1420
|
cwd: options.cwd,
|
|
1493
1421
|
env: options.env,
|
|
1494
|
-
|
|
1495
|
-
|
|
1422
|
+
schema,
|
|
1423
|
+
});
|
|
1424
|
+
assertReadyPluginConfigStore(userStore);
|
|
1425
|
+
if (projectStore)
|
|
1426
|
+
assertReadyPluginConfigStore(projectStore);
|
|
1496
1427
|
const userPlainEntries = getStoreEntries(userStore.values);
|
|
1497
|
-
const projectPlainEntries = getStoreEntries(projectStore.values);
|
|
1498
|
-
const localPlainEntries = getStoreEntries(projectLocalStore.values);
|
|
1428
|
+
const projectPlainEntries = projectStore ? getStoreEntries(projectStore.values) : {};
|
|
1499
1429
|
const effective = await resolveEffectivePluginConfig({
|
|
1500
1430
|
sourceId: options.plugin.sourceId,
|
|
1501
1431
|
sourceIdentity: options.plugin.sourceIdentity,
|
|
1432
|
+
logicalIdentity: options.plugin.catalogLogicalIdentity,
|
|
1502
1433
|
pluginName: options.plugin.manifest.name,
|
|
1503
1434
|
manifest: options.plugin.manifest,
|
|
1435
|
+
targetScope: options.scope === 'user' ? 'user' : 'project',
|
|
1504
1436
|
cwd: options.cwd,
|
|
1505
1437
|
env: options.env,
|
|
1506
1438
|
});
|
|
1439
|
+
const userOnlyKeys = codexNativeRefreshKeys(options.plugin.manifest);
|
|
1440
|
+
const projectRemovalFallback = (key) => {
|
|
1441
|
+
const field = schema[key];
|
|
1442
|
+
if (Object.hasOwn(userPlainEntries, key)) {
|
|
1443
|
+
const value = userPlainEntries[key];
|
|
1444
|
+
const validationIssues = validateCorePluginConfigValue(field, value, key);
|
|
1445
|
+
if (validationIssues.length > 0) {
|
|
1446
|
+
return {
|
|
1447
|
+
state: 'invalid',
|
|
1448
|
+
sourceLayer: 'user',
|
|
1449
|
+
issues: validationIssues.map((issue) => ({
|
|
1450
|
+
type: 'invalid-config',
|
|
1451
|
+
key,
|
|
1452
|
+
source: 'user',
|
|
1453
|
+
message: formatPluginConfigIssue(issue),
|
|
1454
|
+
})),
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
return {
|
|
1458
|
+
state: 'valid',
|
|
1459
|
+
effectiveValue: redactValue(field, value),
|
|
1460
|
+
sourceLayer: 'user',
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
if (field.default === undefined)
|
|
1464
|
+
return { state: 'missing' };
|
|
1465
|
+
const validationIssues = validateCorePluginConfigValue(field, field.default, key);
|
|
1466
|
+
if (validationIssues.length > 0) {
|
|
1467
|
+
return {
|
|
1468
|
+
state: 'invalid',
|
|
1469
|
+
sourceLayer: 'default',
|
|
1470
|
+
issues: validationIssues.map((issue) => ({
|
|
1471
|
+
type: 'invalid-config',
|
|
1472
|
+
key,
|
|
1473
|
+
source: 'default',
|
|
1474
|
+
message: formatPluginConfigIssue(issue),
|
|
1475
|
+
})),
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
return {
|
|
1479
|
+
state: 'valid',
|
|
1480
|
+
effectiveValue: redactValue(field, field.default),
|
|
1481
|
+
sourceLayer: 'default',
|
|
1482
|
+
};
|
|
1483
|
+
};
|
|
1484
|
+
const effectiveFieldState = (key) => {
|
|
1485
|
+
const source = effective.sources[key];
|
|
1486
|
+
const sourceLayer = toConfigSourceLayer(source);
|
|
1487
|
+
const field = schema[key];
|
|
1488
|
+
const fieldIssues = effective.issues.filter((entry) => entry.key === key);
|
|
1489
|
+
const fieldState = effective.fieldStates[key];
|
|
1490
|
+
const fallback = options.scope === 'project'
|
|
1491
|
+
? projectRemovalFallback(key)
|
|
1492
|
+
: fieldState?.fallback
|
|
1493
|
+
? {
|
|
1494
|
+
state: 'valid',
|
|
1495
|
+
effectiveValue: fieldState.fallback.redactedValue,
|
|
1496
|
+
sourceLayer: fieldState.fallback.source,
|
|
1497
|
+
}
|
|
1498
|
+
: undefined;
|
|
1499
|
+
if (source === undefined || source === 'missing') {
|
|
1500
|
+
return {
|
|
1501
|
+
valueState: fieldIssues.length > 0 ? 'invalid' : 'default',
|
|
1502
|
+
effectiveValue: null,
|
|
1503
|
+
sourceLayer: null,
|
|
1504
|
+
...configFieldProjection({ field, source: 'missing', issues: fieldIssues }),
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
const issue = fieldIssues.find((entry) => entry.source === source);
|
|
1508
|
+
return {
|
|
1509
|
+
valueState: issue ? 'invalid' : source === 'default' ? 'default' : 'configured',
|
|
1510
|
+
effectiveValue: issue ? null : effective.redactedValues[key] ?? null,
|
|
1511
|
+
sourceLayer,
|
|
1512
|
+
...(fieldState?.invalidOverride ? {
|
|
1513
|
+
invalidOverride: {
|
|
1514
|
+
sourceLayer: fieldState.invalidOverride.source,
|
|
1515
|
+
issues: fieldState.invalidOverride.issues,
|
|
1516
|
+
},
|
|
1517
|
+
} : {}),
|
|
1518
|
+
...(fallback ? { fallback } : {}),
|
|
1519
|
+
...configFieldProjection({
|
|
1520
|
+
field,
|
|
1521
|
+
value: issue ? undefined : effective.values[key],
|
|
1522
|
+
source,
|
|
1523
|
+
issues: fieldIssues,
|
|
1524
|
+
}),
|
|
1525
|
+
};
|
|
1526
|
+
};
|
|
1507
1527
|
const fields = [];
|
|
1508
1528
|
for (const [key, field] of Object.entries(schema)) {
|
|
1529
|
+
const canSet = options.scope !== 'project'
|
|
1530
|
+
|| (!userOnlyKeys.has(key) && field.sensitive !== true);
|
|
1509
1531
|
const base = {
|
|
1510
1532
|
key,
|
|
1511
1533
|
title: field.title ?? key,
|
|
1512
1534
|
kind: 'manifest',
|
|
1513
1535
|
reserved: false,
|
|
1514
|
-
writable:
|
|
1536
|
+
writable: canSet,
|
|
1537
|
+
canSet,
|
|
1538
|
+
canUnset: options.scope === 'user'
|
|
1539
|
+
? Object.hasOwn(userPlainEntries, key)
|
|
1540
|
+
: options.scope === 'project' && Object.hasOwn(projectPlainEntries, key),
|
|
1515
1541
|
valueState: 'default',
|
|
1516
1542
|
effectiveValue: null,
|
|
1517
1543
|
sourceLayer: null,
|
|
1544
|
+
...configFieldProjection({ field }),
|
|
1518
1545
|
};
|
|
1519
1546
|
if (options.scope === 'user') {
|
|
1520
1547
|
if (Object.hasOwn(userPlainEntries, key)) {
|
|
1521
1548
|
try {
|
|
1522
|
-
const value =
|
|
1523
|
-
key,
|
|
1524
|
-
field,
|
|
1525
|
-
value: userPlainEntries[key],
|
|
1526
|
-
projectRoot,
|
|
1527
|
-
});
|
|
1549
|
+
const value = validateConfigReadValue(key, field, userPlainEntries[key]);
|
|
1528
1550
|
fields.push({
|
|
1529
1551
|
...base,
|
|
1530
1552
|
valueState: 'configured',
|
|
@@ -1532,6 +1554,7 @@ async function buildManifestFieldEntries(options) {
|
|
|
1532
1554
|
sourceLayer: 'user',
|
|
1533
1555
|
storedValue: redactValue(field, value),
|
|
1534
1556
|
storedSourceLayer: 'user',
|
|
1557
|
+
...configFieldProjection({ field, value, source: 'user' }),
|
|
1535
1558
|
});
|
|
1536
1559
|
}
|
|
1537
1560
|
catch {
|
|
@@ -1539,22 +1562,22 @@ async function buildManifestFieldEntries(options) {
|
|
|
1539
1562
|
...base,
|
|
1540
1563
|
valueState: 'invalid',
|
|
1541
1564
|
sourceLayer: 'user',
|
|
1565
|
+
source: 'user',
|
|
1566
|
+
storedSourceLayer: 'user',
|
|
1567
|
+
valid: false,
|
|
1568
|
+
issues: [{ type: 'invalid-config', key, source: 'user' }],
|
|
1542
1569
|
});
|
|
1543
1570
|
}
|
|
1544
1571
|
continue;
|
|
1545
1572
|
}
|
|
1546
1573
|
if (field.default !== undefined) {
|
|
1547
1574
|
try {
|
|
1548
|
-
const value =
|
|
1549
|
-
key,
|
|
1550
|
-
field,
|
|
1551
|
-
value: field.default,
|
|
1552
|
-
projectRoot,
|
|
1553
|
-
});
|
|
1575
|
+
const value = validateConfigReadValue(key, field, field.default);
|
|
1554
1576
|
fields.push({
|
|
1555
1577
|
...base,
|
|
1556
1578
|
effectiveValue: redactValue(field, value),
|
|
1557
1579
|
sourceLayer: 'default',
|
|
1580
|
+
...configFieldProjection({ field, value, source: 'default' }),
|
|
1558
1581
|
});
|
|
1559
1582
|
}
|
|
1560
1583
|
catch {
|
|
@@ -1562,6 +1585,9 @@ async function buildManifestFieldEntries(options) {
|
|
|
1562
1585
|
...base,
|
|
1563
1586
|
valueState: 'invalid',
|
|
1564
1587
|
sourceLayer: 'default',
|
|
1588
|
+
source: 'default',
|
|
1589
|
+
valid: false,
|
|
1590
|
+
issues: [{ type: 'invalid-config', key, source: 'default' }],
|
|
1565
1591
|
});
|
|
1566
1592
|
}
|
|
1567
1593
|
continue;
|
|
@@ -1569,103 +1595,41 @@ async function buildManifestFieldEntries(options) {
|
|
|
1569
1595
|
fields.push(base);
|
|
1570
1596
|
continue;
|
|
1571
1597
|
}
|
|
1572
|
-
if (options.projectLayer === 'local') {
|
|
1573
|
-
if (Object.hasOwn(localPlainEntries, key)) {
|
|
1574
|
-
try {
|
|
1575
|
-
const value = await coercePluginConfigValue({
|
|
1576
|
-
key,
|
|
1577
|
-
field,
|
|
1578
|
-
value: localPlainEntries[key],
|
|
1579
|
-
projectRoot,
|
|
1580
|
-
});
|
|
1581
|
-
fields.push({
|
|
1582
|
-
...base,
|
|
1583
|
-
valueState: 'configured',
|
|
1584
|
-
effectiveValue: redactValue(field, value),
|
|
1585
|
-
sourceLayer: 'local',
|
|
1586
|
-
storedValue: redactValue(field, value),
|
|
1587
|
-
storedSourceLayer: 'local',
|
|
1588
|
-
});
|
|
1589
|
-
}
|
|
1590
|
-
catch {
|
|
1591
|
-
fields.push({
|
|
1592
|
-
...base,
|
|
1593
|
-
valueState: 'invalid',
|
|
1594
|
-
sourceLayer: 'local',
|
|
1595
|
-
});
|
|
1596
|
-
}
|
|
1597
|
-
}
|
|
1598
|
-
else {
|
|
1599
|
-
fields.push(base);
|
|
1600
|
-
}
|
|
1601
|
-
continue;
|
|
1602
|
-
}
|
|
1603
1598
|
let storedProjectValue;
|
|
1604
1599
|
if (options.scope === 'project' && Object.hasOwn(projectPlainEntries, key)) {
|
|
1605
1600
|
try {
|
|
1606
|
-
const value =
|
|
1607
|
-
key,
|
|
1608
|
-
field,
|
|
1609
|
-
value: projectPlainEntries[key],
|
|
1610
|
-
projectRoot,
|
|
1611
|
-
});
|
|
1601
|
+
const value = validateConfigReadValue(key, field, projectPlainEntries[key]);
|
|
1612
1602
|
storedProjectValue = {
|
|
1613
1603
|
storedValue: redactValue(field, value),
|
|
1614
1604
|
storedSourceLayer: 'project',
|
|
1615
1605
|
};
|
|
1616
1606
|
}
|
|
1617
1607
|
catch {
|
|
1618
|
-
storedProjectValue =
|
|
1608
|
+
storedProjectValue = { storedSourceLayer: 'project' };
|
|
1619
1609
|
}
|
|
1620
1610
|
}
|
|
1621
|
-
|
|
1622
|
-
const layer = toConfigSourceLayer(source);
|
|
1623
|
-
if (source === 'default') {
|
|
1624
|
-
fields.push({
|
|
1625
|
-
...base,
|
|
1626
|
-
effectiveValue: effective.redactedValues[key] ?? null,
|
|
1627
|
-
sourceLayer: 'default',
|
|
1628
|
-
...storedProjectValue,
|
|
1629
|
-
});
|
|
1630
|
-
continue;
|
|
1631
|
-
}
|
|
1632
|
-
if (source === 'user' || source === 'project' || source === 'projectLocal') {
|
|
1633
|
-
const issue = effective.issues.find((entry) => entry.key === key && entry.source === source);
|
|
1634
|
-
fields.push({
|
|
1635
|
-
...base,
|
|
1636
|
-
valueState: issue ? 'invalid' : 'configured',
|
|
1637
|
-
effectiveValue: issue ? null : effective.redactedValues[key] ?? null,
|
|
1638
|
-
sourceLayer: layer,
|
|
1639
|
-
...storedProjectValue,
|
|
1640
|
-
});
|
|
1641
|
-
continue;
|
|
1642
|
-
}
|
|
1643
|
-
fields.push({ ...base, ...storedProjectValue });
|
|
1611
|
+
fields.push({ ...base, ...effectiveFieldState(key), ...storedProjectValue });
|
|
1644
1612
|
}
|
|
1645
1613
|
return fields;
|
|
1646
1614
|
}
|
|
1647
1615
|
async function buildLegacyStoreFieldEntries(options) {
|
|
1648
1616
|
const locations = options.scope === 'effective'
|
|
1649
1617
|
? [
|
|
1650
|
-
{ scope: 'user',
|
|
1651
|
-
{ scope: 'project',
|
|
1652
|
-
{ scope: 'project', projectLayer: 'local', sourceLayer: 'local' },
|
|
1618
|
+
{ scope: 'user', sourceLayer: 'user' },
|
|
1619
|
+
{ scope: 'project', sourceLayer: 'project' },
|
|
1653
1620
|
]
|
|
1654
1621
|
: [{
|
|
1655
1622
|
scope: options.scope,
|
|
1656
|
-
|
|
1657
|
-
sourceLayer: options.scope === 'user'
|
|
1658
|
-
? 'user'
|
|
1659
|
-
: options.projectLayer === 'local' ? 'local' : 'project',
|
|
1623
|
+
sourceLayer: options.scope === 'user' ? 'user' : 'project',
|
|
1660
1624
|
}];
|
|
1661
1625
|
const fields = new Map();
|
|
1662
1626
|
for (const location of locations) {
|
|
1663
1627
|
const store = await readPluginConfigStore({
|
|
1664
1628
|
sourceId: options.sourceId,
|
|
1665
1629
|
sourceIdentity: options.sourceIdentity,
|
|
1630
|
+
logicalIdentity: options.logicalIdentity,
|
|
1666
1631
|
pluginName: options.pluginName,
|
|
1667
1632
|
scope: location.scope,
|
|
1668
|
-
...(location.scope === 'project' ? { projectLayer: location.projectLayer } : {}),
|
|
1669
1633
|
cwd: options.cwd,
|
|
1670
1634
|
env: options.env,
|
|
1671
1635
|
});
|
|
@@ -1676,9 +1640,16 @@ async function buildLegacyStoreFieldEntries(options) {
|
|
|
1676
1640
|
kind: 'manifest',
|
|
1677
1641
|
reserved: false,
|
|
1678
1642
|
writable: false,
|
|
1643
|
+
canSet: false,
|
|
1644
|
+
canUnset: false,
|
|
1679
1645
|
valueState: 'configured',
|
|
1680
1646
|
effectiveValue: '[redacted]',
|
|
1681
1647
|
sourceLayer: location.sourceLayer,
|
|
1648
|
+
value: '[redacted]',
|
|
1649
|
+
source: location.sourceLayer,
|
|
1650
|
+
matchedPreset: null,
|
|
1651
|
+
valid: true,
|
|
1652
|
+
issues: [],
|
|
1682
1653
|
});
|
|
1683
1654
|
}
|
|
1684
1655
|
}
|
|
@@ -1708,7 +1679,6 @@ export async function resolveConfigRead(options) {
|
|
|
1708
1679
|
currentPlugin: options.plugin,
|
|
1709
1680
|
currentPluginError: options.pluginError,
|
|
1710
1681
|
scope: options.scope,
|
|
1711
|
-
projectLayer: options.projectLayer,
|
|
1712
1682
|
cwd: options.cwd,
|
|
1713
1683
|
env: options.env,
|
|
1714
1684
|
});
|
|
@@ -1716,24 +1686,25 @@ export async function resolveConfigRead(options) {
|
|
|
1716
1686
|
? await buildManifestFieldEntries({
|
|
1717
1687
|
plugin: schemaAccess.plugin,
|
|
1718
1688
|
scope: options.scope,
|
|
1719
|
-
projectLayer: options.projectLayer,
|
|
1720
1689
|
cwd: options.cwd,
|
|
1721
1690
|
env: options.env,
|
|
1722
1691
|
})
|
|
1723
1692
|
: await buildLegacyStoreFieldEntries({
|
|
1724
1693
|
sourceId: options.sourceId,
|
|
1725
1694
|
sourceIdentity: options.sourceIdentity,
|
|
1695
|
+
logicalIdentity: options.logicalIdentity,
|
|
1726
1696
|
pluginName,
|
|
1727
1697
|
scope: options.scope,
|
|
1728
|
-
projectLayer: options.projectLayer,
|
|
1729
1698
|
cwd: options.cwd,
|
|
1730
1699
|
env: options.env,
|
|
1731
1700
|
});
|
|
1732
1701
|
return {
|
|
1733
1702
|
pluginName,
|
|
1734
1703
|
scope: options.scope,
|
|
1735
|
-
projectLayer: options.projectLayer,
|
|
1736
1704
|
...(schemaAccess.schema ? { schema: schemaAccess.schema } : {}),
|
|
1705
|
+
...(schemaAccess.plugin?.manifest.config?.fields
|
|
1706
|
+
? { fieldDefinitions: schemaAccess.plugin.manifest.config.fields }
|
|
1707
|
+
: {}),
|
|
1737
1708
|
fields: manifestFields,
|
|
1738
1709
|
};
|
|
1739
1710
|
}
|
|
@@ -1747,6 +1718,15 @@ export function summarizeEffectivePluginConfig(config) {
|
|
|
1747
1718
|
return {
|
|
1748
1719
|
values: config.redactedValues,
|
|
1749
1720
|
sources: config.sources,
|
|
1721
|
+
fieldStates: Object.fromEntries(Object.entries(config.fieldStates).map(([key, state]) => [key, {
|
|
1722
|
+
...(state.effective ? {
|
|
1723
|
+
effective: { value: state.effective.redactedValue, source: state.effective.source },
|
|
1724
|
+
} : {}),
|
|
1725
|
+
...(state.invalidOverride ? { invalidOverride: state.invalidOverride } : {}),
|
|
1726
|
+
...(state.fallback ? {
|
|
1727
|
+
fallback: { value: state.fallback.redactedValue, source: state.fallback.source },
|
|
1728
|
+
} : {}),
|
|
1729
|
+
}])),
|
|
1750
1730
|
issues: config.issues,
|
|
1751
1731
|
ignoredUnknownKeys: config.ignoredUnknownKeys,
|
|
1752
1732
|
};
|
|
@@ -1762,52 +1742,75 @@ export function createPluginConfigSchemaIdentity(manifest) {
|
|
|
1762
1742
|
.digest('hex');
|
|
1763
1743
|
return `${manifest.name}:${hash}`;
|
|
1764
1744
|
}
|
|
1745
|
+
function cliConfigPromptEnumValues(field) {
|
|
1746
|
+
if (!field.choices)
|
|
1747
|
+
return undefined;
|
|
1748
|
+
const values = field.choices.presets
|
|
1749
|
+
.map((preset) => preset.value);
|
|
1750
|
+
if (field.type !== 'boolean') {
|
|
1751
|
+
return field.choices.allowCustom ? undefined : values;
|
|
1752
|
+
}
|
|
1753
|
+
if (field.choices.allowCustom) {
|
|
1754
|
+
for (const value of [true, false]) {
|
|
1755
|
+
if (!values.includes(value))
|
|
1756
|
+
values.push(value);
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
return values;
|
|
1760
|
+
}
|
|
1765
1761
|
export function toCliConfigPromptSchema(schema) {
|
|
1766
1762
|
const promptSchema = {};
|
|
1767
1763
|
for (const [key, field] of Object.entries(schema)) {
|
|
1764
|
+
if (field.type === 'object')
|
|
1765
|
+
continue;
|
|
1768
1766
|
promptSchema[key] = {
|
|
1769
|
-
type: field.type
|
|
1767
|
+
type: field.type,
|
|
1770
1768
|
label: field.title ?? key,
|
|
1771
1769
|
description: field.description,
|
|
1772
1770
|
required: field.required,
|
|
1773
1771
|
sensitive: field.sensitive,
|
|
1774
1772
|
default: field.default,
|
|
1775
|
-
enum: field
|
|
1776
|
-
min: field.
|
|
1777
|
-
max: field.
|
|
1773
|
+
enum: cliConfigPromptEnumValues(field),
|
|
1774
|
+
min: field.type === 'number' ? field.validation?.minimum : undefined,
|
|
1775
|
+
max: field.type === 'number' ? field.validation?.maximum : undefined,
|
|
1778
1776
|
};
|
|
1779
1777
|
}
|
|
1780
1778
|
return promptSchema;
|
|
1781
1779
|
}
|
|
1782
1780
|
export function createPluginConfigFormModel(options) {
|
|
1783
|
-
const schema = options.manifest.config?.
|
|
1781
|
+
const schema = options.manifest.config?.fields;
|
|
1784
1782
|
if (!schema) {
|
|
1785
1783
|
throw new Error(`Plugin "${options.pluginName ?? options.manifest.name}" does not declare an AW config schema.`);
|
|
1786
1784
|
}
|
|
1787
1785
|
const requiredKeys = [...new Set(options.effective.issues
|
|
1788
1786
|
.filter((issue) => issue.type === 'missing-required' || issue.type === 'invalid-config')
|
|
1789
1787
|
.map((issue) => issue.key))];
|
|
1788
|
+
const fields = Object.entries(schema).flatMap(([key, field]) => {
|
|
1789
|
+
if (field.type === 'object')
|
|
1790
|
+
return [];
|
|
1791
|
+
return [{
|
|
1792
|
+
key,
|
|
1793
|
+
type: field.type,
|
|
1794
|
+
label: field.title ?? key,
|
|
1795
|
+
description: field.description,
|
|
1796
|
+
required: field.required === true,
|
|
1797
|
+
sensitive: field.sensitive === true,
|
|
1798
|
+
defaultValue: field.default,
|
|
1799
|
+
enumValues: cliConfigPromptEnumValues(field),
|
|
1800
|
+
min: field.type === 'number' ? field.validation?.minimum : undefined,
|
|
1801
|
+
max: field.type === 'number' ? field.validation?.maximum : undefined,
|
|
1802
|
+
configured: options.effective.values[key] !== undefined,
|
|
1803
|
+
currentValue: field.sensitive
|
|
1804
|
+
? undefined
|
|
1805
|
+
: options.effective.values[key],
|
|
1806
|
+
}];
|
|
1807
|
+
});
|
|
1790
1808
|
return {
|
|
1791
1809
|
pluginName: options.pluginName ?? options.manifest.name,
|
|
1792
1810
|
allowedScopes: options.allowedScopes ?? ['user', 'project'],
|
|
1793
1811
|
initialScope: options.initialScope,
|
|
1794
1812
|
requiredKeys,
|
|
1795
|
-
fields
|
|
1796
|
-
key,
|
|
1797
|
-
type: field.type,
|
|
1798
|
-
label: field.title ?? key,
|
|
1799
|
-
description: field.description,
|
|
1800
|
-
required: field.required === true,
|
|
1801
|
-
sensitive: field.sensitive === true,
|
|
1802
|
-
defaultValue: field.default,
|
|
1803
|
-
enumValues: field.type === 'enum' ? field.values : undefined,
|
|
1804
|
-
min: field.min,
|
|
1805
|
-
max: field.max,
|
|
1806
|
-
configured: options.effective.values[key] !== undefined,
|
|
1807
|
-
currentValue: field.sensitive || isPathTypeField(field)
|
|
1808
|
-
? undefined
|
|
1809
|
-
: options.effective.values[key],
|
|
1810
|
-
})),
|
|
1813
|
+
fields,
|
|
1811
1814
|
};
|
|
1812
1815
|
}
|
|
1813
1816
|
export function toCliConfigPromptOptions(model) {
|
|
@@ -1826,7 +1829,7 @@ export function toCliConfigPromptOptions(model) {
|
|
|
1826
1829
|
schema: Object.fromEntries(model.fields.map((field) => [
|
|
1827
1830
|
field.key,
|
|
1828
1831
|
{
|
|
1829
|
-
type: field.type
|
|
1832
|
+
type: field.type,
|
|
1830
1833
|
label: field.label,
|
|
1831
1834
|
description: field.description,
|
|
1832
1835
|
required: field.required,
|
|
@@ -1844,13 +1847,21 @@ export function toCliConfigPromptOptions(model) {
|
|
|
1844
1847
|
requiredKeys: [...model.requiredKeys],
|
|
1845
1848
|
};
|
|
1846
1849
|
}
|
|
1847
|
-
function
|
|
1850
|
+
export function formatPluginConfigSetRecoveryCommand(pluginName, key, field, scope) {
|
|
1851
|
+
return field.sensitive === true
|
|
1852
|
+
? `aw config set ${pluginName} ${key} --scope user --value-stdin`
|
|
1853
|
+
: `aw config set ${pluginName} ${key} --scope ${scope} --value-json '<json>'`;
|
|
1854
|
+
}
|
|
1855
|
+
function formatRequiredConfigSetCommands(pluginName, issues, defaultScope, schema) {
|
|
1848
1856
|
const commands = new Set();
|
|
1849
1857
|
for (const issue of issues) {
|
|
1850
1858
|
const scope = issue.source === 'user' || issue.source === 'project'
|
|
1851
1859
|
? issue.source
|
|
1852
1860
|
: defaultScope;
|
|
1853
|
-
|
|
1861
|
+
const field = schema[issue.key];
|
|
1862
|
+
if (!field)
|
|
1863
|
+
continue;
|
|
1864
|
+
commands.add(formatPluginConfigSetRecoveryCommand(pluginName, issue.key, field, scope));
|
|
1854
1865
|
}
|
|
1855
1866
|
return [...commands].map((command) => `\`${command}\``).join(' 或 ');
|
|
1856
1867
|
}
|
|
@@ -1870,12 +1881,13 @@ export async function preflightRequiredPluginConfigs(options) {
|
|
|
1870
1881
|
}
|
|
1871
1882
|
visited.add(dedupeKey);
|
|
1872
1883
|
result.checked += 1;
|
|
1873
|
-
if (!target.manifest.config?.
|
|
1884
|
+
if (!target.manifest.config?.fields) {
|
|
1874
1885
|
continue;
|
|
1875
1886
|
}
|
|
1876
1887
|
let effective = await resolveEffectivePluginConfig({
|
|
1877
1888
|
sourceId: target.sourceId,
|
|
1878
1889
|
sourceIdentity: target.sourceIdentity,
|
|
1890
|
+
logicalIdentity: target.logicalIdentity,
|
|
1879
1891
|
pluginName,
|
|
1880
1892
|
manifest: target.manifest,
|
|
1881
1893
|
cwd: options.cwd,
|
|
@@ -1886,7 +1898,7 @@ export async function preflightRequiredPluginConfigs(options) {
|
|
|
1886
1898
|
continue;
|
|
1887
1899
|
}
|
|
1888
1900
|
const details = blockingIssues.map((issue) => issue.key).join(', ');
|
|
1889
|
-
const setCommands = formatRequiredConfigSetCommands(pluginName, blockingIssues, options.defaultScope);
|
|
1901
|
+
const setCommands = formatRequiredConfigSetCommands(pluginName, blockingIssues, options.defaultScope, target.manifest.config.fields);
|
|
1890
1902
|
throw new Error(`${pluginName} requires valid AW config before install/upgrade: ${details}. ` +
|
|
1891
1903
|
`Run ${setCommands}, or open \`aw\` to complete setup.`);
|
|
1892
1904
|
}
|