@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,34 +1,80 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
|
-
import
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { flattenStatusTargets, statusCommandV8, statusTargetActivation, statusTargetIntent, statusTargetOwnershipState, } from '../commands/status-v6.js';
|
|
3
4
|
import { addCatalogSourceCommand, listCatalogSources, preflightCatalogSourceMutation, removeCatalogSourceCommand, setPrimaryCatalogSourceCommand, updateCatalogSourceCommand, } from '../commands/catalog-source.js';
|
|
4
|
-
import { cleanupWriteCommand, installWriteCommand, reinstallWriteCommand,
|
|
5
|
+
import { cleanupWriteCommand, failedWriteCommandResult, installWriteCommand, mergeWriteCommandResults, reinstallWriteCommand, syncWriteCommand, taskWriteCommand, uninstallWriteCommand, } from '../commands/write-commands.js';
|
|
6
|
+
import { resolveSyncIntentSelection } from '../commands/sync-command.js';
|
|
7
|
+
import { parseScriptTimeoutMs, SCRIPT_TIMEOUT_DEFAULT_MS, } from '../commands/options.js';
|
|
5
8
|
import { readCliPackageInfo } from '../utils/local-marketplace.js';
|
|
6
|
-
import {
|
|
9
|
+
import { createCodexNativeObservationSession, } from '../utils/codex-native-observation.js';
|
|
10
|
+
import { readCatalogSourcesConfigSnapshot, recoverInvalidCatalogSourcesConfig, } from '../utils/catalog-sources.js';
|
|
7
11
|
import { withResolvedCatalogPlugin, withResolvedMultiSourceCatalog, formatPluginId, parsePluginId, } from '../utils/multi-source-catalog.js';
|
|
8
|
-
import { coercePluginConfigUpdates,
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
+
import { coercePluginConfigUpdates, inspectPluginConfigResetTarget, listNamespacedPluginConfigStoreIdentities, readPluginConfigStore, resolveConfigRead, resolveEffectivePluginConfig, resolvePluginConfigStorePath, } from '../utils/plugin-config.js';
|
|
13
|
+
import { assertProjectSharedConfigPathVisible } from '../utils/project-boundary.js';
|
|
14
|
+
import { calculatePluginIntentDigest, readPluginIntentState, } from '../utils/plugin-intent-state.js';
|
|
15
|
+
import { applyPluginConfigWrite, replayConfigRuntimeWrites, resolveConfigPreApplyAuthorization, resetPluginConfigWithHelper, } from '../utils/plugin-config-write.js';
|
|
16
|
+
import { analyzePluginConfigMutationImpact } from '../utils/plugin-config-impact.js';
|
|
17
|
+
import { createBatchIntentCompatiblePlanFingerprint } from '../commands/operation-plan.js';
|
|
18
|
+
import { inventoryCatalogSourceReferences } from '../utils/catalog-source-references.js';
|
|
19
|
+
import { operationEvidenceMatches, operationEvidenceFingerprint, TuiConfigInvalidStoreError, TuiController, TuiOperationBlockedError, } from './controller.js';
|
|
20
|
+
import { buildConfigGroups, buildConfigPage, buildTuiViewModel, hasActualScopeEvidence, isDirectCodexVersionUpgrade, projectPlatformPlacement, } from './model.js';
|
|
21
|
+
import { buildPluginWriteTaskCandidates, buildPluginWriteTaskDraft, evaluatePluginWriteTaskRestoreVersion, pluginWriteTaskTargetKey, } from './plugin-write-task.js';
|
|
22
|
+
import { projectExecutionDiagnostics, projectThrownOperationDiagnostic } from './diagnostic-projection.js';
|
|
12
23
|
import { projectCatalogSourceLocation } from '../utils/catalog-source-presentation.js';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { resolvePluginsFromCatalog } from '../utils/manifest.js';
|
|
24
|
+
import { calculateCatalogSourceConfigFingerprint, calculateCatalogSourceIdentity, } from '../utils/catalog-source-identity.js';
|
|
25
|
+
import { checkAwCapabilitySupport, readPluginManifest } from '../utils/manifest.js';
|
|
16
26
|
import { withAwHomeMutationLock } from '../utils/aw-home-mutation-lock.js';
|
|
17
|
-
import {
|
|
27
|
+
import { IMPLEMENTED_AW_CAPABILITIES } from '../commands/aw-capabilities.js';
|
|
28
|
+
import { calculateCatalogGenerationSetDigest, resolveActiveCatalogGeneration, } from '../utils/catalog-generation-resolver.js';
|
|
29
|
+
import { resolveCatalogOperationContext, resolvePartialCatalogOperationContext, selectCatalogOperationContext, } from '../utils/catalog-operation-context.js';
|
|
30
|
+
import { createCatalogStoreKey, readCatalogStoreEntry, } from '../utils/catalog-generation-store.js';
|
|
31
|
+
import { activateCatalogSourceGeneration, compareCatalogGenerationFreshness, probeCatalogSourceFreshness, probeCatalogSourceGeneration, probeCatalogSourceRepairGeneration, recoverCatalogGenerationStateAndActivate, repairCatalogSourceGeneration, replaceDamagedCatalogSourceGeneration, syncCatalogSourceGeneration, } from '../utils/catalog-generation-service.js';
|
|
32
|
+
import { calculateCatalogSourceImpactDigest, inspectCatalogSourceImpact, } from '../utils/catalog-source-impact.js';
|
|
33
|
+
import { readCatalogGenerationState } from '../utils/catalog-generation-state.js';
|
|
34
|
+
import { CatalogGenerationError } from '../utils/catalog-generation-error.js';
|
|
35
|
+
import { CatalogProviderCompatibilityError } from '../utils/catalog-public-error.js';
|
|
36
|
+
import { loadCatalogGenerationViewModels } from './catalog-generation.js';
|
|
37
|
+
import { executePortableRetry, installPortableSkills, preparePortableInstall, preparePortableRetry, PortableCommandError, recheckPortableInstall, } from '../portable/commands.js';
|
|
38
|
+
import { MANAGED_CODEX_PACKAGE_NAME, MANAGED_CODEX_VERSION, } from '../utils/codex-manager.js';
|
|
39
|
+
import { projectPluginContentOverview, } from './plugin-content.js';
|
|
40
|
+
import { executeCatalogStoreTask, inspectCatalogStoreTask, prepareCatalogStoreTask, } from './catalog-store-task.js';
|
|
41
|
+
function reusableCandidateEvidenceIsComplete(candidate) {
|
|
42
|
+
const evidence = candidate.probe.candidate;
|
|
43
|
+
return [
|
|
44
|
+
evidence.sourceConfigFingerprint,
|
|
45
|
+
evidence.sourceIdentity,
|
|
46
|
+
evidence.logicalIdentity,
|
|
47
|
+
evidence.requestedRef,
|
|
48
|
+
evidence.resolvedCommit,
|
|
49
|
+
evidence.catalogVersion,
|
|
50
|
+
evidence.channel,
|
|
51
|
+
evidence.manifestDigest,
|
|
52
|
+
evidence.snapshotDigest,
|
|
53
|
+
].every((value) => typeof value === 'string' && value.length > 0)
|
|
54
|
+
&& typeof candidate.probe.manifest === 'object'
|
|
55
|
+
&& candidate.probe.manifest !== null;
|
|
56
|
+
}
|
|
18
57
|
async function loadPortablePluginIds(options) {
|
|
19
58
|
return withResolvedMultiSourceCatalog({
|
|
20
59
|
cliVersion: options.cliVersion,
|
|
21
|
-
freshnessPolicy: 'cacheFirst',
|
|
22
60
|
env: options.context.env,
|
|
23
61
|
signal: options.signal,
|
|
62
|
+
...(options.catalogOperationContext
|
|
63
|
+
? { operationContext: options.catalogOperationContext }
|
|
64
|
+
: {}),
|
|
24
65
|
consume: async (catalog) => (await Promise.all(catalog.sources.flatMap((item) => item.resolution
|
|
25
|
-
?
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
66
|
+
? item.resolution.manifest.entries.flatMap((entry) => {
|
|
67
|
+
const support = checkAwCapabilitySupport(entry.requires.aw.capabilities ?? [], IMPLEMENTED_AW_CAPABILITIES);
|
|
68
|
+
return support.supported
|
|
69
|
+
? [readPluginManifest(path.join(item.resolution.rootDir, 'plugins', entry.name), { mode: 'bundle' }).then((plugin) => plugin.manifest.distributions?.agentSkills
|
|
70
|
+
? formatPluginId({
|
|
71
|
+
sourceId: item.resolution.sourceId,
|
|
72
|
+
pluginName: entry.name,
|
|
73
|
+
})
|
|
74
|
+
: null)]
|
|
75
|
+
: [];
|
|
76
|
+
})
|
|
77
|
+
: []))).filter((pluginId) => pluginId !== null),
|
|
32
78
|
});
|
|
33
79
|
}
|
|
34
80
|
let tuiControllerRuntimeTestHooks;
|
|
@@ -38,24 +84,154 @@ export function setTuiControllerRuntimeTestHooksForTests(hooks) {
|
|
|
38
84
|
function digest(value) {
|
|
39
85
|
return `sha256:${crypto.createHash('sha256').update(JSON.stringify(value)).digest('hex')}`;
|
|
40
86
|
}
|
|
41
|
-
function
|
|
42
|
-
return
|
|
87
|
+
export function statusTargetFingerprint(targets, revisions = []) {
|
|
88
|
+
return digest({
|
|
89
|
+
targets: targets.map((target) => ({
|
|
90
|
+
pluginId: target.pluginId,
|
|
91
|
+
sourceIdentity: target.sourceIdentity,
|
|
92
|
+
platform: target.platform,
|
|
93
|
+
scope: target.scope,
|
|
94
|
+
state: target.state,
|
|
95
|
+
errors: target.reasons.map((error) => error.code).sort(),
|
|
96
|
+
sharedReasons: [...(target.sharedReasons ?? [])]
|
|
97
|
+
.sort((left, right) => `${left.code}\0${left.message}`.localeCompare(`${right.code}\0${right.message}`, 'en')),
|
|
98
|
+
components: target.components.map((component) => [component.key, component.state])
|
|
99
|
+
.sort(([left], [right]) => left.localeCompare(right, 'en')),
|
|
100
|
+
sharedComponents: (target.sharedComponents ?? [])
|
|
101
|
+
.map((component) => [component.key, component.state, component.metadata])
|
|
102
|
+
.sort(([left], [right]) => left.localeCompare(right, 'en')),
|
|
103
|
+
})).sort((left, right) => [left.pluginId, left.platform, left.scope].join('\0')
|
|
104
|
+
.localeCompare([right.pluginId, right.platform, right.scope].join('\0'), 'en')),
|
|
105
|
+
revisions: revisions.map((entry) => ({
|
|
106
|
+
pluginId: entry.pluginId,
|
|
107
|
+
platform: entry.platform,
|
|
108
|
+
scope: entry.scope,
|
|
109
|
+
revision: entry.revision,
|
|
110
|
+
})).sort((left, right) => [left.pluginId, left.platform, left.scope].join('\0')
|
|
111
|
+
.localeCompare([right.pluginId, right.platform, right.scope].join('\0'), 'en')),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
export function configAffectedStatusTargets(targets, pluginId, configScope) {
|
|
115
|
+
const affected = new Map();
|
|
116
|
+
for (const target of flattenStatusTargets(targets)) {
|
|
117
|
+
const intent = statusTargetIntent(target);
|
|
118
|
+
if (target.pluginId !== pluginId || !intent.enabled)
|
|
119
|
+
continue;
|
|
120
|
+
for (const scope of intent.scopes) {
|
|
121
|
+
if (configScope === 'project' && scope !== 'project')
|
|
122
|
+
continue;
|
|
123
|
+
affected.set(`${target.platform}\0${scope}`, { ...target, scope });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return [...affected.values()]
|
|
127
|
+
.sort((left, right) => `${left.platform}\0${left.scope}`
|
|
128
|
+
.localeCompare(`${right.platform}\0${right.scope}`, 'en'));
|
|
129
|
+
}
|
|
130
|
+
function configRetrySharedNativeRenderTargets(plugin) {
|
|
131
|
+
return (plugin.manifest.artifacts?.targets ?? []).flatMap((target) => target.platform === 'codex'
|
|
132
|
+
&& target.target === 'codex.skill'
|
|
133
|
+
&& target.delivery.mode === 'codex-native'
|
|
134
|
+
&& target.skillKey
|
|
135
|
+
&& target.refreshKeys.length > 0
|
|
136
|
+
? [{ id: target.id, skillKey: target.skillKey, refreshKeys: [...target.refreshKeys] }]
|
|
137
|
+
: []);
|
|
138
|
+
}
|
|
139
|
+
function configImpactVisibleTargets(pluginName, targets) {
|
|
140
|
+
return targets.map((target) => ({
|
|
141
|
+
pluginName,
|
|
142
|
+
platform: target.platform,
|
|
143
|
+
scope: target.scope,
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
function configIntentRequestsApply(intent) {
|
|
147
|
+
if (!intent.apply)
|
|
148
|
+
return false;
|
|
149
|
+
if (intent.retryApply === true)
|
|
150
|
+
return true;
|
|
151
|
+
if (intent.changes)
|
|
152
|
+
return intent.changes.length > 0;
|
|
153
|
+
return Object.keys(intent.updates ?? {}).length > 0
|
|
154
|
+
|| (intent.removeKeys?.length ?? 0) > 0;
|
|
155
|
+
}
|
|
156
|
+
function configSaveBatchGroupIntent(intent, group) {
|
|
157
|
+
return {
|
|
158
|
+
kind: 'configSave',
|
|
159
|
+
pluginId: intent.pluginId,
|
|
160
|
+
scope: group.scope,
|
|
161
|
+
apply: intent.apply,
|
|
162
|
+
...(intent.scriptTimeoutMs === undefined ? {} : { scriptTimeoutMs: intent.scriptTimeoutMs }),
|
|
163
|
+
...(group.updates ? { updates: group.updates } : {}),
|
|
164
|
+
...(group.removeKeys ? { removeKeys: group.removeKeys } : {}),
|
|
165
|
+
...(group.sensitiveKeys ? { sensitiveKeys: group.sensitiveKeys } : {}),
|
|
166
|
+
...(group.privateKeys ? { privateKeys: group.privateKeys } : {}),
|
|
167
|
+
...(group.changes ? { changes: group.changes } : {}),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function assertConfigSaveBatchIntent(intent) {
|
|
171
|
+
const order = { project: 0, user: 1 };
|
|
172
|
+
if (intent.groups.length === 0 || intent.groups.length > 2
|
|
173
|
+
|| new Set(intent.groups.map((group) => group.scope)).size !== intent.groups.length
|
|
174
|
+
|| intent.groups.some((group, index) => index > 0
|
|
175
|
+
&& order[intent.groups[index - 1].scope] >= order[group.scope])
|
|
176
|
+
|| intent.groups.some((group) => Object.keys(group.updates ?? {}).length === 0
|
|
177
|
+
&& (group.removeKeys?.length ?? 0) === 0)) {
|
|
178
|
+
throw new TuiOperationBlockedError('configBatchInvalid', '设置批次必须包含按当前项目、个人默认排序的唯一非空目标。');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function operationReviewStale(message, diagnostics) {
|
|
182
|
+
return new TuiOperationBlockedError('operationReviewStale', `operationReviewStale: ${message}`, diagnostics);
|
|
43
183
|
}
|
|
44
184
|
function sourceEvidenceForPlugin(source, pluginName) {
|
|
45
185
|
const entry = source.manifest.entries.find((candidate) => candidate.name === pluginName);
|
|
46
186
|
return {
|
|
47
187
|
sourceId: source.sourceId,
|
|
48
188
|
sourceIdentity: source.sourceIdentity,
|
|
189
|
+
logicalIdentity: source.logicalIdentity,
|
|
49
190
|
requestedRef: source.requestedRef,
|
|
50
191
|
resolvedCommit: source.resolvedCommit,
|
|
51
192
|
manifestDigest: source.manifestDigest,
|
|
52
193
|
snapshotDigest: source.snapshotDigest,
|
|
53
194
|
contentDigest: entry.contentDigest,
|
|
195
|
+
pluginVersion: entry.version,
|
|
196
|
+
catalogVersion: source.manifest.catalogVersion,
|
|
197
|
+
channel: source.manifest.channel,
|
|
54
198
|
};
|
|
55
199
|
}
|
|
200
|
+
async function configLayerStoreDigest(options) {
|
|
201
|
+
const store = await readPluginConfigStore({
|
|
202
|
+
sourceId: options.source.sourceId,
|
|
203
|
+
sourceIdentity: options.source.sourceIdentity,
|
|
204
|
+
...('logicalIdentity' in options.source
|
|
205
|
+
? { logicalIdentity: options.source.logicalIdentity }
|
|
206
|
+
: {}),
|
|
207
|
+
pluginName: options.pluginName,
|
|
208
|
+
scope: options.scope,
|
|
209
|
+
cwd: options.cwd,
|
|
210
|
+
env: options.env,
|
|
211
|
+
});
|
|
212
|
+
return digest({ exists: store.exists, rawDigest: store.rawDigest });
|
|
213
|
+
}
|
|
214
|
+
async function inspectConfigShareability(options) {
|
|
215
|
+
if (options.scope !== 'project')
|
|
216
|
+
return undefined;
|
|
217
|
+
const targetPath = resolvePluginConfigStorePath({
|
|
218
|
+
sourceId: options.sourceId,
|
|
219
|
+
pluginName: options.pluginName,
|
|
220
|
+
scope: 'project',
|
|
221
|
+
cwd: options.cwd,
|
|
222
|
+
env: options.env,
|
|
223
|
+
});
|
|
224
|
+
const visibility = await assertProjectSharedConfigPathVisible({
|
|
225
|
+
cwd: options.cwd,
|
|
226
|
+
targetPath,
|
|
227
|
+
env: options.env,
|
|
228
|
+
});
|
|
229
|
+
return visibility.state === 'unverified' ? 'unverified' : 'verified';
|
|
230
|
+
}
|
|
56
231
|
function assertSourceEvidenceMatches(expected, actual) {
|
|
57
232
|
if (actual.sourceId !== expected.sourceId
|
|
58
233
|
|| actual.sourceIdentity !== expected.sourceIdentity
|
|
234
|
+
|| actual.logicalIdentity !== expected.logicalIdentity
|
|
59
235
|
|| actual.resolvedCommit !== expected.resolvedCommit
|
|
60
236
|
|| actual.manifestDigest !== expected.manifestDigest
|
|
61
237
|
|| actual.snapshotDigest !== expected.snapshotDigest
|
|
@@ -63,131 +239,189 @@ function assertSourceEvidenceMatches(expected, actual) {
|
|
|
63
239
|
throw operationReviewStale('确认后来源内容发生变化。');
|
|
64
240
|
}
|
|
65
241
|
}
|
|
242
|
+
function writeReviewIsStale(result) {
|
|
243
|
+
return result.targetResults.some((target) => target.errorCode === 'operationReviewStale'
|
|
244
|
+
|| target.errorCode === 'operationPlanStale');
|
|
245
|
+
}
|
|
66
246
|
function throwIfWriteReviewStale(result) {
|
|
67
|
-
|
|
68
|
-
if (stale) {
|
|
247
|
+
if (writeReviewIsStale(result)) {
|
|
69
248
|
throw operationReviewStale('确认后实际写入计划发生变化。');
|
|
70
249
|
}
|
|
71
250
|
}
|
|
72
251
|
const SOURCE_PROGRESS_PHASES = {
|
|
73
|
-
refreshingCatalog: '刷新 Catalog 缓存',
|
|
74
|
-
catalogReady: 'Catalog 已读取,索引已刷新',
|
|
75
252
|
savingConfig: '保存来源配置',
|
|
76
253
|
};
|
|
77
254
|
function pluginIntent(intent) {
|
|
78
255
|
return 'pluginId' in intent;
|
|
79
256
|
}
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
257
|
+
function reviewedScriptTimeoutMs(intent) {
|
|
258
|
+
if (intent.kind === 'pluginWriteTask') {
|
|
259
|
+
return parseScriptTimeoutMs(intent.scriptTimeoutMs ?? SCRIPT_TIMEOUT_DEFAULT_MS);
|
|
260
|
+
}
|
|
261
|
+
if ((intent.kind === 'pluginWrite' || intent.kind === 'pluginWriteBatch')
|
|
262
|
+
&& (intent.action === 'install' || intent.action === 'refreshAndSync'
|
|
263
|
+
|| intent.action === 'reinstall' || intent.action === 'sync')) {
|
|
264
|
+
return parseScriptTimeoutMs(intent.scriptTimeoutMs ?? SCRIPT_TIMEOUT_DEFAULT_MS);
|
|
265
|
+
}
|
|
266
|
+
if ((intent.kind === 'configSave' || intent.kind === 'configSaveBatch') && intent.apply) {
|
|
267
|
+
return parseScriptTimeoutMs(intent.scriptTimeoutMs ?? SCRIPT_TIMEOUT_DEFAULT_MS);
|
|
268
|
+
}
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
function selectedBatchGroups(intent) {
|
|
272
|
+
if (intent.selection.kind !== 'selected' && intent.selection.kind !== 'targets') {
|
|
273
|
+
throw new TuiOperationBlockedError('syncSelectionRequired', '当前项目全部同步必须从项目 intent roots 解析。');
|
|
274
|
+
}
|
|
275
|
+
const rawTargets = intent.selection.kind === 'targets'
|
|
276
|
+
? intent.selection.targets
|
|
277
|
+
: intent.selection.pluginIds.map((pluginId) => ({
|
|
278
|
+
pluginId, platform: intent.platform, scope: intent.scope,
|
|
279
|
+
}));
|
|
280
|
+
const normalizedTargets = rawTargets.map((target) => {
|
|
281
|
+
if ((target.platform !== 'codex' && target.platform !== 'claude' && target.platform !== 'both')
|
|
282
|
+
|| (target.scope !== 'project' && target.scope !== 'user')
|
|
283
|
+
|| (intent.selection.kind === 'targets' && target.platform === 'both')) {
|
|
284
|
+
throw new TuiOperationBlockedError('batchTargetInvalid', '批量精确目标必须使用有效 scope 和单个平台。');
|
|
100
285
|
}
|
|
101
|
-
|
|
102
|
-
|
|
286
|
+
const pluginId = target.pluginId;
|
|
287
|
+
const parsed = parsePluginId(pluginId);
|
|
288
|
+
if (!parsed.sourceId) {
|
|
289
|
+
throw new TuiOperationBlockedError('invalidPluginId', '批量操作必须使用 canonical pluginId。');
|
|
103
290
|
}
|
|
104
|
-
|
|
105
|
-
|
|
291
|
+
const canonicalPluginId = formatPluginId({ sourceId: parsed.sourceId, pluginName: parsed.pluginName });
|
|
292
|
+
if (intent.selection.kind === 'targets' && canonicalPluginId !== pluginId) {
|
|
293
|
+
throw new TuiOperationBlockedError('invalidPluginId', '批量精确目标必须使用 canonical pluginId。');
|
|
106
294
|
}
|
|
295
|
+
return {
|
|
296
|
+
pluginId: canonicalPluginId,
|
|
297
|
+
platform: target.platform,
|
|
298
|
+
scope: target.scope,
|
|
299
|
+
};
|
|
300
|
+
}).sort((left, right) => [left.pluginId, left.platform, left.scope].join('\0')
|
|
301
|
+
.localeCompare([right.pluginId, right.platform, right.scope].join('\0'), 'en'));
|
|
302
|
+
const uniqueTargets = [...new Map(normalizedTargets.map((target) => [
|
|
303
|
+
[target.pluginId, target.platform, target.scope].join('\0'), target,
|
|
304
|
+
])).values()];
|
|
305
|
+
const requestedPluginIds = [...new Set(uniqueTargets.map((target) => target.pluginId))];
|
|
306
|
+
if (requestedPluginIds.length === 0) {
|
|
307
|
+
throw new TuiOperationBlockedError('syncSelectionRequired', '批量操作至少需要一个插件。');
|
|
308
|
+
}
|
|
309
|
+
const groups = new Map();
|
|
310
|
+
for (const target of uniqueTargets) {
|
|
311
|
+
const pluginId = target.pluginId;
|
|
312
|
+
const sourceId = parsePluginId(pluginId).sourceId;
|
|
313
|
+
const groupKey = intent.action === 'cleanup'
|
|
314
|
+
? ['cleanup', target.platform, target.scope].join('\0')
|
|
315
|
+
: [sourceId, target.platform, target.scope].join('\0');
|
|
316
|
+
const group = groups.get(groupKey) ?? {
|
|
317
|
+
groupId: groupKey,
|
|
318
|
+
sourceId: intent.action === 'cleanup' ? 'offline' : sourceId,
|
|
319
|
+
platform: target.platform,
|
|
320
|
+
scope: target.scope,
|
|
321
|
+
requestedPluginIds: [],
|
|
322
|
+
};
|
|
323
|
+
group.requestedPluginIds.push(pluginId);
|
|
324
|
+
groups.set(groupKey, group);
|
|
107
325
|
}
|
|
326
|
+
return { requestedPluginIds, groups: [...groups.values()] };
|
|
108
327
|
}
|
|
109
|
-
async function
|
|
110
|
-
if (
|
|
111
|
-
return;
|
|
112
|
-
const stale = (message) => {
|
|
113
|
-
throw new TuiOperationBlockedError('operationReviewStale', `operationReviewStale: ${message}`);
|
|
114
|
-
};
|
|
115
|
-
const selected = targets.find((target) => target.pluginId === intent.pluginId
|
|
116
|
-
&& target.platform === intent.platform
|
|
117
|
-
&& target.scope === intent.scope);
|
|
118
|
-
if (intent.originIssue.category === 'dependencyMissing') {
|
|
119
|
-
if (intent.originIssue.actionId !== 'installDependency'
|
|
120
|
-
|| intent.action !== 'install' || !selected || selected.state !== 'absent') {
|
|
121
|
-
stale('原缺失依赖问题已变化。');
|
|
122
|
-
}
|
|
328
|
+
async function resolveBatchGroups(intent, env, cwd, frozenRequestedPluginIds) {
|
|
329
|
+
if (intent.selection.kind === 'selected' || intent.selection.kind === 'targets') {
|
|
330
|
+
return selectedBatchGroups(intent);
|
|
123
331
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|| intent.action !== 'reinstall' || !selected
|
|
127
|
-
|| (selected.state !== 'partial' && selected.state !== 'error')) {
|
|
128
|
-
stale('原依赖安装问题已变化。');
|
|
129
|
-
}
|
|
332
|
+
if (intent.action !== 'sync' || intent.scope !== 'project') {
|
|
333
|
+
throw new TuiOperationBlockedError('syncSelectionRequired', '当前项目全部操作只支持 project scope sync。');
|
|
130
334
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
335
|
+
const state = await readPluginIntentState({ scope: 'project', cwd, env });
|
|
336
|
+
const projectIntentDigest = calculatePluginIntentDigest(state);
|
|
337
|
+
if (intent.selection.kind === 'currentProjectIntentTargets') {
|
|
338
|
+
if (intent.selection.targets.length === 0
|
|
339
|
+
|| intent.selection.targets.some((target) => target.scope !== 'project')) {
|
|
340
|
+
throw new TuiOperationBlockedError('batchTargetInvalid', '当前项目精确同步目标必须是非空 project targets。');
|
|
341
|
+
}
|
|
342
|
+
const exact = selectedBatchGroups({
|
|
343
|
+
...intent,
|
|
344
|
+
selection: { kind: 'targets', targets: intent.selection.targets },
|
|
345
|
+
});
|
|
346
|
+
const declaredPlatforms = new Map(state.roots.map((root) => [
|
|
347
|
+
root.pluginId,
|
|
348
|
+
new Set(root.platforms),
|
|
349
|
+
]));
|
|
350
|
+
if (intent.selection.targets.some((target) => !declaredPlatforms.get(target.pluginId)?.has(target.platform))) {
|
|
351
|
+
throw new TuiOperationBlockedError('batchTargetInvalid', '当前项目精确同步目标必须属于完整 project intent roots 声明。');
|
|
136
352
|
}
|
|
137
|
-
return;
|
|
353
|
+
return { ...exact, projectIntentDigest };
|
|
138
354
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
stale('原位置冲突已变化。');
|
|
148
|
-
}
|
|
149
|
-
return;
|
|
355
|
+
const selection = resolveSyncIntentSelection({
|
|
356
|
+
roots: state.roots,
|
|
357
|
+
selectors: frozenRequestedPluginIds ?? [],
|
|
358
|
+
all: frozenRequestedPluginIds === undefined,
|
|
359
|
+
platform: intent.platform,
|
|
360
|
+
});
|
|
361
|
+
if (selection.requestedPluginIds.length === 0) {
|
|
362
|
+
throw new TuiOperationBlockedError('syncSelectionRequired', '当前项目没有可同步的 intent roots。');
|
|
150
363
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
364
|
+
return {
|
|
365
|
+
requestedPluginIds: selection.requestedPluginIds,
|
|
366
|
+
projectIntentDigest,
|
|
367
|
+
groups: selection.groups.map((group) => ({
|
|
368
|
+
groupId: `${group.sourceId}\0${group.platform}\0${intent.scope}`,
|
|
369
|
+
sourceId: group.sourceId,
|
|
370
|
+
platform: group.platform,
|
|
371
|
+
scope: intent.scope,
|
|
372
|
+
requestedPluginIds: group.pluginIds,
|
|
373
|
+
})),
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
async function runBatchWriteGroup(options) {
|
|
377
|
+
const commandOptions = {
|
|
378
|
+
cwd: options.cwd,
|
|
379
|
+
platform: options.group.platform,
|
|
380
|
+
scope: options.group.scope,
|
|
381
|
+
...(reviewedScriptTimeoutMs(options.intent) === undefined
|
|
382
|
+
? {}
|
|
383
|
+
: { scriptTimeoutMs: reviewedScriptTimeoutMs(options.intent) }),
|
|
384
|
+
};
|
|
385
|
+
switch (options.intent.action) {
|
|
386
|
+
case 'install':
|
|
387
|
+
return installWriteCommand(options.group.requestedPluginIds, commandOptions, options.runtime);
|
|
388
|
+
case 'sync':
|
|
389
|
+
return syncWriteCommand(options.group.requestedPluginIds, commandOptions, options.runtime);
|
|
390
|
+
case 'reinstall':
|
|
391
|
+
return reinstallWriteCommand(options.group.requestedPluginIds, commandOptions, options.runtime);
|
|
392
|
+
case 'uninstall':
|
|
393
|
+
return uninstallWriteCommand(options.group.requestedPluginIds, commandOptions, options.runtime);
|
|
394
|
+
case 'cleanup':
|
|
395
|
+
return cleanupWriteCommand(options.group.requestedPluginIds, commandOptions, options.runtime);
|
|
160
396
|
}
|
|
161
|
-
const consumer = targets.find((target) => target.pluginId === origin.consumerPluginId
|
|
162
|
-
&& target.sourceIdentity === origin.consumerSourceIdentity
|
|
163
|
-
&& target.platform === origin.consumerPlatform
|
|
164
|
-
&& target.scope === origin.consumerScope);
|
|
165
|
-
const consumerError = consumer?.errors.some((error) => error.code === origin.category && error.message.includes(origin.dependencyPluginId));
|
|
166
|
-
if (!consumer || !consumerError)
|
|
167
|
-
stale('原 consumer 依赖问题已消失或变化。');
|
|
168
397
|
}
|
|
169
398
|
async function loadDependencyOverviews(options) {
|
|
170
399
|
const graph = await withResolvedMultiSourceCatalog({
|
|
171
400
|
cliVersion: options.cliVersion,
|
|
172
|
-
freshnessPolicy: 'cacheFirst',
|
|
173
401
|
env: options.context.env,
|
|
174
402
|
signal: options.signal,
|
|
403
|
+
...(options.catalogOperationContext
|
|
404
|
+
? { operationContext: options.catalogOperationContext }
|
|
405
|
+
: {}),
|
|
175
406
|
consume: async (catalog) => ({
|
|
176
|
-
nodes:
|
|
177
|
-
?
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
407
|
+
nodes: catalog.sources.flatMap((item) => item.resolution
|
|
408
|
+
? item.resolution.manifest.entries.map((entry) => ({
|
|
409
|
+
pluginId: formatPluginId({
|
|
410
|
+
sourceId: item.resolution.sourceId,
|
|
411
|
+
pluginName: entry.name,
|
|
412
|
+
}),
|
|
413
|
+
sourceIdentity: item.resolution.sourceIdentity,
|
|
414
|
+
displayName: entry.interface?.displayName ?? entry.name,
|
|
415
|
+
dependencies: entry.dependencies.map((pluginName) => formatPluginId({ sourceId: item.resolution.sourceId, pluginName })),
|
|
416
|
+
}))
|
|
417
|
+
: []),
|
|
185
418
|
sourceErrors: catalog.sources.flatMap((item) => item.error
|
|
186
419
|
? [{ sourceId: item.sourceId, code: 'dependencyGraphInvalid' }]
|
|
187
420
|
: []),
|
|
188
421
|
}),
|
|
189
422
|
});
|
|
190
423
|
const nodes = new Map(graph.nodes.map((node) => [node.pluginId, node]));
|
|
424
|
+
const summaryById = new Map(options.status.pluginSummaries.map((summary) => [summary.pluginId, summary]));
|
|
191
425
|
const displayName = (pluginId) => nodes.get(pluginId)?.displayName
|
|
192
426
|
?? parsePluginId(pluginId).pluginName;
|
|
193
427
|
const reverse = new Map();
|
|
@@ -197,43 +431,61 @@ async function loadDependencyOverviews(options) {
|
|
|
197
431
|
}
|
|
198
432
|
}
|
|
199
433
|
const targetsByPlugin = new Map();
|
|
200
|
-
for (const target of options.status.payload.targets) {
|
|
434
|
+
for (const target of flattenStatusTargets(options.status.payload.targets)) {
|
|
201
435
|
targetsByPlugin.set(target.pluginId, [...(targetsByPlugin.get(target.pluginId) ?? []), target]);
|
|
202
436
|
}
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
scope:
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
437
|
+
const unsupportedPluginIds = new Set(options.status.pluginSummaries
|
|
438
|
+
.filter((summary) => (summary.missingCapabilities?.length ?? 0) > 0)
|
|
439
|
+
.map((summary) => summary.pluginId));
|
|
440
|
+
const availableReferencesFor = (consumerPluginId, dependencyPluginId) => {
|
|
441
|
+
const consumerSourceId = parsePluginId(consumerPluginId).sourceId;
|
|
442
|
+
const dependencySourceId = parsePluginId(dependencyPluginId).sourceId;
|
|
443
|
+
const consumerNode = nodes.get(consumerPluginId);
|
|
444
|
+
const dependencyNode = nodes.get(dependencyPluginId);
|
|
445
|
+
if (consumerSourceId !== dependencySourceId
|
|
446
|
+
|| !nodes.get(consumerPluginId)?.dependencies.includes(dependencyPluginId))
|
|
447
|
+
return [];
|
|
448
|
+
return (targetsByPlugin.get(consumerPluginId) ?? []).flatMap((consumer) => {
|
|
449
|
+
if (!hasActualScopeEvidence(consumer)
|
|
450
|
+
|| consumer.sourceId !== consumerSourceId
|
|
451
|
+
|| consumer.sourceIdentity !== consumerNode?.sourceIdentity)
|
|
452
|
+
return [];
|
|
453
|
+
return [...consumer.components, ...(consumer.sharedComponents ?? [])].flatMap((component) => {
|
|
454
|
+
if (component.kind !== 'dependency'
|
|
455
|
+
|| component.key !== dependencyPluginId
|
|
456
|
+
|| component.state !== 'ready')
|
|
457
|
+
return [];
|
|
458
|
+
const platform = component.metadata?.platform;
|
|
459
|
+
const scope = component.metadata?.scope;
|
|
460
|
+
const project = component.metadata?.project;
|
|
461
|
+
if ((platform !== 'codex' && platform !== 'claude')
|
|
462
|
+
|| (scope !== 'user' && scope !== 'project')
|
|
463
|
+
|| project !== (scope === 'project' ? 'current' : 'none'))
|
|
464
|
+
return [];
|
|
465
|
+
const dependency = (targetsByPlugin.get(dependencyPluginId) ?? []).find((target) => target.platform === platform
|
|
466
|
+
&& target.scope === scope
|
|
467
|
+
&& target.sourceId === dependencySourceId
|
|
468
|
+
&& target.sourceIdentity === dependencyNode?.sourceIdentity
|
|
469
|
+
&& target.sourceIdentity === consumer.sourceIdentity);
|
|
470
|
+
if (!dependency)
|
|
471
|
+
return [];
|
|
472
|
+
return [{
|
|
473
|
+
consumer: {
|
|
474
|
+
pluginId: consumerPluginId,
|
|
475
|
+
platform: consumer.platform,
|
|
476
|
+
scope: consumer.scope,
|
|
477
|
+
project: consumer.scope === 'project' ? 'current' : null,
|
|
478
|
+
},
|
|
479
|
+
dependency: {
|
|
480
|
+
pluginId: dependencyPluginId,
|
|
481
|
+
platform,
|
|
482
|
+
scope,
|
|
483
|
+
project: scope === 'project' ? 'current' : null,
|
|
484
|
+
},
|
|
485
|
+
}];
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
};
|
|
237
489
|
const placementsFor = (pluginId) => {
|
|
238
490
|
const targets = targetsByPlugin.get(pluginId) ?? [];
|
|
239
491
|
if (targets.length > 0) {
|
|
@@ -246,13 +498,15 @@ async function loadDependencyOverviews(options) {
|
|
|
246
498
|
};
|
|
247
499
|
const aggregateFor = (pluginId, placements) => {
|
|
248
500
|
const sourceId = parsePluginId(pluginId).sourceId;
|
|
249
|
-
if (graph.sourceErrors.some((error) => error.sourceId === sourceId)
|
|
501
|
+
if (graph.sourceErrors.some((error) => error.sourceId === sourceId)
|
|
502
|
+
|| unsupportedPluginIds.has(pluginId))
|
|
250
503
|
return 'unavailable';
|
|
251
504
|
if (placements.length === 0 || placements.every((placement) => placement.state === 'notInstalled')) {
|
|
252
505
|
return 'notInstalled';
|
|
253
506
|
}
|
|
254
507
|
return placements.some((placement) => placement.state === 'scopeConflict'
|
|
255
|
-
|| (placement.state === 'installed'
|
|
508
|
+
|| (placement.state === 'installed'
|
|
509
|
+
&& (placement.health !== 'ready' || placement.blockingReason !== undefined)))
|
|
256
510
|
? 'needsAttention'
|
|
257
511
|
: 'ready';
|
|
258
512
|
};
|
|
@@ -303,7 +557,8 @@ async function loadDependencyOverviews(options) {
|
|
|
303
557
|
})),
|
|
304
558
|
placements,
|
|
305
559
|
aggregateStatus: aggregateFor(dependency.pluginId, placements),
|
|
306
|
-
placementReferences:
|
|
560
|
+
placementReferences: [],
|
|
561
|
+
availablePlacementReferences: availableReferencesFor(pluginId, dependency.pluginId),
|
|
307
562
|
};
|
|
308
563
|
});
|
|
309
564
|
const dependentIds = [...new Set(reverse.get(pluginId) ?? [])].sort();
|
|
@@ -317,15 +572,23 @@ async function loadDependencyOverviews(options) {
|
|
|
317
572
|
requiredBy: [],
|
|
318
573
|
placements,
|
|
319
574
|
aggregateStatus: aggregateFor(dependentId, placements),
|
|
320
|
-
placementReferences:
|
|
575
|
+
placementReferences: [],
|
|
576
|
+
availablePlacementReferences: availableReferencesFor(dependentId, pluginId),
|
|
321
577
|
};
|
|
322
578
|
});
|
|
323
|
-
const targetBlockers = (targetsByPlugin.get(pluginId) ?? []).flatMap((target) => target.
|
|
579
|
+
const targetBlockers = (targetsByPlugin.get(pluginId) ?? []).flatMap((target) => target.reasons.filter((error) => error.code.startsWith('dependency') || error.code === 'sourceChanged')
|
|
324
580
|
.map((error) => ({
|
|
325
581
|
code: error.code,
|
|
326
582
|
title: '依赖关系需要处理',
|
|
327
583
|
explanation: '当前目标的依赖证据不能安全通过检查。',
|
|
328
584
|
})));
|
|
585
|
+
const consumerTargets = summaryById.get(pluginId)?.supportedTargets;
|
|
586
|
+
const unsupportedDependency = dependencyItems.find((dependency) => {
|
|
587
|
+
const dependencyTargets = summaryById.get(dependency.pluginId)?.supportedTargets;
|
|
588
|
+
if (!consumerTargets || !dependencyTargets)
|
|
589
|
+
return true;
|
|
590
|
+
return ['codex', 'claude'].some((platform) => consumerTargets[platform].length > 0 && dependencyTargets[platform].length === 0);
|
|
591
|
+
});
|
|
329
592
|
const sourceId = parsePluginId(pluginId).sourceId;
|
|
330
593
|
const blockers = [
|
|
331
594
|
...graph.sourceErrors.filter((error) => error.sourceId === sourceId).map((error) => ({
|
|
@@ -338,6 +601,11 @@ async function loadDependencyOverviews(options) {
|
|
|
338
601
|
title: '依赖关系无效',
|
|
339
602
|
explanation: '依赖关系形成环。',
|
|
340
603
|
}] : []),
|
|
604
|
+
...(unsupportedDependency ? [{
|
|
605
|
+
code: 'dependencyTargetUnsupported',
|
|
606
|
+
title: '依赖目标不兼容',
|
|
607
|
+
explanation: `${unsupportedDependency.displayName} 不支持根插件所需的平台。`,
|
|
608
|
+
}] : []),
|
|
341
609
|
...targetBlockers,
|
|
342
610
|
];
|
|
343
611
|
const directCount = dependencyItems.filter((item) => item.relation === 'direct').length;
|
|
@@ -360,46 +628,231 @@ async function loadDependencyOverviews(options) {
|
|
|
360
628
|
};
|
|
361
629
|
});
|
|
362
630
|
}
|
|
363
|
-
|
|
631
|
+
function createReviewStageReporter(options) {
|
|
632
|
+
let active;
|
|
633
|
+
const finish = async () => {
|
|
634
|
+
if (!active)
|
|
635
|
+
return;
|
|
636
|
+
const completed = active;
|
|
637
|
+
active = undefined;
|
|
638
|
+
await Promise.allSettled([Promise.resolve().then(() => options.context.debugLogger.log({
|
|
639
|
+
type: `review.prepare.${completed.stage}`,
|
|
640
|
+
level: 'debug',
|
|
641
|
+
target: 'debugLog',
|
|
642
|
+
message: 'TUI review preparation stage completed.',
|
|
643
|
+
data: { durationMs: Math.max(0, options.now() - completed.startedAt) },
|
|
644
|
+
}))]);
|
|
645
|
+
};
|
|
646
|
+
return {
|
|
647
|
+
async emit(stage) {
|
|
648
|
+
await finish();
|
|
649
|
+
active = { stage, startedAt: options.now() };
|
|
650
|
+
await options.onStage(stage);
|
|
651
|
+
},
|
|
652
|
+
finish,
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
export function createTuiControllerDependencies(context, progressFeed, options = {}) {
|
|
656
|
+
const readInventory = options.inventoryCatalogSourceReferences
|
|
657
|
+
?? inventoryCatalogSourceReferences;
|
|
658
|
+
const validatedStoreEntries = new Map();
|
|
659
|
+
const validatingStoreEntries = new Map();
|
|
660
|
+
const loadCatalogGenerations = async () => {
|
|
661
|
+
const generations = await loadCatalogGenerationViewModels({
|
|
662
|
+
env: context.env,
|
|
663
|
+
validatedStoreEntries,
|
|
664
|
+
validatingStoreEntries,
|
|
665
|
+
onStage: (event) => context.debugLogger.log({
|
|
666
|
+
type: 'performance:stage',
|
|
667
|
+
level: 'debug',
|
|
668
|
+
target: 'debugLog',
|
|
669
|
+
message: 'Catalog generation load stage completed.',
|
|
670
|
+
data: event,
|
|
671
|
+
}),
|
|
672
|
+
});
|
|
673
|
+
const retained = new Set(generations.flatMap((source) => [
|
|
674
|
+
source.active?.storeKey,
|
|
675
|
+
].filter((value) => value !== undefined)));
|
|
676
|
+
for (const storeKey of validatedStoreEntries.keys()) {
|
|
677
|
+
if (!retained.has(storeKey))
|
|
678
|
+
validatedStoreEntries.delete(storeKey);
|
|
679
|
+
}
|
|
680
|
+
return generations;
|
|
681
|
+
};
|
|
682
|
+
const createOperationInventoryReader = () => {
|
|
683
|
+
let ordinaryInventory;
|
|
684
|
+
return (inventoryOptions) => {
|
|
685
|
+
ordinaryInventory ??= readInventory(inventoryOptions);
|
|
686
|
+
return ordinaryInventory;
|
|
687
|
+
};
|
|
688
|
+
};
|
|
689
|
+
const catalogOperationByEvidence = new WeakMap();
|
|
690
|
+
const taskCatalogOperationsByEvidence = new WeakMap();
|
|
691
|
+
const codexManagerByEvidence = new WeakMap();
|
|
692
|
+
let sessionCatalogOperationContext;
|
|
693
|
+
let sessionStatusResult;
|
|
694
|
+
const sessionContextForPlugin = (pluginId) => {
|
|
695
|
+
const sourceId = parsePluginId(pluginId).sourceId;
|
|
696
|
+
if (!sourceId || !sessionCatalogOperationContext)
|
|
697
|
+
return undefined;
|
|
698
|
+
return selectCatalogOperationContext(sessionCatalogOperationContext, [sourceId]);
|
|
699
|
+
};
|
|
700
|
+
const readConfigStoresForTui = async (config) => {
|
|
701
|
+
const scopes = config.scope === 'user' ? ['user'] : ['user', 'project'];
|
|
702
|
+
const stores = await Promise.all(scopes.map((scope) => readPluginConfigStore({
|
|
703
|
+
sourceId: config.sourceId,
|
|
704
|
+
sourceIdentity: config.sourceIdentity,
|
|
705
|
+
logicalIdentity: config.logicalIdentity,
|
|
706
|
+
pluginName: config.pluginName,
|
|
707
|
+
scope,
|
|
708
|
+
cwd: context.cwd,
|
|
709
|
+
env: context.env,
|
|
710
|
+
schema: config.schema,
|
|
711
|
+
})));
|
|
712
|
+
const user = stores[0];
|
|
713
|
+
const project = config.scope === 'user' ? undefined : stores[1];
|
|
714
|
+
config.signal.throwIfAborted();
|
|
715
|
+
const invalidStores = ([
|
|
716
|
+
{ scope: 'user', store: user },
|
|
717
|
+
...(project ? [{ scope: 'project', store: project }] : []),
|
|
718
|
+
]).flatMap(({ scope, store }) => store.state === 'invalid' ? [{
|
|
719
|
+
scope,
|
|
720
|
+
code: store.invalidCode,
|
|
721
|
+
pathSummary: scope === 'user'
|
|
722
|
+
? `AW_HOME/plugin-config/${config.sourceId ?? '<source>'}/${config.pluginName}/config.json`
|
|
723
|
+
: `<project>/.agent-workflows/plugin-config/${config.sourceId ?? '<source>'}/${config.pluginName}/config.json`,
|
|
724
|
+
}] : []);
|
|
725
|
+
if (invalidStores.length > 0)
|
|
726
|
+
throw new TuiConfigInvalidStoreError(invalidStores);
|
|
727
|
+
return { user, ...(project ? { project } : {}) };
|
|
728
|
+
};
|
|
364
729
|
const loadStatus = async (signal) => {
|
|
365
730
|
signal.throwIfAborted();
|
|
366
731
|
const packageInfo = await readCliPackageInfo();
|
|
732
|
+
const catalogOperationContext = sessionCatalogOperationContext
|
|
733
|
+
?? await resolvePartialCatalogOperationContext({
|
|
734
|
+
env: context.env,
|
|
735
|
+
signal,
|
|
736
|
+
validatedStoreEntries,
|
|
737
|
+
validatingStoreEntries,
|
|
738
|
+
}).catch(async (error) => {
|
|
739
|
+
if (!(error instanceof CatalogGenerationError)
|
|
740
|
+
|| error.code !== 'catalogGenerationStateInvalid')
|
|
741
|
+
return undefined;
|
|
742
|
+
const snapshot = await readCatalogSourcesConfigSnapshot(context.env);
|
|
743
|
+
const primarySourceId = snapshot.config.sources.find((source) => source.primary).id;
|
|
744
|
+
const generationSetDigest = calculateCatalogGenerationSetDigest(snapshot.digest, primarySourceId, []);
|
|
745
|
+
return {
|
|
746
|
+
generationSet: {
|
|
747
|
+
generationSetDigest,
|
|
748
|
+
configDigest: snapshot.digest,
|
|
749
|
+
primarySourceId,
|
|
750
|
+
sources: [],
|
|
751
|
+
},
|
|
752
|
+
generationSetDigest,
|
|
753
|
+
catalog: {
|
|
754
|
+
sources: snapshot.config.sources.map((source) => ({
|
|
755
|
+
sourceId: source.id,
|
|
756
|
+
error,
|
|
757
|
+
})),
|
|
758
|
+
primarySourceId,
|
|
759
|
+
configDigest: snapshot.digest,
|
|
760
|
+
compositeDigest: generationSetDigest,
|
|
761
|
+
},
|
|
762
|
+
};
|
|
763
|
+
});
|
|
764
|
+
if (catalogOperationContext)
|
|
765
|
+
sessionCatalogOperationContext = catalogOperationContext;
|
|
367
766
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
368
|
-
const result = await
|
|
767
|
+
const result = await statusCommandV8({
|
|
369
768
|
cwd: context.cwd,
|
|
370
769
|
env: context.env,
|
|
770
|
+
nativeCliRunner: context.nativeCliRunner,
|
|
371
771
|
cliVersion: packageInfo.version,
|
|
372
772
|
verbose: context.diagnostics.verbose,
|
|
773
|
+
...(catalogOperationContext ? { catalogOperationContext } : {}),
|
|
774
|
+
inventoryCatalogSourceReferences: readInventory,
|
|
373
775
|
});
|
|
374
776
|
signal.throwIfAborted();
|
|
375
777
|
const configured = await listCatalogSources({ env: context.env });
|
|
376
778
|
signal.throwIfAborted();
|
|
377
779
|
if (result.payload.catalog.configDigest !== configured.configDigest)
|
|
378
780
|
continue;
|
|
781
|
+
sessionStatusResult = result;
|
|
379
782
|
const dependencyOverviews = await loadDependencyOverviews({
|
|
380
783
|
context,
|
|
381
784
|
cliVersion: packageInfo.version,
|
|
382
785
|
status: result,
|
|
383
786
|
signal,
|
|
787
|
+
...(catalogOperationContext ? { catalogOperationContext } : {}),
|
|
384
788
|
});
|
|
385
789
|
const portablePluginIds = await loadPortablePluginIds({
|
|
386
790
|
context,
|
|
387
791
|
cliVersion: packageInfo.version,
|
|
388
792
|
signal,
|
|
793
|
+
...(catalogOperationContext ? { catalogOperationContext } : {}),
|
|
389
794
|
});
|
|
390
795
|
signal.throwIfAborted();
|
|
796
|
+
const catalogGenerations = await loadCatalogGenerations();
|
|
797
|
+
signal.throwIfAborted();
|
|
798
|
+
const configStoreInventory = (await Promise.all(['user', 'project'].map(async (scope) => {
|
|
799
|
+
try {
|
|
800
|
+
return (await listNamespacedPluginConfigStoreIdentities({
|
|
801
|
+
cwd: context.cwd,
|
|
802
|
+
env: context.env,
|
|
803
|
+
scope,
|
|
804
|
+
})).map(({ pluginId, sourceId, pluginName }) => ({
|
|
805
|
+
pluginId, sourceId, pluginName, scope,
|
|
806
|
+
}));
|
|
807
|
+
}
|
|
808
|
+
catch {
|
|
809
|
+
await context.debugLogger.log({
|
|
810
|
+
type: 'tui:config-inventory-error',
|
|
811
|
+
level: 'debug',
|
|
812
|
+
target: 'debugLog',
|
|
813
|
+
message: 'TUI saved config inventory could not be read.',
|
|
814
|
+
data: { scope },
|
|
815
|
+
});
|
|
816
|
+
return [];
|
|
817
|
+
}
|
|
818
|
+
}))).flat();
|
|
819
|
+
signal.throwIfAborted();
|
|
391
820
|
const statusById = new Map(result.payload.catalog.sources.map((source) => [source.id, source]));
|
|
821
|
+
const projectIntentObservation = result.projectIntentObservation;
|
|
822
|
+
const projectIntent = projectIntentObservation.state === 'ready' ? {
|
|
823
|
+
state: 'ready',
|
|
824
|
+
roots: structuredClone(projectIntentObservation.value.roots),
|
|
825
|
+
} : projectIntentObservation.state === 'absent' ? {
|
|
826
|
+
state: 'absent',
|
|
827
|
+
} : {
|
|
828
|
+
state: projectIntentObservation.state,
|
|
829
|
+
code: projectIntentObservation.code,
|
|
830
|
+
message: projectIntentObservation.message,
|
|
831
|
+
};
|
|
392
832
|
return {
|
|
833
|
+
cliChannel: packageInfo.version.includes('-') ? 'beta' : 'latest',
|
|
834
|
+
...(result.projectRoot ? { projectRoot: result.projectRoot } : {}),
|
|
835
|
+
projectIntent,
|
|
836
|
+
...(projectIntent.state === 'ready'
|
|
837
|
+
? { projectIntentRoots: structuredClone(projectIntent.roots) }
|
|
838
|
+
: {}),
|
|
839
|
+
configuredSources: structuredClone(configured.sources),
|
|
393
840
|
payload: result.payload,
|
|
394
841
|
pluginSummaries: result.pluginSummaries,
|
|
842
|
+
targetRevisions: result.targetRevisions,
|
|
395
843
|
dependencyOverviews,
|
|
396
844
|
portablePluginIds,
|
|
845
|
+
configStoreInventory,
|
|
846
|
+
catalogGenerations,
|
|
847
|
+
sessionGenerationKeys: Object.fromEntries((catalogOperationContext?.generationSet.sources ?? [])
|
|
848
|
+
.map((source) => [source.sourceId, source.storeKey])),
|
|
397
849
|
sourceDetails: configured.sources.map((source) => {
|
|
398
850
|
const status = statusById.get(source.id);
|
|
399
851
|
return {
|
|
400
852
|
id: source.id,
|
|
401
853
|
type: source.type,
|
|
402
854
|
primary: source.primary,
|
|
855
|
+
autoCheck: source.autoCheck,
|
|
403
856
|
locationSummary: projectCatalogSourceLocation(source.type === 'git' ? source.url : source.path),
|
|
404
857
|
...(source.type === 'git' ? {
|
|
405
858
|
refPolicy: source.refPolicy.type,
|
|
@@ -413,6 +866,7 @@ export function createTuiControllerDependencies(context, progressFeed) {
|
|
|
413
866
|
...(status?.catalogVersion ? { catalogVersion: status.catalogVersion } : {}),
|
|
414
867
|
...(status?.channel ? { channel: status.channel } : {}),
|
|
415
868
|
...(status?.freshness ? { freshness: status.freshness } : {}),
|
|
869
|
+
...(status?.errorCode ? { errorCode: status.errorCode } : {}),
|
|
416
870
|
configDigest: configured.configDigest,
|
|
417
871
|
};
|
|
418
872
|
}),
|
|
@@ -420,195 +874,1786 @@ export function createTuiControllerDependencies(context, progressFeed) {
|
|
|
420
874
|
}
|
|
421
875
|
throw new TuiOperationBlockedError('operationReviewStale', '状态读取期间来源配置持续变化。请重试。');
|
|
422
876
|
};
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
? { action: 'add', source: intent.source }
|
|
432
|
-
: intent.action === 'update'
|
|
433
|
-
? { action: 'update', sourceId: intent.sourceId, update: intent.update }
|
|
434
|
-
: { action: intent.action, sourceId: intent.sourceId };
|
|
435
|
-
const preview = await preflightCatalogSourceMutation({
|
|
436
|
-
request,
|
|
437
|
-
cwd: context.cwd,
|
|
877
|
+
const loadPluginContent = async (pluginId, signal) => {
|
|
878
|
+
signal.throwIfAborted();
|
|
879
|
+
try {
|
|
880
|
+
const packageInfo = await readCliPackageInfo();
|
|
881
|
+
const catalogOperation = sessionContextForPlugin(pluginId);
|
|
882
|
+
return await withResolvedCatalogPlugin({
|
|
883
|
+
selector: pluginId,
|
|
884
|
+
cliVersion: packageInfo.version,
|
|
438
885
|
env: context.env,
|
|
886
|
+
signal,
|
|
887
|
+
...(catalogOperation ? { operationContext: catalogOperation } : {}),
|
|
888
|
+
consume: ({ plugin, source }) => projectPluginContentOverview({
|
|
889
|
+
pluginId,
|
|
890
|
+
catalogVersion: source.manifest.catalogVersion,
|
|
891
|
+
plugin,
|
|
892
|
+
}),
|
|
439
893
|
});
|
|
894
|
+
}
|
|
895
|
+
catch {
|
|
896
|
+
signal.throwIfAborted();
|
|
897
|
+
const summary = sessionStatusResult?.pluginSummaries.find((entry) => entry.pluginId === pluginId);
|
|
898
|
+
let sourceId;
|
|
899
|
+
try {
|
|
900
|
+
sourceId = parsePluginId(pluginId).sourceId;
|
|
901
|
+
}
|
|
902
|
+
catch {
|
|
903
|
+
// The fixed unavailable result below does not repeat an unsafe selector.
|
|
904
|
+
}
|
|
905
|
+
const catalogVersion = sessionStatusResult?.payload.catalog.sources
|
|
906
|
+
.find((source) => source.id === sourceId)?.catalogVersion;
|
|
440
907
|
return {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
referenceCount: preview.references.references.length,
|
|
448
|
-
inventoryComplete: preview.references.errors.length === 0,
|
|
449
|
-
},
|
|
908
|
+
pluginId,
|
|
909
|
+
pluginVersion: summary?.version ?? '待确认',
|
|
910
|
+
catalogVersion: catalogVersion ?? '待确认',
|
|
911
|
+
evidence: 'unavailable',
|
|
912
|
+
items: [],
|
|
913
|
+
notices: ['当前无法安全读取这个插件的目录内容。请检查来源状态后重试。'],
|
|
450
914
|
};
|
|
451
915
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
scope: intent.scope,
|
|
461
|
-
...(intent.selectedSkills.length > 0 ? { requestedSkills: intent.selectedSkills } : {}),
|
|
462
|
-
...(intent.requestedAgents ? { requestedAgents: intent.requestedAgents } : {}),
|
|
463
|
-
cwd: context.cwd,
|
|
464
|
-
env: context.env,
|
|
465
|
-
cliVersion: packageInfo.version,
|
|
466
|
-
signal,
|
|
467
|
-
});
|
|
916
|
+
};
|
|
917
|
+
const captureEvidence = async (intent, signal, preparation, operationInventory = createOperationInventoryReader(), freshCatalog = false, reusableCandidate, codexObservationSession, frozenBatchEvidence) => {
|
|
918
|
+
reviewedScriptTimeoutMs(intent);
|
|
919
|
+
if (intent.kind === 'catalogStorePrune') {
|
|
920
|
+
signal?.throwIfAborted();
|
|
921
|
+
const config = await readCatalogSourcesConfigSnapshot(context.env);
|
|
922
|
+
const catalogStore = await prepareCatalogStoreTask({ env: context.env });
|
|
923
|
+
signal?.throwIfAborted();
|
|
468
924
|
return {
|
|
469
|
-
catalog: {
|
|
470
|
-
|
|
471
|
-
compositeDigest: portable.evidenceDigest,
|
|
472
|
-
},
|
|
473
|
-
portable,
|
|
925
|
+
catalog: { configDigest: config.digest, compositeDigest: config.digest },
|
|
926
|
+
catalogStore,
|
|
474
927
|
};
|
|
475
928
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
929
|
+
if (intent.kind === 'configSaveBatch') {
|
|
930
|
+
assertConfigSaveBatchIntent(intent);
|
|
931
|
+
const groupEvidence = [];
|
|
932
|
+
let batchBase;
|
|
933
|
+
let batchCatalogOperation;
|
|
934
|
+
for (const group of intent.groups) {
|
|
935
|
+
const current = await captureEvidence(configSaveBatchGroupIntent(intent, group), signal, preparation, operationInventory, freshCatalog, reusableCandidate, codexObservationSession);
|
|
936
|
+
if (!current.config || !current.source || !current.targetFingerprint) {
|
|
937
|
+
throw operationReviewStale('设置批次缺少单层证据。');
|
|
938
|
+
}
|
|
939
|
+
if (batchBase && (JSON.stringify(batchBase.catalog) !== JSON.stringify(current.catalog)
|
|
940
|
+
|| JSON.stringify(batchBase.source) !== JSON.stringify(current.source))) {
|
|
941
|
+
throw operationReviewStale('准备设置批次期间 Catalog 或来源发生变化。');
|
|
942
|
+
}
|
|
943
|
+
batchBase ??= current;
|
|
944
|
+
batchCatalogOperation ??= catalogOperationByEvidence.get(current);
|
|
945
|
+
groupEvidence.push({
|
|
946
|
+
scope: group.scope,
|
|
947
|
+
config: {
|
|
948
|
+
...current.config,
|
|
949
|
+
storeDigest: await configLayerStoreDigest({
|
|
950
|
+
source: current.source,
|
|
951
|
+
pluginName: parsePluginId(intent.pluginId).pluginName,
|
|
952
|
+
scope: group.scope,
|
|
953
|
+
cwd: context.cwd,
|
|
954
|
+
env: context.env,
|
|
955
|
+
}),
|
|
956
|
+
},
|
|
957
|
+
targetFingerprint: current.targetFingerprint,
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
const evidence = {
|
|
961
|
+
catalog: batchBase.catalog,
|
|
962
|
+
source: batchBase.source,
|
|
963
|
+
configBatch: { groups: groupEvidence },
|
|
964
|
+
};
|
|
965
|
+
if (batchCatalogOperation)
|
|
966
|
+
catalogOperationByEvidence.set(evidence, batchCatalogOperation);
|
|
967
|
+
return evidence;
|
|
491
968
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
&&
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
reinstall: reinstallWriteCommand,
|
|
502
|
-
}[intent.action];
|
|
503
|
-
const preview = await command([intent.pluginId], {
|
|
504
|
-
cwd: context.cwd,
|
|
505
|
-
platform: intent.platform,
|
|
506
|
-
scope: intent.scope,
|
|
507
|
-
}, {
|
|
508
|
-
...context,
|
|
509
|
-
expectedCatalogConfigDigest: status.payload.catalog.configDigest,
|
|
510
|
-
previewDependencyPlanOnly: true,
|
|
969
|
+
if ((intent.kind === 'pluginWrite'
|
|
970
|
+
&& (intent.platform === 'codex' || intent.platform === 'both')
|
|
971
|
+
|| intent.kind === 'pluginWriteTask'
|
|
972
|
+
&& intent.targets.some((target) => target.platform === 'codex'))
|
|
973
|
+
&& !codexObservationSession) {
|
|
974
|
+
const observationSession = await createCodexNativeObservationSession({
|
|
975
|
+
exactCwd: context.cwd,
|
|
976
|
+
env: context.env,
|
|
977
|
+
runner: context.nativeCliRunner,
|
|
511
978
|
});
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
throw new TuiOperationBlockedError('dependencyGraphInvalid', '无法建立可信的依赖安装计划。');
|
|
979
|
+
try {
|
|
980
|
+
return await captureEvidence(intent, signal, preparation, operationInventory, freshCatalog, reusableCandidate, observationSession, frozenBatchEvidence);
|
|
515
981
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
throw new TuiOperationBlockedError(blocker.errorCode ?? 'dependencyNotReady', `${blocker.errorCode ?? 'dependencyNotReady'}: 依赖安装计划被阻止。`);
|
|
982
|
+
finally {
|
|
983
|
+
await observationSession.dispose();
|
|
519
984
|
}
|
|
520
985
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
pluginName:
|
|
530
|
-
source: sourceEvidenceForPlugin(source, plugin.manifest.name),
|
|
531
|
-
}),
|
|
532
|
-
});
|
|
533
|
-
const evidence = {
|
|
534
|
-
catalog: {
|
|
535
|
-
configDigest: status.payload.catalog.configDigest,
|
|
536
|
-
compositeDigest: status.payload.catalog.compositeDigest,
|
|
537
|
-
},
|
|
538
|
-
source: selected.source,
|
|
539
|
-
...(dependencyPlan ? { dependencyPlan } : {}),
|
|
540
|
-
targetFingerprint: digest(status.payload.targets.map((target) => ({
|
|
541
|
-
pluginId: target.pluginId,
|
|
542
|
-
sourceIdentity: target.sourceIdentity,
|
|
543
|
-
platform: target.platform,
|
|
544
|
-
scope: target.scope,
|
|
545
|
-
state: target.state,
|
|
546
|
-
errors: target.errors.map((error) => error.code).sort(),
|
|
547
|
-
components: target.components.map((component) => [component.key, component.state])
|
|
548
|
-
.sort(([left], [right]) => left.localeCompare(right, 'en')),
|
|
549
|
-
})).sort((left, right) => [left.pluginId, left.platform, left.scope].join('\0')
|
|
550
|
-
.localeCompare([right.pluginId, right.platform, right.scope].join('\0'), 'en'))),
|
|
551
|
-
};
|
|
552
|
-
if (intent.kind === 'configSave' || intent.kind === 'configReset') {
|
|
553
|
-
const store = await readPluginConfigStore({
|
|
554
|
-
sourceId: selected.source.sourceId,
|
|
555
|
-
sourceIdentity: selected.source.sourceIdentity,
|
|
556
|
-
pluginName: selected.pluginName,
|
|
986
|
+
if (intent.kind === 'configReset') {
|
|
987
|
+
const parsed = parsePluginId(intent.pluginId);
|
|
988
|
+
if (!parsed.sourceId || !parsed.pluginName) {
|
|
989
|
+
throw new TuiOperationBlockedError('invalidPluginId', '设置目标必须使用 canonical pluginId。');
|
|
990
|
+
}
|
|
991
|
+
await preparation?.reporter.emit('config');
|
|
992
|
+
const target = await inspectPluginConfigResetTarget({
|
|
993
|
+
sourceId: parsed.sourceId,
|
|
994
|
+
pluginName: parsed.pluginName,
|
|
557
995
|
scope: intent.scope,
|
|
558
|
-
projectLayer: intent.projectLayer,
|
|
559
996
|
cwd: context.cwd,
|
|
560
997
|
env: context.env,
|
|
561
998
|
});
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
999
|
+
signal?.throwIfAborted();
|
|
1000
|
+
if (target.kind === 'special') {
|
|
1001
|
+
throw new TuiOperationBlockedError('configResetTargetUnsupported', '设置目标是特殊文件系统节点,不能自动重置。请手动移动后重试。');
|
|
1002
|
+
}
|
|
1003
|
+
const relevantTargets = sessionStatusResult
|
|
1004
|
+
? configAffectedStatusTargets(sessionStatusResult.payload.targets, intent.pluginId, intent.scope)
|
|
1005
|
+
: [];
|
|
1006
|
+
const relevantRevisions = (sessionStatusResult?.targetRevisions ?? []).filter((revision) => relevantTargets.some((statusTarget) => statusTarget.platform === revision.platform
|
|
1007
|
+
&& statusTarget.scope === revision.scope
|
|
1008
|
+
&& statusTarget.pluginId === revision.pluginId));
|
|
1009
|
+
let selected;
|
|
1010
|
+
try {
|
|
1011
|
+
const packageInfo = await readCliPackageInfo();
|
|
1012
|
+
selected = await withResolvedCatalogPlugin({
|
|
1013
|
+
selector: intent.pluginId,
|
|
1014
|
+
cliVersion: packageInfo.version,
|
|
1015
|
+
env: context.env,
|
|
1016
|
+
...(preparation && sessionContextForPlugin(intent.pluginId)
|
|
1017
|
+
? { operationContext: sessionContextForPlugin(intent.pluginId) }
|
|
1018
|
+
: {}),
|
|
1019
|
+
...(signal ? { signal } : {}),
|
|
1020
|
+
consume: async ({ plugin, source: resolvedSource }) => ({
|
|
1021
|
+
source: sourceEvidenceForPlugin(resolvedSource, plugin.manifest.name),
|
|
1022
|
+
impact: await analyzePluginConfigMutationImpact({
|
|
1023
|
+
plugin,
|
|
1024
|
+
scope: intent.scope,
|
|
1025
|
+
cwd: context.cwd,
|
|
1026
|
+
env: context.env,
|
|
1027
|
+
visibleTargets: configImpactVisibleTargets(plugin.manifest.name, relevantTargets),
|
|
1028
|
+
mutation: { kind: 'reset' },
|
|
1029
|
+
}),
|
|
1030
|
+
}),
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
catch {
|
|
1034
|
+
selected = undefined;
|
|
1035
|
+
}
|
|
1036
|
+
signal?.throwIfAborted();
|
|
1037
|
+
await preparation?.reporter.emit('evidence');
|
|
1038
|
+
return {
|
|
1039
|
+
catalog: sessionStatusResult ? {
|
|
1040
|
+
configDigest: sessionStatusResult.payload.catalog.configDigest,
|
|
1041
|
+
compositeDigest: sessionStatusResult.payload.catalog.compositeDigest,
|
|
1042
|
+
} : {
|
|
1043
|
+
configDigest: 'config-reset-offline',
|
|
1044
|
+
compositeDigest: 'config-reset-target',
|
|
1045
|
+
},
|
|
1046
|
+
...(selected ? { source: selected.source } : {}),
|
|
1047
|
+
...(sessionStatusResult ? {
|
|
1048
|
+
targetFingerprint: statusTargetFingerprint(relevantTargets, relevantRevisions),
|
|
1049
|
+
} : {}),
|
|
1050
|
+
config: {
|
|
1051
|
+
scope: intent.scope,
|
|
1052
|
+
storeDigest: selected?.impact.storeDigest ?? target.fingerprint,
|
|
1053
|
+
impactState: selected?.impact.impactState ?? 'unknown',
|
|
1054
|
+
...(selected?.impact.noEffectiveChange ? { noEffectiveChange: true } : {}),
|
|
1055
|
+
affectedTargets: (selected?.impact.affectedTargets
|
|
1056
|
+
?? relevantTargets).map(({ platform, scope }) => ({ platform, scope })),
|
|
1057
|
+
...((selected?.impact.sharedNativeRenderTargets.length ?? 0) > 0
|
|
1058
|
+
? { sharedNativeRenderTargets: selected.impact.sharedNativeRenderTargets }
|
|
1059
|
+
: {}),
|
|
1060
|
+
},
|
|
1061
|
+
resetTarget: {
|
|
1062
|
+
sourceId: parsed.sourceId,
|
|
1063
|
+
pluginName: parsed.pluginName,
|
|
1064
|
+
scope: intent.scope,
|
|
1065
|
+
kind: target.kind,
|
|
1066
|
+
fingerprint: target.fingerprint,
|
|
1067
|
+
},
|
|
567
1068
|
};
|
|
568
1069
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
async inspectSourceRemoval(_sourceId, signal) {
|
|
574
|
-
signal.throwIfAborted();
|
|
1070
|
+
if (intent.kind === 'catalogSourcesRecovery') {
|
|
1071
|
+
signal?.throwIfAborted();
|
|
1072
|
+
await preparation?.reporter.emit('validate');
|
|
1073
|
+
await preparation?.reporter.emit('evidence');
|
|
575
1074
|
return {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
lifecycleCount: 0,
|
|
581
|
-
marketplaceCount: 0,
|
|
582
|
-
dependencies: [],
|
|
1075
|
+
catalog: {
|
|
1076
|
+
configDigest: 'invalid-catalog-sources-config',
|
|
1077
|
+
compositeDigest: 'bootstrap-recovery',
|
|
1078
|
+
},
|
|
583
1079
|
};
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
1080
|
+
}
|
|
1081
|
+
if (intent.kind === 'catalogGeneration') {
|
|
1082
|
+
signal?.throwIfAborted();
|
|
1083
|
+
await preparation?.reporter.emit('validate');
|
|
1084
|
+
const packageInfo = await readCliPackageInfo();
|
|
1085
|
+
const config = await readCatalogSourcesConfigSnapshot(context.env);
|
|
1086
|
+
const configuredSource = config.config.sources.find((source) => source.id === intent.sourceId);
|
|
1087
|
+
let proposedSource;
|
|
1088
|
+
let proposedConfigDigest;
|
|
1089
|
+
let explicitSourceChange = false;
|
|
1090
|
+
let sourceIdentityChangeImpact;
|
|
1091
|
+
if (intent.sourceMutation) {
|
|
1092
|
+
const preview = await preflightCatalogSourceMutation({
|
|
1093
|
+
request: intent.sourceMutation.action === 'add'
|
|
1094
|
+
? intent.sourceMutation
|
|
1095
|
+
: { action: 'update', sourceId: intent.sourceId, update: intent.sourceMutation.update },
|
|
1096
|
+
cwd: context.cwd,
|
|
1097
|
+
env: context.env,
|
|
1098
|
+
});
|
|
1099
|
+
proposedSource = preview.proposedSource;
|
|
1100
|
+
proposedConfigDigest = preview.proposedConfigDigest;
|
|
1101
|
+
if (preview.configDigest !== config.digest) {
|
|
1102
|
+
throw operationReviewStale('准备 Catalog 候选期间来源配置发生变化。');
|
|
1103
|
+
}
|
|
1104
|
+
const sourceFingerprintChanged = Boolean(proposedSource && (!configuredSource
|
|
1105
|
+
|| calculateCatalogSourceConfigFingerprint(proposedSource)
|
|
1106
|
+
!== calculateCatalogSourceConfigFingerprint(configuredSource)));
|
|
1107
|
+
explicitSourceChange = preview.proposedConfigDigest !== preview.configDigest
|
|
1108
|
+
&& (preview.identityChanged || sourceFingerprintChanged);
|
|
1109
|
+
if (preview.identityChanged)
|
|
1110
|
+
sourceIdentityChangeImpact = {
|
|
1111
|
+
impactSchemaVersion: 3,
|
|
1112
|
+
sourceId: preview.sourceId,
|
|
1113
|
+
primaryChange: preview.references.primaryChange,
|
|
1114
|
+
inventoryComplete: preview.references.inventoryComplete,
|
|
1115
|
+
references: preview.references.references,
|
|
1116
|
+
affectedReferences: preview.references.references,
|
|
1117
|
+
changes: {
|
|
1118
|
+
added: [], removed: [], changed: [], newlyIncompatible: [],
|
|
1119
|
+
dependencyChanged: [], platformSupportChanged: [],
|
|
1120
|
+
},
|
|
1121
|
+
errors: preview.references.errors,
|
|
1122
|
+
};
|
|
1123
|
+
if (!explicitSourceChange) {
|
|
589
1124
|
throw new TuiOperationBlockedError('sourceNoChanges', '来源没有变化。');
|
|
590
1125
|
}
|
|
591
|
-
return;
|
|
592
1126
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
1127
|
+
let previousActive;
|
|
1128
|
+
let healthyActive;
|
|
1129
|
+
let previousActiveVersion;
|
|
1130
|
+
let previousStoreReady = false;
|
|
1131
|
+
try {
|
|
1132
|
+
previousActive = (await readCatalogGenerationState(context.env))
|
|
1133
|
+
.sources[intent.sourceId]?.active;
|
|
1134
|
+
}
|
|
1135
|
+
catch (error) {
|
|
1136
|
+
if (intent.action !== 'recoverState'
|
|
1137
|
+
|| !(error instanceof CatalogGenerationError)
|
|
1138
|
+
|| error.code !== 'catalogGenerationStateInvalid')
|
|
1139
|
+
throw error;
|
|
1140
|
+
}
|
|
1141
|
+
const activeStoreKey = previousActive
|
|
1142
|
+
? createCatalogStoreKey(previousActive.logicalIdentity, previousActive.snapshotDigest)
|
|
1143
|
+
: null;
|
|
1144
|
+
const configuredFingerprint = configuredSource
|
|
1145
|
+
? calculateCatalogSourceConfigFingerprint(configuredSource)
|
|
1146
|
+
: undefined;
|
|
1147
|
+
const canReuseCandidate = Boolean(reusableCandidate
|
|
1148
|
+
&& !intent.sourceMutation
|
|
1149
|
+
&& (intent.action === 'activate' || intent.action === 'update')
|
|
1150
|
+
&& configuredFingerprint
|
|
1151
|
+
&& reusableCandidate.sourceId === intent.sourceId
|
|
1152
|
+
&& reusableCandidate.sourceConfigFingerprint === configuredFingerprint
|
|
1153
|
+
&& reusableCandidate.probe.candidate.sourceConfigFingerprint === configuredFingerprint
|
|
1154
|
+
&& reusableCandidate.activeStoreKey === activeStoreKey
|
|
1155
|
+
&& reusableCandidate.probe.sourceId === intent.sourceId
|
|
1156
|
+
&& reusableCandidateEvidenceIsComplete(reusableCandidate));
|
|
1157
|
+
const probe = intent.action === 'repair'
|
|
1158
|
+
? await probeCatalogSourceRepairGeneration({
|
|
1159
|
+
sourceId: intent.sourceId,
|
|
597
1160
|
cliVersion: packageInfo.version,
|
|
598
|
-
|
|
1161
|
+
...(previousActive ? { expectedActive: previousActive } : {}),
|
|
1162
|
+
expectedConfigDigest: config.digest,
|
|
599
1163
|
env: context.env,
|
|
600
1164
|
signal,
|
|
1165
|
+
runtime: options.catalogGenerationRuntime,
|
|
1166
|
+
})
|
|
1167
|
+
: canReuseCandidate
|
|
1168
|
+
? structuredClone(reusableCandidate.probe)
|
|
1169
|
+
: await probeCatalogSourceGeneration({
|
|
1170
|
+
sourceId: intent.sourceId,
|
|
1171
|
+
...(proposedSource ? { source: proposedSource } : {}),
|
|
1172
|
+
cliVersion: packageInfo.version,
|
|
1173
|
+
env: context.env,
|
|
1174
|
+
signal,
|
|
1175
|
+
runtime: options.catalogGenerationRuntime,
|
|
1176
|
+
});
|
|
1177
|
+
signal?.throwIfAborted();
|
|
1178
|
+
await preparation?.reporter.emit('evidence');
|
|
1179
|
+
if (intent.action === 'activate' || intent.action === 'update') {
|
|
1180
|
+
try {
|
|
1181
|
+
healthyActive = await resolveActiveCatalogGeneration({ sourceId: intent.sourceId, env: context.env });
|
|
1182
|
+
}
|
|
1183
|
+
catch (error) {
|
|
1184
|
+
if (!(error instanceof CatalogGenerationError)
|
|
1185
|
+
&& !(error instanceof CatalogProviderCompatibilityError))
|
|
1186
|
+
throw error;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
const comparisonSource = proposedSource
|
|
1190
|
+
?? configuredSource;
|
|
1191
|
+
if (previousActive) {
|
|
1192
|
+
const store = await readCatalogStoreEntry({
|
|
1193
|
+
logicalIdentity: previousActive.logicalIdentity,
|
|
1194
|
+
snapshotDigest: previousActive.snapshotDigest,
|
|
1195
|
+
env: context.env,
|
|
1196
|
+
}).catch(() => undefined);
|
|
1197
|
+
previousStoreReady = Boolean(store);
|
|
1198
|
+
previousActiveVersion = healthyActive?.catalogVersion
|
|
1199
|
+
?? store?.metadata.catalogVersion;
|
|
1200
|
+
}
|
|
1201
|
+
if (intent.action === 'update' && !intent.sourceMutation && previousActive && comparisonSource) {
|
|
1202
|
+
explicitSourceChange = previousActive.sourceConfigFingerprint
|
|
1203
|
+
!== calculateCatalogSourceConfigFingerprint(comparisonSource)
|
|
1204
|
+
|| previousActive.sourceIdentity !== await calculateCatalogSourceIdentity(comparisonSource);
|
|
1205
|
+
}
|
|
1206
|
+
const comparisonActive = healthyActive
|
|
1207
|
+
? {
|
|
1208
|
+
logicalIdentity: healthyActive.logicalIdentity,
|
|
1209
|
+
catalogVersion: healthyActive.catalogVersion,
|
|
1210
|
+
snapshotDigest: healthyActive.snapshotDigest,
|
|
1211
|
+
resolvedCommit: healthyActive.resolvedCommit,
|
|
1212
|
+
}
|
|
1213
|
+
: previousActive && previousActiveVersion
|
|
1214
|
+
? {
|
|
1215
|
+
logicalIdentity: previousActive.logicalIdentity,
|
|
1216
|
+
catalogVersion: previousActiveVersion,
|
|
1217
|
+
snapshotDigest: previousActive.snapshotDigest,
|
|
1218
|
+
resolvedCommit: previousActive.resolvedCommit,
|
|
1219
|
+
}
|
|
1220
|
+
: undefined;
|
|
1221
|
+
const candidateRelation = comparisonActive && comparisonSource
|
|
1222
|
+
? compareCatalogGenerationFreshness({
|
|
1223
|
+
sourceType: comparisonSource.type,
|
|
1224
|
+
active: comparisonActive,
|
|
1225
|
+
candidate: probe.candidate,
|
|
1226
|
+
}).state
|
|
1227
|
+
: undefined;
|
|
1228
|
+
const exactStoreTarget = Boolean(previousActive)
|
|
1229
|
+
&& createCatalogStoreKey(previousActive.logicalIdentity, previousActive.snapshotDigest)
|
|
1230
|
+
=== createCatalogStoreKey(probe.candidate.logicalIdentity, probe.candidate.snapshotDigest);
|
|
1231
|
+
if (intent.action === 'repair' && !exactStoreTarget) {
|
|
1232
|
+
throw new CatalogGenerationError({
|
|
1233
|
+
code: 'catalogReplaceDamagedRequired', sourceId: intent.sourceId,
|
|
1234
|
+
message: `Catalog source "${intent.sourceId}" cannot reproduce the damaged Active entry. Use the current candidate replacement Review.`,
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
const exactRepair = (intent.action === 'repair' || intent.action === 'update')
|
|
1238
|
+
&& exactStoreTarget && !previousStoreReady;
|
|
1239
|
+
if (previousActive && !previousStoreReady && !exactStoreTarget
|
|
1240
|
+
&& !intent.sourceMutation && intent.action !== 'replaceDamaged') {
|
|
1241
|
+
throw new CatalogGenerationError({
|
|
1242
|
+
code: 'catalogReplaceDamagedRequired',
|
|
1243
|
+
sourceId: intent.sourceId,
|
|
1244
|
+
message: `Catalog source "${intent.sourceId}" has a damaged Active entry whose version cannot be compared safely. The current Active remains unchanged. Edit the source explicitly, or remove and add it again.`,
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
if (intent.action !== 'replaceDamaged' && !explicitSourceChange && candidateRelation === 'incompatible') {
|
|
1248
|
+
throw new CatalogGenerationError({
|
|
1249
|
+
code: 'catalogLogicalIdentityChanged', sourceId: intent.sourceId,
|
|
1250
|
+
message: `Catalog source "${intent.sourceId}" changed logical identity. Edit or remove and re-add the source.`,
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
if (intent.action !== 'replaceDamaged' && !explicitSourceChange && candidateRelation === 'versionConflict') {
|
|
1254
|
+
throw new TuiOperationBlockedError('catalogVersionConflict', '候选 Catalog 与当前 Active 版本相同,但内容摘要不同。普通更新已阻止。');
|
|
1255
|
+
}
|
|
1256
|
+
if (intent.action !== 'replaceDamaged' && !explicitSourceChange && candidateRelation === 'rollbackDetected') {
|
|
1257
|
+
throw new TuiOperationBlockedError('catalogRollbackDetected', '候选 Catalog 版本低于当前 Active。普通更新已阻止。');
|
|
1258
|
+
}
|
|
1259
|
+
const impact = exactRepair ? undefined
|
|
1260
|
+
: sourceIdentityChangeImpact ?? await inspectCatalogSourceImpact({
|
|
1261
|
+
sourceId: intent.sourceId,
|
|
1262
|
+
cwd: context.cwd,
|
|
1263
|
+
env: context.env,
|
|
1264
|
+
sourcePrimary: comparisonSource?.primary === true,
|
|
1265
|
+
mode: 'generation',
|
|
1266
|
+
candidateManifest: probe.manifest,
|
|
1267
|
+
});
|
|
1268
|
+
const primaryPreview = intent.setPrimaryAfterActivation
|
|
1269
|
+
? await preflightCatalogSourceMutation({
|
|
1270
|
+
request: { action: 'setPrimary', sourceId: intent.sourceId },
|
|
1271
|
+
cwd: context.cwd,
|
|
1272
|
+
env: context.env,
|
|
1273
|
+
})
|
|
1274
|
+
: undefined;
|
|
1275
|
+
if (primaryPreview && primaryPreview.configDigest !== config.digest) {
|
|
1276
|
+
throw operationReviewStale('准备主来源切换期间来源配置发生变化。');
|
|
1277
|
+
}
|
|
1278
|
+
const primaryImpact = primaryPreview ? {
|
|
1279
|
+
impactSchemaVersion: 3,
|
|
1280
|
+
sourceId: primaryPreview.sourceId,
|
|
1281
|
+
primaryChange: primaryPreview.references.primaryChange,
|
|
1282
|
+
inventoryComplete: primaryPreview.references.inventoryComplete,
|
|
1283
|
+
references: primaryPreview.references.references,
|
|
1284
|
+
affectedReferences: primaryPreview.references.references,
|
|
1285
|
+
changes: {
|
|
1286
|
+
added: [], removed: [], changed: [], newlyIncompatible: [],
|
|
1287
|
+
dependencyChanged: [], platformSupportChanged: [],
|
|
1288
|
+
},
|
|
1289
|
+
errors: primaryPreview.references.errors,
|
|
1290
|
+
} : undefined;
|
|
1291
|
+
return {
|
|
1292
|
+
catalog: {
|
|
1293
|
+
configDigest: config.digest,
|
|
1294
|
+
compositeDigest: probe.candidate.snapshotDigest,
|
|
1295
|
+
},
|
|
1296
|
+
catalogGeneration: {
|
|
1297
|
+
action: intent.action,
|
|
1298
|
+
...(previousActive ? { previousActive } : {}),
|
|
1299
|
+
...(previousActiveVersion ? { previousActiveVersion } : {}),
|
|
1300
|
+
candidate: probe.candidate,
|
|
1301
|
+
...(candidateRelation ? { candidateRelation } : {}),
|
|
1302
|
+
...(explicitSourceChange ? { explicitSourceChange: true } : {}),
|
|
1303
|
+
...(exactRepair ? { exactRepair: true } : {}),
|
|
1304
|
+
activationMode: intent.action === 'replaceDamaged' ? 'replaceDamaged'
|
|
1305
|
+
: explicitSourceChange ? 'explicitSourceChange' : 'normal',
|
|
1306
|
+
...(impact ? {
|
|
1307
|
+
impact,
|
|
1308
|
+
impactDigest: calculateCatalogSourceImpactDigest(impact),
|
|
1309
|
+
impactMode: sourceIdentityChangeImpact ? 'identityChange' : 'generation',
|
|
1310
|
+
} : {}),
|
|
1311
|
+
...(primaryPreview && primaryImpact ? {
|
|
1312
|
+
primaryImpact,
|
|
1313
|
+
primaryImpactDigest: primaryPreview.impactDigest,
|
|
1314
|
+
primaryConfigDigest: primaryPreview.configDigest,
|
|
1315
|
+
} : {}),
|
|
1316
|
+
configDigest: config.digest,
|
|
1317
|
+
...(proposedConfigDigest ? { proposedConfigDigest } : {}),
|
|
1318
|
+
...(proposedSource ? { proposedSource } : {}),
|
|
1319
|
+
...(intent.sourceMutation ? { sourceMutation: intent.sourceMutation } : {}),
|
|
1320
|
+
...(intent.setPrimaryAfterActivation ? { setPrimaryAfterActivation: true } : {}),
|
|
1321
|
+
},
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
if (intent.kind === 'sourceMutation') {
|
|
1325
|
+
signal?.throwIfAborted();
|
|
1326
|
+
if (intent.action === 'add' && !intent.source
|
|
1327
|
+
|| intent.action === 'update' && !intent.update) {
|
|
1328
|
+
throw new TuiOperationBlockedError('invalidSourceMutation', '来源操作缺少候选输入。');
|
|
1329
|
+
}
|
|
1330
|
+
const request = intent.action === 'add'
|
|
1331
|
+
? { action: 'add', source: intent.source }
|
|
1332
|
+
: intent.action === 'update'
|
|
1333
|
+
? { action: 'update', sourceId: intent.sourceId, update: intent.update }
|
|
1334
|
+
: { action: intent.action, sourceId: intent.sourceId };
|
|
1335
|
+
await preparation?.reporter.emit('validate');
|
|
1336
|
+
const preview = await preflightCatalogSourceMutation({
|
|
1337
|
+
request,
|
|
1338
|
+
cwd: context.cwd,
|
|
1339
|
+
env: context.env,
|
|
1340
|
+
});
|
|
1341
|
+
signal?.throwIfAborted();
|
|
1342
|
+
await preparation?.reporter.emit('evidence');
|
|
1343
|
+
signal?.throwIfAborted();
|
|
1344
|
+
const evidence = {
|
|
1345
|
+
catalog: { configDigest: preview.configDigest, compositeDigest: preview.proposedConfigDigest },
|
|
1346
|
+
sourceWrite: {
|
|
1347
|
+
configDigest: preview.configDigest,
|
|
1348
|
+
proposedConfigDigest: preview.proposedConfigDigest,
|
|
1349
|
+
...(preview.sourceIdentity ? { sourceIdentity: preview.sourceIdentity } : {}),
|
|
1350
|
+
inventoryDigest: digest({ references: preview.references.references, errors: preview.references.errors }),
|
|
1351
|
+
impactDigest: preview.impactDigest,
|
|
1352
|
+
referenceCount: preview.references.references.length,
|
|
1353
|
+
inventoryComplete: preview.references.inventoryComplete,
|
|
1354
|
+
impact: {
|
|
1355
|
+
impactSchemaVersion: 3,
|
|
1356
|
+
sourceId: preview.sourceId,
|
|
1357
|
+
primaryChange: preview.references.primaryChange,
|
|
1358
|
+
inventoryComplete: preview.references.inventoryComplete,
|
|
1359
|
+
references: preview.references.references,
|
|
1360
|
+
affectedReferences: preview.references.references,
|
|
1361
|
+
changes: {
|
|
1362
|
+
added: [], removed: [], changed: [], newlyIncompatible: [],
|
|
1363
|
+
dependencyChanged: [], platformSupportChanged: [],
|
|
1364
|
+
},
|
|
1365
|
+
errors: preview.references.errors,
|
|
1366
|
+
},
|
|
1367
|
+
},
|
|
1368
|
+
};
|
|
1369
|
+
if (preparation?.validateIntent
|
|
1370
|
+
&& evidence.sourceWrite?.configDigest === evidence.sourceWrite?.proposedConfigDigest) {
|
|
1371
|
+
throw new TuiOperationBlockedError('sourceNoChanges', '来源没有变化。');
|
|
1372
|
+
}
|
|
1373
|
+
return evidence;
|
|
1374
|
+
}
|
|
1375
|
+
if (!pluginIntent(intent) && intent.kind !== 'pluginWriteBatch'
|
|
1376
|
+
&& intent.kind !== 'pluginWriteTask') {
|
|
1377
|
+
throw new TuiOperationBlockedError('operationNotImplemented', '这个写操作尚未接入新 Controller。');
|
|
1378
|
+
}
|
|
1379
|
+
signal?.throwIfAborted();
|
|
1380
|
+
if (intent.kind === 'pluginWriteTask') {
|
|
1381
|
+
await preparation?.reporter.emit('status');
|
|
1382
|
+
const packageInfo = await readCliPackageInfo();
|
|
1383
|
+
const configSnapshot = await readCatalogSourcesConfigSnapshot(context.env);
|
|
1384
|
+
const requestedBySource = new Map();
|
|
1385
|
+
for (const target of intent.targets) {
|
|
1386
|
+
const parsed = parsePluginId(target.pluginId);
|
|
1387
|
+
if (!parsed.sourceId || formatPluginId({
|
|
1388
|
+
sourceId: parsed.sourceId,
|
|
1389
|
+
pluginName: parsed.pluginName,
|
|
1390
|
+
}) !== target.pluginId) {
|
|
1391
|
+
throw new TuiOperationBlockedError('invalidPluginId', '任务目标必须使用 canonical pluginId。');
|
|
1392
|
+
}
|
|
1393
|
+
requestedBySource.set(parsed.sourceId, [
|
|
1394
|
+
...(requestedBySource.get(parsed.sourceId) ?? []),
|
|
1395
|
+
target,
|
|
1396
|
+
]);
|
|
1397
|
+
}
|
|
1398
|
+
const sources = [];
|
|
1399
|
+
const executableTargets = [];
|
|
1400
|
+
const causes = [];
|
|
1401
|
+
const blockedTargets = [];
|
|
1402
|
+
const observedTargets = [];
|
|
1403
|
+
const observedRevisions = [];
|
|
1404
|
+
const operationContexts = new Map();
|
|
1405
|
+
await preparation?.reporter.emit('catalog');
|
|
1406
|
+
for (const [sourceId, requestedTargets] of [...requestedBySource.entries()]
|
|
1407
|
+
.sort(([left], [right]) => left.localeCompare(right, 'en'))) {
|
|
1408
|
+
signal?.throwIfAborted();
|
|
1409
|
+
let catalogOperation;
|
|
1410
|
+
try {
|
|
1411
|
+
catalogOperation = !freshCatalog && sessionCatalogOperationContext
|
|
1412
|
+
? selectCatalogOperationContext(sessionCatalogOperationContext, [sourceId])
|
|
1413
|
+
: await resolveCatalogOperationContext({
|
|
1414
|
+
sourceIds: [sourceId],
|
|
1415
|
+
env: context.env,
|
|
1416
|
+
...(!freshCatalog ? { validatedStoreEntries, validatingStoreEntries } : {}),
|
|
1417
|
+
...(signal ? { signal } : {}),
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
catch (error) {
|
|
1421
|
+
if (error instanceof Error && error.name === 'AbortError')
|
|
1422
|
+
throw error;
|
|
1423
|
+
blockedTargets.push(...requestedTargets.map((target, index) => {
|
|
1424
|
+
const diagnostic = error instanceof CatalogGenerationError ? {
|
|
1425
|
+
id: `task-source:${sourceId}:${index}`,
|
|
1426
|
+
code: error.code,
|
|
1427
|
+
stage: 'source',
|
|
1428
|
+
sourceId,
|
|
1429
|
+
safeMessage: 'The Catalog source could not be prepared safely.',
|
|
1430
|
+
target: { pluginId: target.pluginId, platform: target.platform, scope: target.scope },
|
|
1431
|
+
causes: [],
|
|
1432
|
+
detailLines: [],
|
|
1433
|
+
} : projectThrownOperationDiagnostic({
|
|
1434
|
+
error,
|
|
1435
|
+
id: `task-source:${sourceId}:${index}`,
|
|
1436
|
+
stage: 'source',
|
|
1437
|
+
fallbackCode: 'catalogSourceResolutionFailed',
|
|
1438
|
+
sourceId,
|
|
1439
|
+
target,
|
|
1440
|
+
});
|
|
1441
|
+
return {
|
|
1442
|
+
target,
|
|
1443
|
+
reason: 'targetUnavailable',
|
|
1444
|
+
issueIds: [],
|
|
1445
|
+
errorCodes: [diagnostic.code],
|
|
1446
|
+
diagnostics: [diagnostic],
|
|
1447
|
+
};
|
|
1448
|
+
}));
|
|
1449
|
+
continue;
|
|
1450
|
+
}
|
|
1451
|
+
operationContexts.set(sourceId, catalogOperation);
|
|
1452
|
+
const status = await statusCommandV8({
|
|
1453
|
+
plugins: [...new Set(requestedTargets.map((target) => target.pluginId))],
|
|
1454
|
+
cwd: context.cwd,
|
|
1455
|
+
env: context.env,
|
|
1456
|
+
nativeCliRunner: context.nativeCliRunner,
|
|
1457
|
+
...(codexObservationSession ? { codexObservationSession } : {}),
|
|
1458
|
+
cliVersion: packageInfo.version,
|
|
1459
|
+
catalogOperationContext: catalogOperation,
|
|
1460
|
+
inventoryCatalogSourceReferences: operationInventory,
|
|
1461
|
+
});
|
|
1462
|
+
observedTargets.push(...flattenStatusTargets(status.payload.targets));
|
|
1463
|
+
observedRevisions.push(...status.targetRevisions);
|
|
1464
|
+
const model = buildTuiViewModel({
|
|
1465
|
+
status: status.payload,
|
|
1466
|
+
pluginSummaries: status.pluginSummaries,
|
|
1467
|
+
targetRevisions: status.targetRevisions,
|
|
1468
|
+
});
|
|
1469
|
+
const runtimeCandidates = intent.goal === 'restore'
|
|
1470
|
+
? buildPluginWriteTaskCandidates(model, 'restore')
|
|
1471
|
+
: requestedTargets.map((target) => {
|
|
1472
|
+
const expected = target.expectedUpdate;
|
|
1473
|
+
const revision = status.targetRevisions.find((entry) => entry.pluginId === target.pluginId && entry.platform === target.platform
|
|
1474
|
+
&& entry.scope === target.scope);
|
|
1475
|
+
const live = flattenStatusTargets(status.payload.targets).find((entry) => {
|
|
1476
|
+
if (entry.pluginId !== target.pluginId || entry.platform !== target.platform)
|
|
1477
|
+
return false;
|
|
1478
|
+
const desired = statusTargetIntent(entry);
|
|
1479
|
+
return entry.scope === target.scope || desired.scopes.includes(target.scope);
|
|
1480
|
+
});
|
|
1481
|
+
const generation = catalogOperation.generationSet.sources.find((entry) => entry.sourceId === sourceId);
|
|
1482
|
+
const eligible = Boolean(expected && live && revision
|
|
1483
|
+
&& live.sourceIdentity === expected.sourceIdentity
|
|
1484
|
+
&& generation?.storeKey === expected.generationKey
|
|
1485
|
+
&& revision.revision.installedVersion === expected.installedVersion
|
|
1486
|
+
&& revision.revision.availableVersion === expected.activeVersion
|
|
1487
|
+
&& revision.revision.state === 'upgradeAvailable');
|
|
1488
|
+
const directVersionUpgrade = live !== undefined && revision !== undefined
|
|
1489
|
+
&& isDirectCodexVersionUpgrade(live, revision.revision);
|
|
1490
|
+
return {
|
|
1491
|
+
target,
|
|
1492
|
+
displayName: model.plugins.find((plugin) => plugin.pluginId === target.pluginId)
|
|
1493
|
+
?.displayName ?? parsePluginId(target.pluginId).pluginName,
|
|
1494
|
+
sourceId,
|
|
1495
|
+
eligible,
|
|
1496
|
+
...(!eligible ? { blockReason: 'updateEvidenceIncomplete' } : {}),
|
|
1497
|
+
recommendedMethod: live?.state === 'ready' || directVersionUpgrade
|
|
1498
|
+
? 'sync' : 'reinstall',
|
|
1499
|
+
issueIds: [],
|
|
1500
|
+
};
|
|
1501
|
+
});
|
|
1502
|
+
const draft = buildPluginWriteTaskDraft({ ...intent, targets: requestedTargets }, model, runtimeCandidates);
|
|
1503
|
+
blockedTargets.push(...draft.blockedTargets);
|
|
1504
|
+
if (draft.executableTargets.length === 0)
|
|
1505
|
+
continue;
|
|
1506
|
+
const methodsByTarget = new Map();
|
|
1507
|
+
for (const cause of draft.causes) {
|
|
1508
|
+
const key = pluginWriteTaskTargetKey(cause.repairTarget);
|
|
1509
|
+
const methods = methodsByTarget.get(key) ?? new Set();
|
|
1510
|
+
methods.add(cause.method);
|
|
1511
|
+
methodsByTarget.set(key, methods);
|
|
1512
|
+
}
|
|
1513
|
+
const conflicted = new Set([...methodsByTarget].filter(([, methods]) => methods.size > 1)
|
|
1514
|
+
.map(([key]) => key));
|
|
1515
|
+
if (conflicted.size > 0) {
|
|
1516
|
+
const blockedKeys = new Set(draft.causes.filter((cause) => conflicted.has(pluginWriteTaskTargetKey(cause.repairTarget)))
|
|
1517
|
+
.flatMap((cause) => cause.consumers.map(pluginWriteTaskTargetKey)));
|
|
1518
|
+
blockedTargets.push(...draft.executableTargets.filter((target) => blockedKeys.has(pluginWriteTaskTargetKey(target))).map((target) => ({
|
|
1519
|
+
target,
|
|
1520
|
+
reason: 'taskActionConflict',
|
|
1521
|
+
issueIds: draft.causes.filter((cause) => cause.consumers.some((consumer) => pluginWriteTaskTargetKey(consumer) === pluginWriteTaskTargetKey(target)))
|
|
1522
|
+
.map((cause) => cause.issueId),
|
|
1523
|
+
})));
|
|
1524
|
+
draft.executableTargets = draft.executableTargets.filter((target) => !blockedKeys.has(pluginWriteTaskTargetKey(target)));
|
|
1525
|
+
draft.causes = draft.causes.filter((cause) => cause.consumers.some((consumer) => !blockedKeys.has(pluginWriteTaskTargetKey(consumer))));
|
|
1526
|
+
}
|
|
1527
|
+
if (draft.executableTargets.length === 0)
|
|
1528
|
+
continue;
|
|
1529
|
+
const taskTargets = () => draft.executableTargets.map((target) => {
|
|
1530
|
+
const direct = draft.causes.filter((cause) => pluginWriteTaskTargetKey(cause.repairTarget) === pluginWriteTaskTargetKey(target));
|
|
1531
|
+
return {
|
|
1532
|
+
pluginId: target.pluginId,
|
|
1533
|
+
platform: target.platform,
|
|
1534
|
+
scope: target.scope,
|
|
1535
|
+
method: direct.some((cause) => cause.method === 'reinstall')
|
|
1536
|
+
? 'reinstall' : 'sync',
|
|
1537
|
+
};
|
|
1538
|
+
});
|
|
1539
|
+
const repairTargets = () => [...new Map(draft.causes.map((cause) => [
|
|
1540
|
+
pluginWriteTaskTargetKey(cause.repairTarget),
|
|
1541
|
+
{ ...cause.repairTarget, method: cause.method },
|
|
1542
|
+
])).values()];
|
|
1543
|
+
let preview;
|
|
1544
|
+
while (draft.executableTargets.length > 0) {
|
|
1545
|
+
preview = await taskWriteCommand(taskTargets(), {
|
|
1546
|
+
cwd: context.cwd,
|
|
1547
|
+
repairTargets: repairTargets(),
|
|
1548
|
+
...(reviewedScriptTimeoutMs(intent) === undefined
|
|
1549
|
+
? {} : { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
|
|
1550
|
+
}, {
|
|
1551
|
+
...context,
|
|
1552
|
+
catalogOperationContext: catalogOperation,
|
|
1553
|
+
expectedGenerationSetDigest: catalogOperation.generationSetDigest,
|
|
1554
|
+
expectedCatalogConfigDigest: configSnapshot.digest,
|
|
1555
|
+
inventoryCatalogSourceReferences: operationInventory,
|
|
1556
|
+
previewOperationPlan: true,
|
|
1557
|
+
...(codexObservationSession ? { codexObservationSession } : {}),
|
|
1558
|
+
...(preparation ? {
|
|
1559
|
+
onPreviewStage: (stage) => stage === 'catalog' ? undefined : preparation.reporter.emit(stage),
|
|
1560
|
+
} : {}),
|
|
1561
|
+
});
|
|
1562
|
+
if (!preview.operationPlan) {
|
|
1563
|
+
const projected = projectExecutionDiagnostics(preview, `task-preview:${sourceId}`, [], 'pluginWrite');
|
|
1564
|
+
const sourceDiagnostics = projected.diagnostics.filter((diagnostic) => !diagnostic.target)
|
|
1565
|
+
.map((diagnostic) => ({ ...diagnostic, sourceId, target: undefined }));
|
|
1566
|
+
blockedTargets.push(...draft.executableTargets.map((target, targetIndex) => {
|
|
1567
|
+
const matchingDiagnostics = projected.diagnostics.filter((diagnostic) => diagnostic.target?.pluginId === target.pluginId
|
|
1568
|
+
&& diagnostic.target.platform === target.platform
|
|
1569
|
+
&& diagnostic.target.scope === target.scope);
|
|
1570
|
+
const preparedDiagnostics = [
|
|
1571
|
+
...matchingDiagnostics,
|
|
1572
|
+
...(targetIndex === 0 ? sourceDiagnostics : []),
|
|
1573
|
+
];
|
|
1574
|
+
const targetDiagnostics = preparedDiagnostics.length > 0
|
|
1575
|
+
? preparedDiagnostics
|
|
1576
|
+
: [projectThrownOperationDiagnostic({
|
|
1577
|
+
error: undefined,
|
|
1578
|
+
id: `task-preview:${sourceId}:${targetIndex}`,
|
|
1579
|
+
stage: 'dependencyPlan',
|
|
1580
|
+
fallbackCode: 'taskOperationPlanMissing',
|
|
1581
|
+
sourceId,
|
|
1582
|
+
target,
|
|
1583
|
+
})];
|
|
1584
|
+
const diagnostics = targetDiagnostics.map((diagnostic, diagnosticIndex) => ({
|
|
1585
|
+
...structuredClone(diagnostic),
|
|
1586
|
+
id: `task-preview:${sourceId}:${targetIndex}:${diagnosticIndex}`,
|
|
1587
|
+
sourceId,
|
|
1588
|
+
}));
|
|
1589
|
+
return {
|
|
1590
|
+
target,
|
|
1591
|
+
reason: 'targetUnavailable',
|
|
1592
|
+
issueIds: draft.causes.filter((cause) => cause.consumers.some((consumer) => pluginWriteTaskTargetKey(consumer) === pluginWriteTaskTargetKey(target)))
|
|
1593
|
+
.map((cause) => cause.issueId),
|
|
1594
|
+
errorCodes: [...new Set(diagnostics.map((diagnostic) => diagnostic.code))],
|
|
1595
|
+
diagnostics,
|
|
1596
|
+
};
|
|
1597
|
+
}));
|
|
1598
|
+
draft.executableTargets = [];
|
|
1599
|
+
draft.causes = [];
|
|
1600
|
+
break;
|
|
1601
|
+
}
|
|
1602
|
+
if (intent.goal === 'restore') {
|
|
1603
|
+
const grouped = new Map();
|
|
1604
|
+
const blockedByVersion = new Map();
|
|
1605
|
+
for (const cause of draft.causes) {
|
|
1606
|
+
const placements = (preview.dependencyPlan?.targets ?? []).filter((target) => target.pluginId === cause.repairTarget.pluginId
|
|
1607
|
+
&& target.platform === cause.repairTarget.platform);
|
|
1608
|
+
const placement = placements.find((target) => target.requestedScope === cause.repairTarget.scope)
|
|
1609
|
+
?? (placements.length === 1 ? placements[0] : undefined);
|
|
1610
|
+
if (!placement) {
|
|
1611
|
+
throw new TuiOperationBlockedError('taskRepairPlacementMissing', '任务修复目标无法绑定到依赖位置。');
|
|
1612
|
+
}
|
|
1613
|
+
const repairTarget = {
|
|
1614
|
+
pluginId: cause.repairTarget.pluginId,
|
|
1615
|
+
platform: cause.repairTarget.platform,
|
|
1616
|
+
scope: placement.requestedScope ?? placement.resolvedScope,
|
|
1617
|
+
};
|
|
1618
|
+
const version = evaluatePluginWriteTaskRestoreVersion(repairTarget, cause.restoreVersionEvidence?.state === 'absent', model);
|
|
1619
|
+
if (!version.evidence || version.blockReason) {
|
|
1620
|
+
for (const consumer of cause.consumers) {
|
|
1621
|
+
const key = pluginWriteTaskTargetKey(consumer);
|
|
1622
|
+
const current = blockedByVersion.get(key) ?? {
|
|
1623
|
+
reason: version.blockReason ?? 'restoreVersionUnverified',
|
|
1624
|
+
issueIds: new Set(),
|
|
1625
|
+
};
|
|
1626
|
+
if (version.blockReason === 'restoreVersionChanged') {
|
|
1627
|
+
current.reason = version.blockReason;
|
|
1628
|
+
}
|
|
1629
|
+
current.issueIds.add(cause.issueId);
|
|
1630
|
+
blockedByVersion.set(key, current);
|
|
1631
|
+
}
|
|
1632
|
+
continue;
|
|
1633
|
+
}
|
|
1634
|
+
const causeKey = ['restore', cause.sourceIdentity,
|
|
1635
|
+
pluginWriteTaskTargetKey(repairTarget)].join('\0');
|
|
1636
|
+
const current = grouped.get(causeKey);
|
|
1637
|
+
grouped.set(causeKey, {
|
|
1638
|
+
cause: {
|
|
1639
|
+
...cause,
|
|
1640
|
+
causeKey,
|
|
1641
|
+
issueId: [cause.issueId, ...(current?.issueIds ?? [])]
|
|
1642
|
+
.sort((left, right) => left.localeCompare(right, 'en'))[0],
|
|
1643
|
+
repairTarget,
|
|
1644
|
+
consumers: [...new Map([
|
|
1645
|
+
...(current?.cause.consumers ?? []),
|
|
1646
|
+
...cause.consumers,
|
|
1647
|
+
].map((consumer) => [pluginWriteTaskTargetKey(consumer), consumer])).values()]
|
|
1648
|
+
.sort((left, right) => pluginWriteTaskTargetKey(left)
|
|
1649
|
+
.localeCompare(pluginWriteTaskTargetKey(right), 'en')),
|
|
1650
|
+
restoreVersionEvidence: version.evidence,
|
|
1651
|
+
},
|
|
1652
|
+
issueIds: new Set([...(current?.issueIds ?? []), cause.issueId]),
|
|
1653
|
+
methods: new Set([...(current?.methods ?? []), cause.method]),
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
for (const group of grouped.values()) {
|
|
1657
|
+
if (group.methods.size < 2)
|
|
1658
|
+
continue;
|
|
1659
|
+
for (const consumer of group.cause.consumers) {
|
|
1660
|
+
const key = pluginWriteTaskTargetKey(consumer);
|
|
1661
|
+
const current = blockedByVersion.get(key) ?? {
|
|
1662
|
+
reason: 'taskActionConflict',
|
|
1663
|
+
issueIds: new Set(),
|
|
1664
|
+
};
|
|
1665
|
+
group.issueIds.forEach((issueId) => current.issueIds.add(issueId));
|
|
1666
|
+
blockedByVersion.set(key, current);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
const newlyBlockedKeys = new Set(blockedByVersion.keys());
|
|
1670
|
+
blockedTargets.push(...draft.executableTargets.flatMap((target) => {
|
|
1671
|
+
const blocked = blockedByVersion.get(pluginWriteTaskTargetKey(target));
|
|
1672
|
+
return blocked ? [{
|
|
1673
|
+
target,
|
|
1674
|
+
reason: blocked.reason,
|
|
1675
|
+
issueIds: [...blocked.issueIds].sort((left, right) => left.localeCompare(right, 'en')),
|
|
1676
|
+
}] : [];
|
|
1677
|
+
}));
|
|
1678
|
+
const normalizedCauses = [...grouped.values()]
|
|
1679
|
+
.filter((group) => group.methods.size === 1)
|
|
1680
|
+
.map((group) => ({
|
|
1681
|
+
...group.cause,
|
|
1682
|
+
consumers: group.cause.consumers.filter((consumer) => !newlyBlockedKeys.has(pluginWriteTaskTargetKey(consumer))),
|
|
1683
|
+
}))
|
|
1684
|
+
.filter((cause) => cause.consumers.length > 0)
|
|
1685
|
+
.sort((left, right) => left.causeKey.localeCompare(right.causeKey, 'en'));
|
|
1686
|
+
const normalizedTargets = draft.executableTargets.filter((target) => !newlyBlockedKeys.has(pluginWriteTaskTargetKey(target)));
|
|
1687
|
+
const changed = JSON.stringify(normalizedCauses) !== JSON.stringify(draft.causes)
|
|
1688
|
+
|| JSON.stringify(normalizedTargets) !== JSON.stringify(draft.executableTargets);
|
|
1689
|
+
draft.causes = normalizedCauses;
|
|
1690
|
+
draft.executableTargets = normalizedTargets;
|
|
1691
|
+
if (changed)
|
|
1692
|
+
continue;
|
|
1693
|
+
}
|
|
1694
|
+
const blockedPlanTargets = (preview.dependencyPlan?.targets ?? []).filter((target) => target.action === 'block' || target.errorCode !== undefined);
|
|
1695
|
+
if (blockedPlanTargets.length === 0)
|
|
1696
|
+
break;
|
|
1697
|
+
const requestedKeys = new Set(draft.executableTargets.map(pluginWriteTaskTargetKey));
|
|
1698
|
+
const blockedPlansByRoot = new Map();
|
|
1699
|
+
for (const blockedPlanTarget of blockedPlanTargets) {
|
|
1700
|
+
const pending = [blockedPlanTarget];
|
|
1701
|
+
const visited = new Set();
|
|
1702
|
+
const mappedRoots = new Set();
|
|
1703
|
+
const repairKey = pluginWriteTaskTargetKey({
|
|
1704
|
+
pluginId: blockedPlanTarget.pluginId,
|
|
1705
|
+
platform: blockedPlanTarget.platform,
|
|
1706
|
+
scope: blockedPlanTarget.requestedScope ?? blockedPlanTarget.resolvedScope,
|
|
1707
|
+
});
|
|
1708
|
+
for (const cause of draft.causes) {
|
|
1709
|
+
if (pluginWriteTaskTargetKey(cause.repairTarget) !== repairKey)
|
|
1710
|
+
continue;
|
|
1711
|
+
cause.consumers.forEach((consumer) => mappedRoots.add(pluginWriteTaskTargetKey(consumer)));
|
|
1712
|
+
}
|
|
1713
|
+
while (pending.length > 0) {
|
|
1714
|
+
const current = pending.shift();
|
|
1715
|
+
const currentKey = pluginWriteTaskTargetKey({
|
|
1716
|
+
pluginId: current.pluginId,
|
|
1717
|
+
platform: current.platform,
|
|
1718
|
+
scope: current.requestedScope ?? current.resolvedScope,
|
|
1719
|
+
});
|
|
1720
|
+
if (visited.has(currentKey))
|
|
1721
|
+
continue;
|
|
1722
|
+
visited.add(currentKey);
|
|
1723
|
+
if (current.relation === 'requested' && requestedKeys.has(currentKey)) {
|
|
1724
|
+
mappedRoots.add(currentKey);
|
|
1725
|
+
}
|
|
1726
|
+
for (const consumer of current.requiredByTargets ?? []) {
|
|
1727
|
+
const consumerKey = pluginWriteTaskTargetKey(consumer);
|
|
1728
|
+
if (requestedKeys.has(consumerKey)) {
|
|
1729
|
+
mappedRoots.add(consumerKey);
|
|
1730
|
+
continue;
|
|
1731
|
+
}
|
|
1732
|
+
const placement = preview.dependencyPlan?.targets.find((target) => target.pluginId === consumer.pluginId && target.platform === consumer.platform
|
|
1733
|
+
&& (target.requestedScope ?? target.resolvedScope) === consumer.scope);
|
|
1734
|
+
if (placement)
|
|
1735
|
+
pending.push(placement);
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
if (mappedRoots.size === 0) {
|
|
1739
|
+
throw new TuiOperationBlockedError('taskPlanConsumerMissing', '任务计划中的阻止项无法映射到请求目标。');
|
|
1740
|
+
}
|
|
1741
|
+
for (const key of mappedRoots) {
|
|
1742
|
+
const plans = blockedPlansByRoot.get(key) ?? [];
|
|
1743
|
+
plans.push(blockedPlanTarget);
|
|
1744
|
+
blockedPlansByRoot.set(key, plans);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
blockedTargets.push(...draft.executableTargets.flatMap((target) => {
|
|
1748
|
+
const plans = blockedPlansByRoot.get(pluginWriteTaskTargetKey(target));
|
|
1749
|
+
return plans ? [{
|
|
1750
|
+
target,
|
|
1751
|
+
reason: 'targetUnavailable',
|
|
1752
|
+
issueIds: draft.causes.filter((cause) => cause.consumers.some((consumer) => pluginWriteTaskTargetKey(consumer) === pluginWriteTaskTargetKey(target)))
|
|
1753
|
+
.map((cause) => cause.issueId),
|
|
1754
|
+
errorCodes: [...new Set(plans.map((plan) => plan.errorCode ?? 'writePlanBlocked'))]
|
|
1755
|
+
.sort((left, right) => left.localeCompare(right, 'en')),
|
|
1756
|
+
diagnostics: plans.map((plan, index) => ({
|
|
1757
|
+
id: `task-plan:${sourceId}:${target.pluginId}:${target.platform}:${target.scope}:${index}`,
|
|
1758
|
+
code: plan.errorCode ?? 'writePlanBlocked',
|
|
1759
|
+
stage: 'dependencyPlan',
|
|
1760
|
+
sourceId,
|
|
1761
|
+
target: {
|
|
1762
|
+
pluginId: plan.pluginId, platform: plan.platform,
|
|
1763
|
+
scope: plan.requestedScope ?? plan.resolvedScope,
|
|
1764
|
+
},
|
|
1765
|
+
causes: [],
|
|
1766
|
+
detailLines: [],
|
|
1767
|
+
safeMessage: plan.errorCode === 'artifactCollision'
|
|
1768
|
+
? '输出位置存在外来内容,无法覆盖。'
|
|
1769
|
+
: plan.errorCode === 'exclusiveOwnerEvidenceInvalid'
|
|
1770
|
+
? '独立扩展的归属标记缺失或损坏。'
|
|
1771
|
+
: plan.errorCode === 'dependencyNotReady'
|
|
1772
|
+
? '依赖安装状态无法读取。'
|
|
1773
|
+
: '依赖计划阻止了此目标。',
|
|
1774
|
+
})),
|
|
1775
|
+
}] : [];
|
|
1776
|
+
}));
|
|
1777
|
+
draft.executableTargets = draft.executableTargets.filter((target) => !blockedPlansByRoot.has(pluginWriteTaskTargetKey(target)));
|
|
1778
|
+
draft.causes = draft.causes.map((cause) => ({
|
|
1779
|
+
...cause,
|
|
1780
|
+
consumers: cause.consumers.filter((consumer) => !blockedPlansByRoot.has(pluginWriteTaskTargetKey(consumer))),
|
|
1781
|
+
})).filter((cause) => cause.consumers.length > 0);
|
|
1782
|
+
}
|
|
1783
|
+
if (!preview?.operationPlan || draft.executableTargets.length === 0)
|
|
1784
|
+
continue;
|
|
1785
|
+
const sourceIdentity = draft.causes[0]?.sourceIdentity
|
|
1786
|
+
?? status.payload.catalog.sources.find((source) => source.id === sourceId)?.identity;
|
|
1787
|
+
if (!sourceIdentity) {
|
|
1788
|
+
throw new TuiOperationBlockedError('taskSourceIdentityMissing', '任务来源身份不可验证。');
|
|
1789
|
+
}
|
|
1790
|
+
const sourceEvidence = {
|
|
1791
|
+
sourceId,
|
|
1792
|
+
sourceIdentity,
|
|
1793
|
+
requestedTargets: draft.executableTargets,
|
|
1794
|
+
causes: draft.causes,
|
|
1795
|
+
preview,
|
|
1796
|
+
executable: true,
|
|
1797
|
+
};
|
|
1798
|
+
sources.push(sourceEvidence);
|
|
1799
|
+
executableTargets.push(...draft.executableTargets);
|
|
1800
|
+
causes.push(...draft.causes);
|
|
1801
|
+
}
|
|
1802
|
+
const canonicalBlocked = [...new Map(blockedTargets.map((target) => [
|
|
1803
|
+
pluginWriteTaskTargetKey(target.target), target,
|
|
1804
|
+
])).values()].sort((left, right) => pluginWriteTaskTargetKey(left.target)
|
|
1805
|
+
.localeCompare(pluginWriteTaskTargetKey(right.target), 'en'));
|
|
1806
|
+
if (executableTargets.length === 0) {
|
|
1807
|
+
throw new TuiOperationBlockedError('taskNoExecutableTargets', '任务没有可安全执行的目标。', canonicalBlocked.flatMap((target) => target.diagnostics ?? []));
|
|
1808
|
+
}
|
|
1809
|
+
if (intent.executionPolicy === 'all' && canonicalBlocked.length > 0) {
|
|
1810
|
+
throw new TuiOperationBlockedError('taskHasBlockedTargets', '部分目标需要先处理。请调整选择。', canonicalBlocked.flatMap((target) => target.diagnostics ?? []));
|
|
1811
|
+
}
|
|
1812
|
+
await preparation?.reporter.emit('evidence');
|
|
1813
|
+
const evidence = {
|
|
1814
|
+
catalog: {
|
|
1815
|
+
configDigest: configSnapshot.digest,
|
|
1816
|
+
compositeDigest: digest(sources.map((source) => [
|
|
1817
|
+
source.sourceId,
|
|
1818
|
+
source.sourceIdentity,
|
|
1819
|
+
source.preview.operationPlan?.planFingerprint,
|
|
1820
|
+
])),
|
|
1821
|
+
},
|
|
1822
|
+
taskPlan: {
|
|
1823
|
+
goal: intent.goal,
|
|
1824
|
+
requestedTargets: intent.targets,
|
|
1825
|
+
executableTargets: [...executableTargets].sort((left, right) => pluginWriteTaskTargetKey(left).localeCompare(pluginWriteTaskTargetKey(right), 'en')),
|
|
1826
|
+
causes: [...new Map(causes.map((cause) => [cause.causeKey, cause])).values()]
|
|
1827
|
+
.sort((left, right) => left.causeKey.localeCompare(right.causeKey, 'en')),
|
|
1828
|
+
blockedTargets: canonicalBlocked,
|
|
1829
|
+
sources,
|
|
1830
|
+
targetFingerprint: statusTargetFingerprint(observedTargets, observedRevisions),
|
|
1831
|
+
planFingerprint: '',
|
|
1832
|
+
},
|
|
1833
|
+
};
|
|
1834
|
+
evidence.taskPlan.planFingerprint = operationEvidenceFingerprint(evidence);
|
|
1835
|
+
taskCatalogOperationsByEvidence.set(evidence, operationContexts);
|
|
1836
|
+
return evidence;
|
|
1837
|
+
}
|
|
1838
|
+
if (intent.kind === 'pluginWriteBatch') {
|
|
1839
|
+
await preparation?.reporter.emit('status');
|
|
1840
|
+
const selection = await resolveBatchGroups(intent, context.env, context.cwd, frozenBatchEvidence?.requestedPluginIds);
|
|
1841
|
+
signal?.throwIfAborted();
|
|
1842
|
+
if (intent.action === 'cleanup') {
|
|
1843
|
+
const groups = [];
|
|
1844
|
+
for (const group of selection.groups) {
|
|
1845
|
+
signal?.throwIfAborted();
|
|
1846
|
+
const preview = await runBatchWriteGroup({
|
|
1847
|
+
intent,
|
|
1848
|
+
group,
|
|
1849
|
+
cwd: context.cwd,
|
|
1850
|
+
runtime: {
|
|
1851
|
+
...context,
|
|
1852
|
+
inventoryCatalogSourceReferences: operationInventory,
|
|
1853
|
+
previewOperationPlan: true,
|
|
1854
|
+
...(codexObservationSession ? { codexObservationSession } : {}),
|
|
1855
|
+
...(preparation ? {
|
|
1856
|
+
onPreviewStage: (stage) => stage === 'catalog' ? undefined : preparation.reporter.emit(stage),
|
|
1857
|
+
} : {}),
|
|
1858
|
+
},
|
|
1859
|
+
});
|
|
1860
|
+
groups.push({ ...group, preview, executable: preview.operationPlan !== undefined });
|
|
1861
|
+
}
|
|
1862
|
+
await preparation?.reporter.emit('evidence');
|
|
1863
|
+
signal?.throwIfAborted();
|
|
1864
|
+
const evidence = {
|
|
1865
|
+
catalog: {
|
|
1866
|
+
configDigest: 'cleanup-offline',
|
|
1867
|
+
compositeDigest: digest(groups.map((group) => [
|
|
1868
|
+
group.groupId,
|
|
1869
|
+
group.preview.operationPlan?.planFingerprint,
|
|
1870
|
+
])),
|
|
1871
|
+
},
|
|
1872
|
+
requestedPluginIds: selection.requestedPluginIds,
|
|
1873
|
+
groups,
|
|
1874
|
+
};
|
|
1875
|
+
return evidence;
|
|
1876
|
+
}
|
|
1877
|
+
const configSnapshot = await readCatalogSourcesConfigSnapshot(context.env);
|
|
1878
|
+
const groups = [];
|
|
1879
|
+
const ownsObservationSession = !codexObservationSession
|
|
1880
|
+
&& selection.groups.some((group) => {
|
|
1881
|
+
const reviewed = frozenBatchEvidence?.groups.find((candidate) => candidate.groupId === group.groupId);
|
|
1882
|
+
return reviewed?.executable !== false
|
|
1883
|
+
&& (group.platform === 'codex' || group.platform === 'both');
|
|
1884
|
+
});
|
|
1885
|
+
const observationSession = codexObservationSession ?? (ownsObservationSession
|
|
1886
|
+
? await createCodexNativeObservationSession({
|
|
1887
|
+
exactCwd: context.cwd,
|
|
1888
|
+
env: context.env,
|
|
1889
|
+
runner: context.nativeCliRunner,
|
|
1890
|
+
})
|
|
1891
|
+
: undefined);
|
|
1892
|
+
try {
|
|
1893
|
+
await preparation?.reporter.emit('catalog');
|
|
1894
|
+
for (const group of selection.groups) {
|
|
1895
|
+
signal?.throwIfAborted();
|
|
1896
|
+
const reviewed = frozenBatchEvidence?.groups.find((candidate) => candidate.groupId === group.groupId);
|
|
1897
|
+
if (reviewed?.executable === false) {
|
|
1898
|
+
groups.push({ ...group, preview: reviewed.preview, executable: false });
|
|
1899
|
+
continue;
|
|
1900
|
+
}
|
|
1901
|
+
let catalogOperation;
|
|
1902
|
+
try {
|
|
1903
|
+
catalogOperation = !freshCatalog && sessionCatalogOperationContext
|
|
1904
|
+
? selectCatalogOperationContext(sessionCatalogOperationContext, [group.sourceId])
|
|
1905
|
+
: await resolveCatalogOperationContext({
|
|
1906
|
+
sourceIds: [group.sourceId],
|
|
1907
|
+
env: context.env,
|
|
1908
|
+
...(!freshCatalog ? { validatedStoreEntries, validatingStoreEntries } : {}),
|
|
1909
|
+
...(signal ? { signal } : {}),
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1912
|
+
catch {
|
|
1913
|
+
catalogOperation = undefined;
|
|
1914
|
+
}
|
|
1915
|
+
const preview = await runBatchWriteGroup({
|
|
1916
|
+
intent,
|
|
1917
|
+
group,
|
|
1918
|
+
cwd: context.cwd,
|
|
1919
|
+
runtime: {
|
|
1920
|
+
...context,
|
|
1921
|
+
inventoryCatalogSourceReferences: operationInventory,
|
|
1922
|
+
expectedCatalogConfigDigest: configSnapshot.digest,
|
|
1923
|
+
...(catalogOperation ? {
|
|
1924
|
+
catalogOperationContext: catalogOperation,
|
|
1925
|
+
expectedGenerationSetDigest: catalogOperation.generationSetDigest,
|
|
1926
|
+
} : {}),
|
|
1927
|
+
previewOperationPlan: true,
|
|
1928
|
+
...(observationSession ? { codexObservationSession: observationSession } : {}),
|
|
1929
|
+
...(preparation ? {
|
|
1930
|
+
onPreviewStage: (stage) => stage === 'catalog' ? undefined : preparation.reporter.emit(stage),
|
|
1931
|
+
} : {}),
|
|
1932
|
+
},
|
|
1933
|
+
});
|
|
1934
|
+
groups.push({ ...group, preview, executable: preview.operationPlan !== undefined });
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
finally {
|
|
1938
|
+
if (ownsObservationSession)
|
|
1939
|
+
await observationSession?.dispose();
|
|
1940
|
+
}
|
|
1941
|
+
await preparation?.reporter.emit('evidence');
|
|
1942
|
+
signal?.throwIfAborted();
|
|
1943
|
+
const merged = mergeWriteCommandResults(groups.map((group) => group.preview));
|
|
1944
|
+
const targetVersions = groups.flatMap((group) => {
|
|
1945
|
+
if (group.platform === 'both')
|
|
1946
|
+
return [];
|
|
1947
|
+
const platform = group.platform;
|
|
1948
|
+
return group.requestedPluginIds.map((pluginId) => {
|
|
1949
|
+
const live = flattenStatusTargets(sessionStatusResult?.payload.targets ?? []).find((target) => target.pluginId === pluginId && target.platform === platform
|
|
1950
|
+
&& (target.scope === group.scope || statusTargetIntent(target).scopes.includes(group.scope)
|
|
1951
|
+
|| platform === 'codex' && statusTargetActivation(target)?.[group.scope] === true));
|
|
1952
|
+
const revision = sessionStatusResult?.targetRevisions?.find((target) => target.pluginId === pluginId && target.platform === platform
|
|
1953
|
+
&& target.scope === group.scope)?.revision;
|
|
1954
|
+
const activation = live ? statusTargetActivation(live) : undefined;
|
|
1955
|
+
const ownership = live && live.scope === group.scope
|
|
1956
|
+
? statusTargetOwnershipState(live) : 'absent';
|
|
1957
|
+
const present = ownership === 'present' || ownership === 'partial'
|
|
1958
|
+
|| platform === 'codex' && activation?.[group.scope] === true;
|
|
1959
|
+
const planned = live ? statusTargetIntent(live).scopes.includes(group.scope) : false;
|
|
1960
|
+
return {
|
|
1961
|
+
pluginId,
|
|
1962
|
+
platform,
|
|
1963
|
+
scope: group.scope,
|
|
1964
|
+
presence: present ? 'installed'
|
|
1965
|
+
: ownership === 'absent' && planned ? 'new' : 'unknown',
|
|
1966
|
+
...(revision?.installedVersion ? { installedVersion: revision.installedVersion } : {}),
|
|
1967
|
+
...(revision?.availableVersion ? { availableVersion: revision.availableVersion } : {}),
|
|
1968
|
+
};
|
|
1969
|
+
});
|
|
1970
|
+
});
|
|
1971
|
+
const evidence = {
|
|
1972
|
+
catalog: {
|
|
1973
|
+
configDigest: configSnapshot.digest,
|
|
1974
|
+
compositeDigest: sessionStatusResult?.payload.catalog.compositeDigest
|
|
1975
|
+
?? digest(groups.map((group) => [group.groupId, group.preview.operationPlan?.planFingerprint])),
|
|
1976
|
+
},
|
|
1977
|
+
requestedPluginIds: selection.requestedPluginIds,
|
|
1978
|
+
...(selection.projectIntentDigest
|
|
1979
|
+
? { projectIntentDigest: selection.projectIntentDigest }
|
|
1980
|
+
: {}),
|
|
1981
|
+
groups,
|
|
1982
|
+
targetVersions: [...new Map(targetVersions.map((target) => [[
|
|
1983
|
+
target.pluginId, target.platform, target.scope,
|
|
1984
|
+
].join('\0'), target])).values()].sort((left, right) => [
|
|
1985
|
+
left.pluginId, left.platform, left.scope,
|
|
1986
|
+
].join('\0').localeCompare([
|
|
1987
|
+
right.pluginId, right.platform, right.scope,
|
|
1988
|
+
].join('\0'), 'en')),
|
|
1989
|
+
...(merged.dependencyPlan ? { dependencyPlan: merged.dependencyPlan } : {}),
|
|
1990
|
+
...(merged.codexReconcilePlans ? { codexReconcilePlans: merged.codexReconcilePlans } : {}),
|
|
1991
|
+
...(merged.operationPlan ? { operationPlan: merged.operationPlan } : {}),
|
|
1992
|
+
};
|
|
1993
|
+
return evidence;
|
|
1994
|
+
}
|
|
1995
|
+
if (intent.kind === 'pluginWrite' && intent.action === 'cleanup') {
|
|
1996
|
+
const preview = await cleanupWriteCommand([intent.pluginId], {
|
|
1997
|
+
cwd: context.cwd,
|
|
1998
|
+
platform: intent.platform,
|
|
1999
|
+
scope: intent.scope,
|
|
2000
|
+
force: intent.force === true,
|
|
2001
|
+
}, {
|
|
2002
|
+
...context,
|
|
2003
|
+
inventoryCatalogSourceReferences: operationInventory,
|
|
2004
|
+
previewOperationPlan: true,
|
|
2005
|
+
...(codexObservationSession ? { codexObservationSession } : {}),
|
|
2006
|
+
...(preparation ? {
|
|
2007
|
+
onPreviewStage: (stage) => stage === 'catalog' ? undefined : preparation.reporter.emit(stage),
|
|
2008
|
+
} : {}),
|
|
2009
|
+
});
|
|
2010
|
+
signal?.throwIfAborted();
|
|
2011
|
+
if (!preview.operationPlan) {
|
|
2012
|
+
throw new TuiOperationBlockedError('cleanupPlanUnavailable', '无法建立可信的清理计划。');
|
|
2013
|
+
}
|
|
2014
|
+
const offlineCleanupPlans = preview.offlineCleanupPlans ?? [];
|
|
2015
|
+
await preparation?.reporter.emit('evidence');
|
|
2016
|
+
return {
|
|
2017
|
+
catalog: {
|
|
2018
|
+
configDigest: 'cleanup-offline',
|
|
2019
|
+
compositeDigest: preview.operationPlan.planFingerprint,
|
|
2020
|
+
},
|
|
2021
|
+
operationPlan: preview.operationPlan,
|
|
2022
|
+
offlineCleanupPlans,
|
|
2023
|
+
...(offlineCleanupPlans.length === 1
|
|
2024
|
+
? { offlineCleanup: offlineCleanupPlans[0] }
|
|
2025
|
+
: {}),
|
|
2026
|
+
targetFingerprint: preview.operationPlan.planFingerprint,
|
|
2027
|
+
};
|
|
2028
|
+
}
|
|
2029
|
+
const packageInfo = await readCliPackageInfo();
|
|
2030
|
+
let catalogOperation;
|
|
2031
|
+
let catalogOperationError;
|
|
2032
|
+
if (intent.kind !== 'portableInstall') {
|
|
2033
|
+
try {
|
|
2034
|
+
const sourceIds = [parsePluginId(intent.pluginId).sourceId];
|
|
2035
|
+
catalogOperation = !freshCatalog && sessionCatalogOperationContext
|
|
2036
|
+
? selectCatalogOperationContext(sessionCatalogOperationContext, sourceIds)
|
|
2037
|
+
: await resolveCatalogOperationContext({
|
|
2038
|
+
sourceIds,
|
|
2039
|
+
env: context.env,
|
|
2040
|
+
...(!freshCatalog ? { validatedStoreEntries, validatingStoreEntries } : {}),
|
|
2041
|
+
...(signal ? { signal } : {}),
|
|
2042
|
+
});
|
|
2043
|
+
}
|
|
2044
|
+
catch (error) {
|
|
2045
|
+
catalogOperationError = error;
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
if (intent.kind === 'portableInstall') {
|
|
2049
|
+
await preparation?.reporter.emit('portable');
|
|
2050
|
+
if (intent.retry
|
|
2051
|
+
&& (intent.retry.receipt.plugin.id !== intent.pluginId
|
|
2052
|
+
|| intent.retry.receipt.scope !== intent.scope)) {
|
|
2053
|
+
throw operationReviewStale('Portable 重试结果与当前插件或范围不匹配。');
|
|
2054
|
+
}
|
|
2055
|
+
const portableSourceId = parsePluginId(intent.pluginId).sourceId;
|
|
2056
|
+
const portableCatalogOperation = sessionCatalogOperationContext
|
|
2057
|
+
? selectCatalogOperationContext(sessionCatalogOperationContext, [portableSourceId])
|
|
2058
|
+
: undefined;
|
|
2059
|
+
const portable = intent.retry
|
|
2060
|
+
? await preparePortableRetry({
|
|
2061
|
+
receipt: intent.retry.receipt,
|
|
2062
|
+
cwd: context.cwd,
|
|
2063
|
+
env: context.env,
|
|
2064
|
+
cliVersion: packageInfo.version,
|
|
2065
|
+
signal,
|
|
2066
|
+
...(portableCatalogOperation
|
|
2067
|
+
? { catalogOperationContext: portableCatalogOperation }
|
|
2068
|
+
: {}),
|
|
2069
|
+
})
|
|
2070
|
+
: await preparePortableInstall({
|
|
2071
|
+
selector: intent.pluginId,
|
|
2072
|
+
scope: intent.scope,
|
|
2073
|
+
...(intent.selectedSkills.length > 0 ? { requestedSkills: intent.selectedSkills } : {}),
|
|
2074
|
+
...(intent.requestedAgents ? { requestedAgents: intent.requestedAgents } : {}),
|
|
2075
|
+
cwd: context.cwd,
|
|
2076
|
+
env: context.env,
|
|
2077
|
+
cliVersion: packageInfo.version,
|
|
2078
|
+
signal,
|
|
2079
|
+
...(portableCatalogOperation
|
|
2080
|
+
? { catalogOperationContext: portableCatalogOperation }
|
|
2081
|
+
: {}),
|
|
2082
|
+
});
|
|
2083
|
+
signal?.throwIfAborted();
|
|
2084
|
+
await preparation?.reporter.emit('evidence');
|
|
2085
|
+
signal?.throwIfAborted();
|
|
2086
|
+
const evidence = {
|
|
2087
|
+
catalog: {
|
|
2088
|
+
configDigest: portable.configDigest,
|
|
2089
|
+
compositeDigest: portable.evidenceDigest,
|
|
2090
|
+
...(portable.generationSetDigest
|
|
2091
|
+
? { generationSetDigest: portable.generationSetDigest }
|
|
2092
|
+
: {}),
|
|
2093
|
+
},
|
|
2094
|
+
portable,
|
|
2095
|
+
};
|
|
2096
|
+
if (catalogOperation)
|
|
2097
|
+
catalogOperationByEvidence.set(evidence, catalogOperation);
|
|
2098
|
+
return evidence;
|
|
2099
|
+
}
|
|
2100
|
+
if (preparation && intent.kind === 'configSave') {
|
|
2101
|
+
if (!sessionStatusResult || !catalogOperation) {
|
|
2102
|
+
throw operationReviewStale('当前 TUI session 没有可复用的设置证据。');
|
|
2103
|
+
}
|
|
2104
|
+
await preparation.reporter.emit('config');
|
|
2105
|
+
const configSnapshot = await readCatalogSourcesConfigSnapshot(context.env);
|
|
2106
|
+
signal?.throwIfAborted();
|
|
2107
|
+
if (configSnapshot.digest !== sessionStatusResult.payload.catalog.configDigest) {
|
|
2108
|
+
throw operationReviewStale('准备设置确认期间来源配置发生变化。');
|
|
2109
|
+
}
|
|
2110
|
+
const relevantTargets = configAffectedStatusTargets(sessionStatusResult.payload.targets, intent.pluginId, intent.scope);
|
|
2111
|
+
const preparedCatalogOperation = catalogOperation;
|
|
2112
|
+
const selected = await withResolvedCatalogPlugin({
|
|
2113
|
+
selector: intent.pluginId,
|
|
2114
|
+
cliVersion: packageInfo.version,
|
|
2115
|
+
configSnapshot,
|
|
2116
|
+
env: context.env,
|
|
2117
|
+
operationContext: preparedCatalogOperation,
|
|
2118
|
+
...(signal ? { signal } : {}),
|
|
2119
|
+
consume: async ({ plugin, source }) => {
|
|
2120
|
+
if (!plugin.manifest.config) {
|
|
2121
|
+
throw new TuiOperationBlockedError('configSchemaUnavailable', '这个插件没有设置 schema。');
|
|
2122
|
+
}
|
|
2123
|
+
if (intent.kind === 'configSave') {
|
|
2124
|
+
await coercePluginConfigUpdates({
|
|
2125
|
+
schema: plugin.manifest.config.fields,
|
|
2126
|
+
updates: intent.updates ?? {},
|
|
2127
|
+
cwd: context.cwd,
|
|
2128
|
+
});
|
|
2129
|
+
for (const key of intent.removeKeys ?? []) {
|
|
2130
|
+
if (!plugin.manifest.config.fields[key]) {
|
|
2131
|
+
throw new TuiOperationBlockedError('unknownConfigKey', '设置字段已变化,请重新检查。');
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
const impact = intent.retryApply ? undefined : await analyzePluginConfigMutationImpact({
|
|
2136
|
+
plugin,
|
|
2137
|
+
scope: intent.scope,
|
|
2138
|
+
cwd: context.cwd,
|
|
2139
|
+
env: context.env,
|
|
2140
|
+
visibleTargets: configImpactVisibleTargets(plugin.manifest.name, relevantTargets),
|
|
2141
|
+
mutation: {
|
|
2142
|
+
kind: 'patch',
|
|
2143
|
+
updates: intent.updates,
|
|
2144
|
+
removeKeys: intent.removeKeys,
|
|
2145
|
+
},
|
|
2146
|
+
});
|
|
2147
|
+
const affectedTargets = intent.retryApply
|
|
2148
|
+
? configImpactVisibleTargets(plugin.manifest.name, relevantTargets)
|
|
2149
|
+
: impact.affectedTargets;
|
|
2150
|
+
return {
|
|
2151
|
+
pluginName: plugin.manifest.name,
|
|
2152
|
+
source: sourceEvidenceForPlugin(source, plugin.manifest.name),
|
|
2153
|
+
impact,
|
|
2154
|
+
preApplyAuthorization: configIntentRequestsApply(intent)
|
|
2155
|
+
? (await resolveConfigPreApplyAuthorization({
|
|
2156
|
+
plugin,
|
|
2157
|
+
scope: intent.scope,
|
|
2158
|
+
cwd: context.cwd,
|
|
2159
|
+
env: context.env,
|
|
2160
|
+
applyTargets: affectedTargets,
|
|
2161
|
+
nativeCliRunner: context.nativeCliRunner,
|
|
2162
|
+
rebuildRuntime: {
|
|
2163
|
+
catalogOperationContext: preparedCatalogOperation,
|
|
2164
|
+
expectedGenerationSetDigest: preparedCatalogOperation.generationSetDigest,
|
|
2165
|
+
inventoryCatalogSourceReferences: operationInventory,
|
|
2166
|
+
},
|
|
2167
|
+
})).targets
|
|
2168
|
+
: [],
|
|
2169
|
+
sharedNativeRenderTargets: intent.retryApply
|
|
2170
|
+
? configRetrySharedNativeRenderTargets(plugin)
|
|
2171
|
+
: impact.sharedNativeRenderTargets,
|
|
2172
|
+
};
|
|
2173
|
+
},
|
|
2174
|
+
});
|
|
2175
|
+
const retryStore = intent.retryApply ? await readPluginConfigStore({
|
|
2176
|
+
sourceId: selected.source.sourceId,
|
|
2177
|
+
sourceIdentity: selected.source.sourceIdentity,
|
|
2178
|
+
logicalIdentity: selected.source.logicalIdentity,
|
|
2179
|
+
pluginName: selected.pluginName,
|
|
2180
|
+
scope: intent.scope,
|
|
2181
|
+
cwd: context.cwd,
|
|
2182
|
+
env: context.env,
|
|
2183
|
+
}) : undefined;
|
|
2184
|
+
signal?.throwIfAborted();
|
|
2185
|
+
const relevantRevisions = (sessionStatusResult.targetRevisions ?? []).filter((revision) => relevantTargets.some((target) => target.platform === revision.platform
|
|
2186
|
+
&& target.scope === revision.scope
|
|
2187
|
+
&& target.pluginId === revision.pluginId));
|
|
2188
|
+
await preparation.reporter.emit('evidence');
|
|
2189
|
+
const evidence = {
|
|
2190
|
+
catalog: {
|
|
2191
|
+
configDigest: sessionStatusResult.payload.catalog.configDigest,
|
|
2192
|
+
compositeDigest: sessionStatusResult.payload.catalog.compositeDigest,
|
|
2193
|
+
...(catalogOperation ? { generationSetDigest: catalogOperation.generationSetDigest } : {}),
|
|
2194
|
+
},
|
|
2195
|
+
...(selected.source ? { source: selected.source } : {}),
|
|
2196
|
+
targetFingerprint: statusTargetFingerprint(relevantTargets, relevantRevisions),
|
|
2197
|
+
config: {
|
|
2198
|
+
scope: intent.scope,
|
|
2199
|
+
...(selected.source?.manifestDigest ? { schemaIdentity: selected.source.manifestDigest } : {}),
|
|
2200
|
+
storeDigest: selected.impact?.storeDigest
|
|
2201
|
+
?? digest({ exists: retryStore?.exists, rawDigest: retryStore?.rawDigest }),
|
|
2202
|
+
impactState: selected.impact?.impactState ?? 'known',
|
|
2203
|
+
...(selected.impact?.noEffectiveChange ? { noEffectiveChange: true } : {}),
|
|
2204
|
+
affectedTargets: (selected.impact?.affectedTargets
|
|
2205
|
+
?? relevantTargets).map(({ platform, scope }) => ({ platform, scope })),
|
|
2206
|
+
...(selected.preApplyAuthorization.length > 0
|
|
2207
|
+
? { preApplyAuthorization: selected.preApplyAuthorization }
|
|
2208
|
+
: {}),
|
|
2209
|
+
...(selected.sharedNativeRenderTargets.length > 0
|
|
2210
|
+
? { sharedNativeRenderTargets: selected.sharedNativeRenderTargets }
|
|
2211
|
+
: {}),
|
|
2212
|
+
},
|
|
2213
|
+
};
|
|
2214
|
+
if (catalogOperation)
|
|
2215
|
+
catalogOperationByEvidence.set(evidence, catalogOperation);
|
|
2216
|
+
return evidence;
|
|
2217
|
+
}
|
|
2218
|
+
await preparation?.reporter.emit('status');
|
|
2219
|
+
const statusCatalogOperation = catalogOperation
|
|
2220
|
+
?? (!freshCatalog ? sessionCatalogOperationContext : undefined);
|
|
2221
|
+
const status = await statusCommandV8({
|
|
2222
|
+
plugins: [...new Set([intent.pluginId,
|
|
2223
|
+
...(intent.kind === 'pluginWrite' && intent.originIssue?.consumerPluginId
|
|
2224
|
+
? [intent.originIssue.consumerPluginId] : [])])],
|
|
2225
|
+
...('platform' in intent ? { platform: intent.platform } : {}),
|
|
2226
|
+
...(intent.kind !== 'pluginWrite' && intent.scope === 'project'
|
|
2227
|
+
? { scope: intent.scope }
|
|
2228
|
+
: {}),
|
|
2229
|
+
cwd: context.cwd,
|
|
2230
|
+
env: context.env,
|
|
2231
|
+
nativeCliRunner: context.nativeCliRunner,
|
|
2232
|
+
...(codexObservationSession ? { codexObservationSession } : {}),
|
|
2233
|
+
cliVersion: packageInfo.version,
|
|
2234
|
+
...(statusCatalogOperation ? { catalogOperationContext: statusCatalogOperation } : {}),
|
|
2235
|
+
inventoryCatalogSourceReferences: operationInventory,
|
|
2236
|
+
});
|
|
2237
|
+
signal?.throwIfAborted();
|
|
2238
|
+
const configSnapshot = await readCatalogSourcesConfigSnapshot(context.env);
|
|
2239
|
+
signal?.throwIfAborted();
|
|
2240
|
+
if (configSnapshot.digest !== status.payload.catalog.configDigest) {
|
|
2241
|
+
throw operationReviewStale('准备确认信息期间来源配置发生变化。');
|
|
2242
|
+
}
|
|
2243
|
+
if (catalogOperationError)
|
|
2244
|
+
throw catalogOperationError;
|
|
2245
|
+
if (!catalogOperation) {
|
|
2246
|
+
catalogOperation = await resolveCatalogOperationContext({
|
|
2247
|
+
sourceIds: [parsePluginId(intent.pluginId).sourceId],
|
|
2248
|
+
configSnapshot,
|
|
2249
|
+
env: context.env,
|
|
2250
|
+
...(!freshCatalog ? { validatedStoreEntries, validatingStoreEntries } : {}),
|
|
2251
|
+
...(signal ? { signal } : {}),
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2254
|
+
let dependencyPlan;
|
|
2255
|
+
let codexReconcilePlans;
|
|
2256
|
+
let operationPlan;
|
|
2257
|
+
await preparation?.reporter.emit('catalog');
|
|
2258
|
+
const configVisibleTargets = intent.kind === 'configSave'
|
|
2259
|
+
? configAffectedStatusTargets(status.payload.targets, intent.pluginId, intent.scope)
|
|
2260
|
+
: [];
|
|
2261
|
+
const selected = await withResolvedCatalogPlugin({
|
|
2262
|
+
selector: intent.pluginId,
|
|
2263
|
+
cliVersion: packageInfo.version,
|
|
2264
|
+
configSnapshot,
|
|
2265
|
+
env: context.env,
|
|
2266
|
+
...(catalogOperation ? { operationContext: catalogOperation } : {}),
|
|
2267
|
+
...(signal ? { signal } : {}),
|
|
2268
|
+
consume: async ({ plugin, source }) => {
|
|
2269
|
+
if (preparation?.validateIntent && intent.kind === 'configSave') {
|
|
2270
|
+
await preparation.reporter.emit('config');
|
|
2271
|
+
if (!plugin.manifest.config) {
|
|
2272
|
+
throw new TuiOperationBlockedError('configSchemaUnavailable', '这个插件没有设置 schema。');
|
|
2273
|
+
}
|
|
2274
|
+
await coercePluginConfigUpdates({
|
|
2275
|
+
schema: plugin.manifest.config.fields,
|
|
2276
|
+
updates: intent.updates ?? {},
|
|
2277
|
+
cwd: context.cwd,
|
|
2278
|
+
});
|
|
2279
|
+
for (const key of intent.removeKeys ?? []) {
|
|
2280
|
+
if (!plugin.manifest.config.fields[key]) {
|
|
2281
|
+
throw new TuiOperationBlockedError('unknownConfigKey', '设置字段已变化,请重新检查。');
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
const impact = intent.kind === 'configSave' && !intent.retryApply
|
|
2286
|
+
? await analyzePluginConfigMutationImpact({
|
|
2287
|
+
plugin,
|
|
2288
|
+
scope: intent.scope,
|
|
2289
|
+
cwd: context.cwd,
|
|
2290
|
+
env: context.env,
|
|
2291
|
+
visibleTargets: configImpactVisibleTargets(plugin.manifest.name, configVisibleTargets),
|
|
2292
|
+
mutation: {
|
|
2293
|
+
kind: 'patch',
|
|
2294
|
+
updates: intent.updates,
|
|
2295
|
+
removeKeys: intent.removeKeys,
|
|
2296
|
+
},
|
|
2297
|
+
})
|
|
2298
|
+
: undefined;
|
|
2299
|
+
return {
|
|
2300
|
+
pluginName: plugin.manifest.name,
|
|
2301
|
+
source: sourceEvidenceForPlugin(source, plugin.manifest.name),
|
|
2302
|
+
impact,
|
|
2303
|
+
preApplyAuthorization: intent.kind === 'configSave'
|
|
2304
|
+
&& configIntentRequestsApply(intent)
|
|
2305
|
+
? (await resolveConfigPreApplyAuthorization({
|
|
2306
|
+
plugin,
|
|
2307
|
+
scope: intent.scope,
|
|
2308
|
+
cwd: context.cwd,
|
|
2309
|
+
env: context.env,
|
|
2310
|
+
applyTargets: intent.retryApply
|
|
2311
|
+
? configImpactVisibleTargets(plugin.manifest.name, configVisibleTargets)
|
|
2312
|
+
: impact.affectedTargets,
|
|
2313
|
+
nativeCliRunner: context.nativeCliRunner,
|
|
2314
|
+
rebuildRuntime: {
|
|
2315
|
+
...(catalogOperation ? { catalogOperationContext: catalogOperation } : {}),
|
|
2316
|
+
...(catalogOperation?.generationSetDigest
|
|
2317
|
+
? { expectedGenerationSetDigest: catalogOperation.generationSetDigest }
|
|
2318
|
+
: {}),
|
|
2319
|
+
inventoryCatalogSourceReferences: operationInventory,
|
|
2320
|
+
},
|
|
2321
|
+
})).targets
|
|
2322
|
+
: [],
|
|
2323
|
+
sharedNativeRenderTargets: intent.kind === 'configSave'
|
|
2324
|
+
? intent.retryApply
|
|
2325
|
+
? configRetrySharedNativeRenderTargets(plugin)
|
|
2326
|
+
: impact.sharedNativeRenderTargets
|
|
2327
|
+
: [],
|
|
2328
|
+
};
|
|
2329
|
+
},
|
|
2330
|
+
});
|
|
2331
|
+
if (intent.kind === 'pluginWrite' && intent.expectedUpdate) {
|
|
2332
|
+
const revision = status.targetRevisions.find((entry) => entry.pluginId === intent.pluginId && entry.platform === intent.platform
|
|
2333
|
+
&& entry.scope === intent.scope);
|
|
2334
|
+
const generation = catalogOperation?.generationSet.sources.find((entry) => entry.sourceId === selected.source.sourceId);
|
|
2335
|
+
if (intent.action !== 'sync'
|
|
2336
|
+
|| !('pluginVersion' in selected.source)
|
|
2337
|
+
|| selected.source.sourceIdentity !== intent.expectedUpdate.sourceIdentity
|
|
2338
|
+
|| selected.source.pluginVersion !== intent.expectedUpdate.activeVersion
|
|
2339
|
+
|| generation?.storeKey !== intent.expectedUpdate.generationKey
|
|
2340
|
+
|| revision?.revision.installedVersion !== intent.expectedUpdate.installedVersion
|
|
2341
|
+
|| revision?.revision.availableVersion !== intent.expectedUpdate.activeVersion
|
|
2342
|
+
|| revision.revision.state !== 'upgradeAvailable') {
|
|
2343
|
+
throw operationReviewStale('插件更新版本或 Active Catalog 证据已变化。');
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
let codexManager;
|
|
2347
|
+
let resolvedCodexManager;
|
|
2348
|
+
const hasDeclaredDependencies = (status.pluginSummaries.find((summary) => summary.pluginId === intent.pluginId)?.dependencyTotalCount ?? 0) > 0;
|
|
2349
|
+
if (intent.kind === 'pluginWrite') {
|
|
2350
|
+
const command = {
|
|
2351
|
+
install: installWriteCommand,
|
|
2352
|
+
refreshAndSync: syncWriteCommand,
|
|
2353
|
+
reinstall: reinstallWriteCommand,
|
|
2354
|
+
uninstall: uninstallWriteCommand,
|
|
2355
|
+
cleanup: cleanupWriteCommand,
|
|
2356
|
+
sync: syncWriteCommand,
|
|
2357
|
+
}[intent.action];
|
|
2358
|
+
const preview = await command([intent.pluginId], {
|
|
2359
|
+
cwd: context.cwd,
|
|
2360
|
+
platform: intent.platform,
|
|
2361
|
+
scope: intent.scope,
|
|
2362
|
+
...(reviewedScriptTimeoutMs(intent) === undefined
|
|
2363
|
+
? {}
|
|
2364
|
+
: { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
|
|
2365
|
+
...(intent.action === 'install' && intent.dependencyScopeOverrides
|
|
2366
|
+
? { dependencyScopeOverrides: intent.dependencyScopeOverrides }
|
|
2367
|
+
: {}),
|
|
2368
|
+
...(intent.action === 'cleanup' ? { force: intent.force === true } : {}),
|
|
2369
|
+
}, {
|
|
2370
|
+
...context,
|
|
2371
|
+
inventoryCatalogSourceReferences: operationInventory,
|
|
2372
|
+
expectedCatalogConfigDigest: status.payload.catalog.configDigest,
|
|
2373
|
+
...(catalogOperation ? {
|
|
2374
|
+
catalogOperationContext: catalogOperation,
|
|
2375
|
+
expectedGenerationSetDigest: catalogOperation.generationSetDigest,
|
|
2376
|
+
} : {}),
|
|
2377
|
+
previewOperationPlan: true,
|
|
2378
|
+
...(codexObservationSession ? { codexObservationSession } : {}),
|
|
2379
|
+
...(preparation ? {
|
|
2380
|
+
onPreviewStage: (stage) => stage === 'catalog' ? undefined : preparation.reporter.emit(stage),
|
|
2381
|
+
} : {}),
|
|
2382
|
+
});
|
|
2383
|
+
signal?.throwIfAborted();
|
|
2384
|
+
codexReconcilePlans = preview?.codexReconcilePlans;
|
|
2385
|
+
operationPlan = preview?.operationPlan;
|
|
2386
|
+
if (hasDeclaredDependencies
|
|
2387
|
+
&& (intent.action === 'install'
|
|
2388
|
+
|| intent.action === 'refreshAndSync'
|
|
2389
|
+
|| intent.action === 'reinstall'
|
|
2390
|
+
|| intent.action === 'sync')) {
|
|
2391
|
+
dependencyPlan = preview.dependencyPlan;
|
|
2392
|
+
if (!dependencyPlan) {
|
|
2393
|
+
throw new TuiOperationBlockedError('dependencyGraphInvalid', '无法建立可信的依赖安装计划。');
|
|
2394
|
+
}
|
|
2395
|
+
const blocker = dependencyPlan.targets.find((target) => target.action === 'block');
|
|
2396
|
+
if (blocker) {
|
|
2397
|
+
const code = blocker.errorCode ?? 'dependencyNotReady';
|
|
2398
|
+
const reason = code === 'dependencyScopeConflict' || code === 'codexExtensionScopeConflict'
|
|
2399
|
+
? '这个依赖的安装位置存在冲突,需要先选择保留的位置。'
|
|
2400
|
+
: code === 'artifactCollision' || code === 'exclusiveOwnerEvidenceInvalid'
|
|
2401
|
+
? '这个依赖的安装位置被其他内容占用,需要先检查并处理冲突。'
|
|
2402
|
+
: '这个依赖尚不可用,需要先检查并恢复后再安装。';
|
|
2403
|
+
throw new TuiOperationBlockedError(code, `${blocker.pluginId}:${reason}`, [{
|
|
2404
|
+
id: `dependency:${blocker.pluginId}:${blocker.platform}:${blocker.resolvedScope}`,
|
|
2405
|
+
code, stage: 'dependencyPlan', safeMessage: reason,
|
|
2406
|
+
target: { pluginId: blocker.pluginId, platform: blocker.platform, scope: blocker.resolvedScope },
|
|
2407
|
+
causes: [], detailLines: [],
|
|
2408
|
+
}]);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
const managerPreview = preview?.codexManagerPreview;
|
|
2412
|
+
if (managerPreview?.state === 'blocked') {
|
|
2413
|
+
const message = managerPreview.code === 'codexSystemCliUnsupported'
|
|
2414
|
+
? '系统 Codex CLI 版本不受支持。请升级官方 Codex CLI 后重试。'
|
|
2415
|
+
: 'AW-managed Codex runtime 当前不可用。请重试、升级 AW,或安装官方 Codex CLI。';
|
|
2416
|
+
throw new TuiOperationBlockedError(managerPreview.code, `${managerPreview.code}: ${message}`);
|
|
2417
|
+
}
|
|
2418
|
+
if (managerPreview) {
|
|
2419
|
+
if (managerPreview.state === 'system' || managerPreview.state === 'cached') {
|
|
2420
|
+
resolvedCodexManager = managerPreview.manager;
|
|
2421
|
+
}
|
|
2422
|
+
codexManager = {
|
|
2423
|
+
state: managerPreview.state,
|
|
2424
|
+
packageName: MANAGED_CODEX_PACKAGE_NAME,
|
|
2425
|
+
version: managerPreview.state === 'requires-install'
|
|
2426
|
+
? MANAGED_CODEX_VERSION
|
|
2427
|
+
: managerPreview.manager.version,
|
|
2428
|
+
};
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
await preparation?.reporter.emit('evidence');
|
|
2432
|
+
signal?.throwIfAborted();
|
|
2433
|
+
const evidenceTargets = intent.kind === 'configSave'
|
|
2434
|
+
? configVisibleTargets
|
|
2435
|
+
: flattenStatusTargets(status.payload.targets);
|
|
2436
|
+
const evidenceRevisions = intent.kind === 'configSave'
|
|
2437
|
+
? (status.targetRevisions ?? []).filter((revision) => evidenceTargets.some((target) => target.platform === revision.platform
|
|
2438
|
+
&& target.scope === revision.scope
|
|
2439
|
+
&& target.pluginId === revision.pluginId))
|
|
2440
|
+
: (status.targetRevisions ?? []);
|
|
2441
|
+
const evidence = {
|
|
2442
|
+
catalog: {
|
|
2443
|
+
configDigest: status.payload.catalog.configDigest,
|
|
2444
|
+
compositeDigest: status.payload.catalog.compositeDigest,
|
|
2445
|
+
...(catalogOperation
|
|
2446
|
+
? { generationSetDigest: catalogOperation.generationSetDigest }
|
|
2447
|
+
: {}),
|
|
2448
|
+
},
|
|
2449
|
+
source: selected.source,
|
|
2450
|
+
...(dependencyPlan ? { dependencyPlan } : {}),
|
|
2451
|
+
...(codexReconcilePlans ? { codexReconcilePlans } : {}),
|
|
2452
|
+
...(operationPlan ? { operationPlan } : {}),
|
|
2453
|
+
...(codexManager ? { codexManager } : {}),
|
|
2454
|
+
targetFingerprint: statusTargetFingerprint(evidenceTargets, evidenceRevisions),
|
|
2455
|
+
};
|
|
2456
|
+
if (intent.kind === 'configSave') {
|
|
2457
|
+
const impact = 'impact' in selected ? selected.impact : undefined;
|
|
2458
|
+
const retryStore = intent.retryApply ? await readPluginConfigStore({
|
|
2459
|
+
sourceId: selected.source.sourceId,
|
|
2460
|
+
sourceIdentity: selected.source.sourceIdentity,
|
|
2461
|
+
...('logicalIdentity' in selected.source
|
|
2462
|
+
? { logicalIdentity: selected.source.logicalIdentity }
|
|
2463
|
+
: {}),
|
|
2464
|
+
pluginName: selected.pluginName,
|
|
2465
|
+
scope: intent.scope,
|
|
2466
|
+
cwd: context.cwd,
|
|
2467
|
+
env: context.env,
|
|
2468
|
+
}) : undefined;
|
|
2469
|
+
signal?.throwIfAborted();
|
|
2470
|
+
evidence.config = {
|
|
2471
|
+
scope: intent.scope,
|
|
2472
|
+
...('manifestDigest' in selected.source
|
|
2473
|
+
? { schemaIdentity: selected.source.manifestDigest }
|
|
2474
|
+
: {}),
|
|
2475
|
+
storeDigest: impact?.storeDigest
|
|
2476
|
+
?? digest({ exists: retryStore?.exists, rawDigest: retryStore?.rawDigest }),
|
|
2477
|
+
impactState: impact?.impactState ?? 'known',
|
|
2478
|
+
...(impact?.noEffectiveChange ? { noEffectiveChange: true } : {}),
|
|
2479
|
+
affectedTargets: (impact?.affectedTargets
|
|
2480
|
+
?? evidenceTargets).map(({ platform, scope }) => ({ platform, scope })),
|
|
2481
|
+
...(intent.scope === 'project' ? {
|
|
2482
|
+
shareability: await inspectConfigShareability({
|
|
2483
|
+
sourceId: selected.source.sourceId,
|
|
2484
|
+
pluginName: selected.pluginName,
|
|
2485
|
+
scope: intent.scope,
|
|
2486
|
+
cwd: context.cwd,
|
|
2487
|
+
env: context.env,
|
|
2488
|
+
}),
|
|
2489
|
+
} : {}),
|
|
2490
|
+
...('sharedNativeRenderTargets' in selected
|
|
2491
|
+
&& selected.sharedNativeRenderTargets.length > 0
|
|
2492
|
+
? { sharedNativeRenderTargets: selected.sharedNativeRenderTargets }
|
|
2493
|
+
: {}),
|
|
2494
|
+
...('preApplyAuthorization' in selected
|
|
2495
|
+
&& selected.preApplyAuthorization.length > 0
|
|
2496
|
+
? { preApplyAuthorization: selected.preApplyAuthorization }
|
|
2497
|
+
: {}),
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
if (catalogOperation)
|
|
2501
|
+
catalogOperationByEvidence.set(evidence, catalogOperation);
|
|
2502
|
+
if (resolvedCodexManager)
|
|
2503
|
+
codexManagerByEvidence.set(evidence, resolvedCodexManager);
|
|
2504
|
+
return evidence;
|
|
2505
|
+
};
|
|
2506
|
+
const dependencies = {
|
|
2507
|
+
beginCatalogSession(sessionOptions) {
|
|
2508
|
+
sessionCatalogOperationContext = undefined;
|
|
2509
|
+
if (sessionOptions?.reuseValidatedGenerations !== true) {
|
|
2510
|
+
validatedStoreEntries.clear();
|
|
2511
|
+
validatingStoreEntries.clear();
|
|
2512
|
+
}
|
|
2513
|
+
},
|
|
2514
|
+
loadStatus,
|
|
2515
|
+
loadPluginContent,
|
|
2516
|
+
async probeCatalogFreshness(sourceId, freshnessOptions, signal) {
|
|
2517
|
+
signal.throwIfAborted();
|
|
2518
|
+
const packageInfo = await readCliPackageInfo();
|
|
2519
|
+
return probeCatalogSourceFreshness({
|
|
2520
|
+
sourceId,
|
|
2521
|
+
cliVersion: packageInfo.version,
|
|
2522
|
+
env: context.env,
|
|
2523
|
+
signal,
|
|
2524
|
+
fullValidation: freshnessOptions.fullValidation,
|
|
2525
|
+
runtime: {
|
|
2526
|
+
...options.catalogGenerationRuntime,
|
|
2527
|
+
validatedStoreEntries,
|
|
2528
|
+
validatingStoreEntries,
|
|
2529
|
+
},
|
|
2530
|
+
});
|
|
2531
|
+
},
|
|
2532
|
+
async inspectSourceRemoval(sourceId, signal) {
|
|
2533
|
+
signal.throwIfAborted();
|
|
2534
|
+
const config = await readCatalogSourcesConfigSnapshot(context.env);
|
|
2535
|
+
const source = config.config.sources.find((entry) => entry.id === sourceId);
|
|
2536
|
+
if (!source)
|
|
2537
|
+
throw new TuiOperationBlockedError('catalogGenerationSourceMissing', '来源不存在。');
|
|
2538
|
+
const impact = await inspectCatalogSourceImpact({
|
|
2539
|
+
sourceId,
|
|
2540
|
+
cwd: context.cwd,
|
|
2541
|
+
env: context.env,
|
|
2542
|
+
sourcePrimary: source.primary,
|
|
2543
|
+
mode: 'remove',
|
|
2544
|
+
});
|
|
2545
|
+
const counts = (category) => impact.affectedReferences.filter((reference) => reference.category === category).length;
|
|
2546
|
+
return {
|
|
2547
|
+
blocked: impact.affectedReferences.length > 0 || !impact.inventoryComplete,
|
|
2548
|
+
inventoryComplete: impact.inventoryComplete,
|
|
2549
|
+
installationCount: counts('installation'),
|
|
2550
|
+
configCount: counts('config'),
|
|
2551
|
+
lifecycleCount: counts('lifecycle'),
|
|
2552
|
+
marketplaceCount: counts('marketplace'),
|
|
2553
|
+
dependencies: impact.affectedReferences,
|
|
2554
|
+
};
|
|
2555
|
+
},
|
|
2556
|
+
async prepareReviewEvidence(intent, signal, onStage, reusableCandidate) {
|
|
2557
|
+
const reporter = createReviewStageReporter({
|
|
2558
|
+
context,
|
|
2559
|
+
onStage,
|
|
2560
|
+
now: options.now ?? Date.now,
|
|
2561
|
+
});
|
|
2562
|
+
try {
|
|
2563
|
+
const preparationWarnings = [];
|
|
2564
|
+
if (intent.kind === 'pluginWrite' && intent.action === 'refreshAndSync') {
|
|
2565
|
+
await reporter.emit('refresh');
|
|
2566
|
+
const packageInfo = await readCliPackageInfo();
|
|
2567
|
+
const sourceId = parsePluginId(intent.pluginId).sourceId;
|
|
2568
|
+
await syncCatalogSourceGeneration({
|
|
2569
|
+
sourceId,
|
|
2570
|
+
cliVersion: packageInfo.version,
|
|
2571
|
+
cwd: context.cwd,
|
|
2572
|
+
env: context.env,
|
|
2573
|
+
signal,
|
|
2574
|
+
}).catch((error) => {
|
|
2575
|
+
preparationWarnings.push(`catalogSourceRefreshFailed: ${sourceId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2576
|
+
});
|
|
2577
|
+
sessionCatalogOperationContext = undefined;
|
|
2578
|
+
sessionStatusResult = undefined;
|
|
2579
|
+
validatedStoreEntries.clear();
|
|
2580
|
+
validatingStoreEntries.clear();
|
|
2581
|
+
}
|
|
2582
|
+
const taskObservationSession = intent.kind === 'pluginWriteTask'
|
|
2583
|
+
&& intent.targets.some((target) => target.platform === 'codex')
|
|
2584
|
+
? await createCodexNativeObservationSession({
|
|
2585
|
+
exactCwd: context.cwd,
|
|
2586
|
+
env: context.env,
|
|
2587
|
+
runner: context.nativeCliRunner,
|
|
2588
|
+
})
|
|
2589
|
+
: undefined;
|
|
2590
|
+
let evidence;
|
|
2591
|
+
try {
|
|
2592
|
+
evidence = await captureEvidence(intent, signal, {
|
|
2593
|
+
reporter,
|
|
2594
|
+
validateIntent: true,
|
|
2595
|
+
}, createOperationInventoryReader(), intent.kind === 'pluginWrite'
|
|
2596
|
+
&& intent.action === 'refreshAndSync', reusableCandidate, taskObservationSession);
|
|
2597
|
+
}
|
|
2598
|
+
finally {
|
|
2599
|
+
await taskObservationSession?.dispose();
|
|
2600
|
+
}
|
|
2601
|
+
if (preparationWarnings.length > 0)
|
|
2602
|
+
evidence.preparationWarnings = preparationWarnings;
|
|
2603
|
+
return evidence;
|
|
2604
|
+
}
|
|
2605
|
+
finally {
|
|
2606
|
+
await reporter.finish();
|
|
2607
|
+
}
|
|
2608
|
+
},
|
|
2609
|
+
async previewDependencyPlacement(intent, signal) {
|
|
2610
|
+
const evidence = await captureEvidence(intent, signal);
|
|
2611
|
+
return evidence.dependencyPlan;
|
|
2612
|
+
},
|
|
2613
|
+
async validateIntent(intent, signal) {
|
|
2614
|
+
if (intent.kind === 'catalogSourcesRecovery') {
|
|
2615
|
+
signal.throwIfAborted();
|
|
2616
|
+
return;
|
|
2617
|
+
}
|
|
2618
|
+
if (intent.kind === 'catalogGeneration') {
|
|
2619
|
+
signal.throwIfAborted();
|
|
2620
|
+
return;
|
|
2621
|
+
}
|
|
2622
|
+
if (intent.kind === 'sourceMutation') {
|
|
2623
|
+
const evidence = await captureEvidence(intent, signal);
|
|
2624
|
+
if (evidence.sourceWrite?.configDigest === evidence.sourceWrite?.proposedConfigDigest) {
|
|
2625
|
+
throw new TuiOperationBlockedError('sourceNoChanges', '来源没有变化。');
|
|
2626
|
+
}
|
|
2627
|
+
return;
|
|
2628
|
+
}
|
|
2629
|
+
if (intent.kind === 'configSaveBatch') {
|
|
2630
|
+
assertConfigSaveBatchIntent(intent);
|
|
2631
|
+
for (const group of intent.groups) {
|
|
2632
|
+
await dependencies.validateIntent(configSaveBatchGroupIntent(intent, group), signal);
|
|
2633
|
+
}
|
|
2634
|
+
return;
|
|
2635
|
+
}
|
|
2636
|
+
if (intent.kind === 'configSave') {
|
|
2637
|
+
const packageInfo = await readCliPackageInfo();
|
|
2638
|
+
await withResolvedCatalogPlugin({
|
|
2639
|
+
selector: intent.pluginId,
|
|
2640
|
+
cliVersion: packageInfo.version,
|
|
2641
|
+
env: context.env,
|
|
2642
|
+
signal,
|
|
2643
|
+
...(sessionContextForPlugin(intent.pluginId)
|
|
2644
|
+
? { operationContext: sessionContextForPlugin(intent.pluginId) }
|
|
2645
|
+
: {}),
|
|
601
2646
|
consume: async ({ plugin }) => {
|
|
602
2647
|
if (!plugin.manifest.config) {
|
|
603
2648
|
throw new TuiOperationBlockedError('configSchemaUnavailable', '这个插件没有设置 schema。');
|
|
604
2649
|
}
|
|
605
2650
|
await coercePluginConfigUpdates({
|
|
606
|
-
schema: plugin.manifest.config.
|
|
2651
|
+
schema: plugin.manifest.config.fields,
|
|
607
2652
|
updates: intent.updates ?? {},
|
|
608
2653
|
cwd: context.cwd,
|
|
609
2654
|
});
|
|
610
2655
|
for (const key of intent.removeKeys ?? []) {
|
|
611
|
-
if (!plugin.manifest.config.
|
|
2656
|
+
if (!plugin.manifest.config.fields[key]) {
|
|
612
2657
|
throw new TuiOperationBlockedError('unknownConfigKey', '设置字段已变化,请重新检查。');
|
|
613
2658
|
}
|
|
614
2659
|
}
|
|
@@ -619,41 +2664,24 @@ export function createTuiControllerDependencies(context, progressFeed) {
|
|
|
619
2664
|
if (intent.kind !== 'pluginWrite')
|
|
620
2665
|
return;
|
|
621
2666
|
signal.throwIfAborted();
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
cliVersion: packageInfo.version,
|
|
630
|
-
});
|
|
2667
|
+
const parsed = parsePluginId(intent.pluginId);
|
|
2668
|
+
if (!parsed.sourceId || !parsed.pluginName) {
|
|
2669
|
+
throw new TuiOperationBlockedError('invalidPluginId', '操作必须使用 canonical pluginId。');
|
|
2670
|
+
}
|
|
2671
|
+
},
|
|
2672
|
+
captureEvidence,
|
|
2673
|
+
inspectCatalogStore(signal) {
|
|
631
2674
|
signal.throwIfAborted();
|
|
632
|
-
|
|
633
|
-
assertIntentIsLegal(intent, status.payload.targets);
|
|
2675
|
+
return inspectCatalogStoreTask({ env: context.env });
|
|
634
2676
|
},
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
cliVersion: packageInfo.version,
|
|
640
|
-
freshnessPolicy: 'forceRemote',
|
|
641
|
-
sourceIds: [sourceId],
|
|
642
|
-
configSnapshot,
|
|
2677
|
+
recheckPortableInstall(receipt, signal) {
|
|
2678
|
+
return recheckPortableInstall({
|
|
2679
|
+
receipt,
|
|
2680
|
+
cwd: context.cwd,
|
|
643
2681
|
env: context.env,
|
|
644
2682
|
signal,
|
|
645
|
-
consume: (catalog) => {
|
|
646
|
-
const item = catalog.sources[0];
|
|
647
|
-
if (item.error)
|
|
648
|
-
throw item.error;
|
|
649
|
-
return {
|
|
650
|
-
sourceIdentity: item.resolution.sourceIdentity,
|
|
651
|
-
requestedRef: item.resolution.requestedRef,
|
|
652
|
-
};
|
|
653
|
-
},
|
|
654
2683
|
});
|
|
655
2684
|
},
|
|
656
|
-
captureEvidence,
|
|
657
2685
|
logError(_error, code, logContext) {
|
|
658
2686
|
return context.debugLogger.log({
|
|
659
2687
|
type: 'tui:controller-error',
|
|
@@ -670,9 +2698,11 @@ export function createTuiControllerDependencies(context, progressFeed) {
|
|
|
670
2698
|
return withResolvedCatalogPlugin({
|
|
671
2699
|
selector: pluginId,
|
|
672
2700
|
cliVersion: packageInfo.version,
|
|
673
|
-
freshnessPolicy: 'cacheFirst',
|
|
674
2701
|
env: context.env,
|
|
675
2702
|
signal,
|
|
2703
|
+
...(sessionContextForPlugin(pluginId)
|
|
2704
|
+
? { operationContext: sessionContextForPlugin(pluginId) }
|
|
2705
|
+
: {}),
|
|
676
2706
|
consume: async ({ plugin }) => {
|
|
677
2707
|
if (!plugin.manifest.config) {
|
|
678
2708
|
throw new TuiOperationBlockedError('configSchemaUnavailable', '这个插件没有设置 schema。');
|
|
@@ -681,11 +2711,17 @@ export function createTuiControllerDependencies(context, progressFeed) {
|
|
|
681
2711
|
pluginName: plugin.manifest.name,
|
|
682
2712
|
plugin,
|
|
683
2713
|
scope: layer.scope,
|
|
684
|
-
projectLayer: layer.projectLayer,
|
|
685
2714
|
cwd: context.cwd,
|
|
686
2715
|
env: context.env,
|
|
687
2716
|
});
|
|
688
|
-
|
|
2717
|
+
const effectiveRead = await resolveConfigRead({
|
|
2718
|
+
pluginName: plugin.manifest.name,
|
|
2719
|
+
plugin,
|
|
2720
|
+
scope: 'effective',
|
|
2721
|
+
cwd: context.cwd,
|
|
2722
|
+
env: context.env,
|
|
2723
|
+
});
|
|
2724
|
+
return buildConfigGroups({ schema: plugin.manifest.config, read, effectiveRead });
|
|
689
2725
|
},
|
|
690
2726
|
});
|
|
691
2727
|
},
|
|
@@ -694,71 +2730,355 @@ export function createTuiControllerDependencies(context, progressFeed) {
|
|
|
694
2730
|
return withResolvedCatalogPlugin({
|
|
695
2731
|
selector: pluginId,
|
|
696
2732
|
cliVersion: packageInfo.version,
|
|
697
|
-
freshnessPolicy: 'cacheFirst',
|
|
698
2733
|
env: context.env,
|
|
699
2734
|
signal,
|
|
2735
|
+
...(sessionContextForPlugin(pluginId)
|
|
2736
|
+
? { operationContext: sessionContextForPlugin(pluginId) }
|
|
2737
|
+
: {}),
|
|
700
2738
|
consume: async ({ plugin }) => {
|
|
701
2739
|
if (!plugin.manifest.config) {
|
|
702
2740
|
throw new TuiOperationBlockedError('configSchemaUnavailable', '这个插件没有设置 schema。');
|
|
703
2741
|
}
|
|
2742
|
+
const stores = await readConfigStoresForTui({
|
|
2743
|
+
sourceId: plugin.sourceId,
|
|
2744
|
+
sourceIdentity: plugin.sourceIdentity,
|
|
2745
|
+
logicalIdentity: plugin.catalogLogicalIdentity,
|
|
2746
|
+
pluginName: plugin.manifest.name,
|
|
2747
|
+
schema: plugin.manifest.config.fields,
|
|
2748
|
+
scope: layer.scope,
|
|
2749
|
+
signal,
|
|
2750
|
+
});
|
|
2751
|
+
const userStore = stores.user;
|
|
2752
|
+
const projectStore = stores.project;
|
|
704
2753
|
const read = await resolveConfigRead({
|
|
705
2754
|
pluginName: plugin.manifest.name,
|
|
706
2755
|
plugin,
|
|
707
2756
|
scope: layer.scope,
|
|
708
|
-
projectLayer: layer.projectLayer,
|
|
709
2757
|
cwd: context.cwd,
|
|
710
2758
|
env: context.env,
|
|
711
2759
|
});
|
|
712
|
-
|
|
2760
|
+
const effectiveRead = layer.scope === 'effective' ? read : await resolveConfigRead({
|
|
2761
|
+
pluginName: plugin.manifest.name,
|
|
2762
|
+
plugin,
|
|
2763
|
+
scope: 'effective',
|
|
2764
|
+
cwd: context.cwd,
|
|
2765
|
+
env: context.env,
|
|
2766
|
+
});
|
|
2767
|
+
const layerReads = layer.scope === 'effective' ? undefined : {
|
|
2768
|
+
user: layer.scope === 'user' ? read : await resolveConfigRead({
|
|
2769
|
+
pluginName: plugin.manifest.name,
|
|
2770
|
+
plugin,
|
|
2771
|
+
scope: 'user',
|
|
2772
|
+
cwd: context.cwd,
|
|
2773
|
+
env: context.env,
|
|
2774
|
+
}),
|
|
2775
|
+
...(layer.scope === 'user' ? {} : { project: read }),
|
|
2776
|
+
};
|
|
2777
|
+
const effectiveRaw = await resolveEffectivePluginConfig({
|
|
2778
|
+
sourceId: plugin.sourceId,
|
|
2779
|
+
sourceIdentity: plugin.sourceIdentity,
|
|
2780
|
+
logicalIdentity: plugin.catalogLogicalIdentity,
|
|
2781
|
+
pluginName: plugin.manifest.name,
|
|
2782
|
+
manifest: plugin.manifest,
|
|
2783
|
+
targetScope: layer.scope === 'user' ? 'user' : 'project',
|
|
2784
|
+
cwd: context.cwd,
|
|
2785
|
+
env: context.env,
|
|
2786
|
+
});
|
|
2787
|
+
const layerStore = layer.scope === 'effective'
|
|
2788
|
+
? undefined
|
|
2789
|
+
: layer.scope === 'user' ? userStore : projectStore;
|
|
2790
|
+
return buildConfigPage({
|
|
2791
|
+
pluginId,
|
|
2792
|
+
schema: plugin.manifest.config,
|
|
2793
|
+
read,
|
|
2794
|
+
effectiveRead,
|
|
2795
|
+
...(layerReads ? { layerReads } : {}),
|
|
2796
|
+
...(layerStore ? { rawValues: layerStore.values } : {}),
|
|
2797
|
+
rawLayerValues: {
|
|
2798
|
+
user: userStore.values,
|
|
2799
|
+
...(projectStore ? { project: projectStore.values } : {}),
|
|
2800
|
+
},
|
|
2801
|
+
effectiveRawValues: effectiveRaw.values,
|
|
2802
|
+
sharedNativeRefreshKeys: new Set((plugin.manifest.artifacts?.targets ?? []).flatMap((target) => target.target === 'codex.skill' && target.delivery.mode === 'codex-native'
|
|
2803
|
+
? target.refreshKeys
|
|
2804
|
+
: [])),
|
|
2805
|
+
});
|
|
713
2806
|
},
|
|
714
2807
|
});
|
|
715
2808
|
},
|
|
716
2809
|
async getConfigResetAvailability(pluginId, layer, signal) {
|
|
2810
|
+
const parsed = parsePluginId(pluginId);
|
|
2811
|
+
if (!parsed.sourceId || !parsed.pluginName) {
|
|
2812
|
+
throw new TuiOperationBlockedError('invalidPluginId', '设置目标必须使用 canonical pluginId。');
|
|
2813
|
+
}
|
|
2814
|
+
let sourceIdentity;
|
|
2815
|
+
let logicalIdentity;
|
|
2816
|
+
try {
|
|
2817
|
+
const packageInfo = await readCliPackageInfo();
|
|
2818
|
+
const source = await withResolvedCatalogPlugin({
|
|
2819
|
+
selector: pluginId,
|
|
2820
|
+
cliVersion: packageInfo.version,
|
|
2821
|
+
env: context.env,
|
|
2822
|
+
signal,
|
|
2823
|
+
...(sessionContextForPlugin(pluginId)
|
|
2824
|
+
? { operationContext: sessionContextForPlugin(pluginId) }
|
|
2825
|
+
: {}),
|
|
2826
|
+
consume: async ({ source }) => ({
|
|
2827
|
+
sourceIdentity: source.sourceIdentity,
|
|
2828
|
+
logicalIdentity: source.logicalIdentity,
|
|
2829
|
+
}),
|
|
2830
|
+
});
|
|
2831
|
+
sourceIdentity = source.sourceIdentity;
|
|
2832
|
+
logicalIdentity = source.logicalIdentity;
|
|
2833
|
+
}
|
|
2834
|
+
catch {
|
|
2835
|
+
sourceIdentity = undefined;
|
|
2836
|
+
logicalIdentity = undefined;
|
|
2837
|
+
}
|
|
717
2838
|
signal.throwIfAborted();
|
|
718
|
-
const
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
const identity = await readNamespacedPluginConfigStoreIdentity({
|
|
722
|
-
sourceId,
|
|
723
|
-
pluginName,
|
|
2839
|
+
const target = await inspectPluginConfigResetTarget({
|
|
2840
|
+
sourceId: parsed.sourceId,
|
|
2841
|
+
pluginName: parsed.pluginName,
|
|
724
2842
|
scope: layer.scope,
|
|
725
|
-
projectLayer: layer.projectLayer,
|
|
726
2843
|
cwd: context.cwd,
|
|
727
2844
|
env: context.env,
|
|
728
2845
|
});
|
|
729
2846
|
signal.throwIfAborted();
|
|
730
|
-
if (
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
2847
|
+
if (target.kind === 'regularFile') {
|
|
2848
|
+
const store = await readPluginConfigStore({
|
|
2849
|
+
sourceId: parsed.sourceId,
|
|
2850
|
+
...(sourceIdentity ? { sourceIdentity } : {}),
|
|
2851
|
+
...(logicalIdentity ? { logicalIdentity } : {}),
|
|
2852
|
+
pluginName: parsed.pluginName,
|
|
2853
|
+
scope: layer.scope,
|
|
2854
|
+
cwd: context.cwd,
|
|
2855
|
+
env: context.env,
|
|
2856
|
+
});
|
|
2857
|
+
signal.throwIfAborted();
|
|
738
2858
|
return {
|
|
739
|
-
exists:
|
|
740
|
-
|
|
741
|
-
|
|
2859
|
+
exists: store.exists,
|
|
2860
|
+
state: store.state,
|
|
2861
|
+
...(store.invalidCode ? { invalidCode: store.invalidCode } : {}),
|
|
2862
|
+
sourceIdentityPresent: sourceIdentity !== undefined,
|
|
2863
|
+
...(sourceIdentity ? { sourceIdentity } : {}),
|
|
2864
|
+
targetKind: target.kind,
|
|
2865
|
+
resettable: target.resettable,
|
|
2866
|
+
storeDigest: target.fingerprint,
|
|
742
2867
|
};
|
|
743
2868
|
}
|
|
744
2869
|
return {
|
|
745
|
-
exists:
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
}),
|
|
2870
|
+
exists: target.exists,
|
|
2871
|
+
state: target.kind === 'absent' ? 'missing' : 'invalid',
|
|
2872
|
+
sourceIdentityPresent: sourceIdentity !== undefined,
|
|
2873
|
+
...(sourceIdentity ? { sourceIdentity } : {}),
|
|
2874
|
+
targetKind: target.kind,
|
|
2875
|
+
resettable: target.resettable,
|
|
2876
|
+
storeDigest: target.fingerprint,
|
|
753
2877
|
};
|
|
754
2878
|
},
|
|
755
|
-
async executeIntent(intent, expectedEvidence) {
|
|
2879
|
+
async executeIntent(intent, expectedEvidence, signal) {
|
|
2880
|
+
if (intent.kind === 'catalogStorePrune') {
|
|
2881
|
+
signal?.throwIfAborted();
|
|
2882
|
+
if (!expectedEvidence.catalogStore) {
|
|
2883
|
+
throw new TuiOperationBlockedError('catalogStoreReviewStale', '确认信息缺少精确清理目标。');
|
|
2884
|
+
}
|
|
2885
|
+
return executeCatalogStoreTask({
|
|
2886
|
+
env: context.env,
|
|
2887
|
+
authorizedItems: expectedEvidence.catalogStore.authorizedItems,
|
|
2888
|
+
});
|
|
2889
|
+
}
|
|
2890
|
+
if (intent.kind === 'catalogSourcesRecovery') {
|
|
2891
|
+
signal?.throwIfAborted();
|
|
2892
|
+
return recoverInvalidCatalogSourcesConfig({ env: context.env });
|
|
2893
|
+
}
|
|
2894
|
+
if (intent.kind === 'catalogGeneration') {
|
|
2895
|
+
signal?.throwIfAborted();
|
|
2896
|
+
const expected = expectedEvidence.catalogGeneration;
|
|
2897
|
+
if (!expected)
|
|
2898
|
+
throw new TuiOperationBlockedError('catalogGenerationReviewStale', '确认信息缺少 Catalog candidate。');
|
|
2899
|
+
const packageInfo = await readCliPackageInfo();
|
|
2900
|
+
let configSaved = false;
|
|
2901
|
+
if (intent.sourceMutation) {
|
|
2902
|
+
const common = {
|
|
2903
|
+
cwd: context.cwd,
|
|
2904
|
+
env: context.env,
|
|
2905
|
+
cliVersion: packageInfo.version,
|
|
2906
|
+
expectedConfigDigest: expected.configDigest,
|
|
2907
|
+
};
|
|
2908
|
+
if (intent.sourceMutation.action === 'add') {
|
|
2909
|
+
await addCatalogSourceCommand({
|
|
2910
|
+
...common,
|
|
2911
|
+
source: intent.sourceMutation.source,
|
|
2912
|
+
expectedProposedConfigDigest: expected.proposedConfigDigest,
|
|
2913
|
+
expectedImpactDigest: expected.impactDigest,
|
|
2914
|
+
requireImpactConfirmation: true,
|
|
2915
|
+
confirmImpact: true,
|
|
2916
|
+
});
|
|
2917
|
+
}
|
|
2918
|
+
else {
|
|
2919
|
+
await updateCatalogSourceCommand({
|
|
2920
|
+
...common,
|
|
2921
|
+
sourceId: intent.sourceId,
|
|
2922
|
+
update: intent.sourceMutation.update,
|
|
2923
|
+
expectedProposedConfigDigest: expected.proposedConfigDigest,
|
|
2924
|
+
expectedImpactDigest: expected.impactDigest,
|
|
2925
|
+
requireIdentityChangeConfirmation: true,
|
|
2926
|
+
confirmIdentityChange: true,
|
|
2927
|
+
});
|
|
2928
|
+
}
|
|
2929
|
+
configSaved = true;
|
|
2930
|
+
await tuiControllerRuntimeTestHooks?.afterCatalogSourceConfigCommit?.();
|
|
2931
|
+
}
|
|
2932
|
+
try {
|
|
2933
|
+
let generation;
|
|
2934
|
+
if (intent.action === 'recoverState') {
|
|
2935
|
+
generation = await recoverCatalogGenerationStateAndActivate({
|
|
2936
|
+
sourceId: intent.sourceId,
|
|
2937
|
+
cliVersion: packageInfo.version,
|
|
2938
|
+
cwd: context.cwd,
|
|
2939
|
+
impactConfirmed: true,
|
|
2940
|
+
expectedImpactDigest: expected.impactDigest,
|
|
2941
|
+
expectedCandidate: expected.candidate,
|
|
2942
|
+
env: context.env,
|
|
2943
|
+
signal,
|
|
2944
|
+
runtime: options.catalogGenerationRuntime,
|
|
2945
|
+
});
|
|
2946
|
+
}
|
|
2947
|
+
else if (intent.action === 'repair') {
|
|
2948
|
+
generation = await repairCatalogSourceGeneration({
|
|
2949
|
+
sourceId: intent.sourceId,
|
|
2950
|
+
cliVersion: packageInfo.version,
|
|
2951
|
+
cwd: context.cwd,
|
|
2952
|
+
expectedCandidate: expected.candidate,
|
|
2953
|
+
expectedActive: expected.previousActive ?? null,
|
|
2954
|
+
expectedConfigDigest: expected.proposedConfigDigest ?? expected.configDigest,
|
|
2955
|
+
env: context.env,
|
|
2956
|
+
signal,
|
|
2957
|
+
runtime: {
|
|
2958
|
+
...options.catalogGenerationRuntime,
|
|
2959
|
+
afterCandidateResolved: tuiControllerRuntimeTestHooks
|
|
2960
|
+
?.afterCatalogGenerationCandidateResolved,
|
|
2961
|
+
},
|
|
2962
|
+
});
|
|
2963
|
+
}
|
|
2964
|
+
else if (intent.action === 'update' && expected.exactRepair) {
|
|
2965
|
+
generation = await repairCatalogSourceGeneration({
|
|
2966
|
+
sourceId: intent.sourceId,
|
|
2967
|
+
cliVersion: packageInfo.version,
|
|
2968
|
+
cwd: context.cwd,
|
|
2969
|
+
expectedActive: expected.previousActive ?? null,
|
|
2970
|
+
expectedConfigDigest: expected.proposedConfigDigest ?? expected.configDigest,
|
|
2971
|
+
env: context.env,
|
|
2972
|
+
signal,
|
|
2973
|
+
runtime: options.catalogGenerationRuntime,
|
|
2974
|
+
});
|
|
2975
|
+
}
|
|
2976
|
+
else if (intent.action === 'replaceDamaged') {
|
|
2977
|
+
generation = await replaceDamagedCatalogSourceGeneration({
|
|
2978
|
+
sourceId: intent.sourceId,
|
|
2979
|
+
cliVersion: packageInfo.version,
|
|
2980
|
+
cwd: context.cwd,
|
|
2981
|
+
expectedCandidate: expected.candidate,
|
|
2982
|
+
expectedActive: expected.previousActive,
|
|
2983
|
+
expectedConfigDigest: expected.proposedConfigDigest ?? expected.configDigest,
|
|
2984
|
+
impactConfirmed: true,
|
|
2985
|
+
expectedImpactDigest: expected.impactDigest,
|
|
2986
|
+
env: context.env,
|
|
2987
|
+
signal,
|
|
2988
|
+
runtime: {
|
|
2989
|
+
...options.catalogGenerationRuntime,
|
|
2990
|
+
afterCandidateResolved: tuiControllerRuntimeTestHooks
|
|
2991
|
+
?.afterCatalogGenerationCandidateResolved,
|
|
2992
|
+
},
|
|
2993
|
+
});
|
|
2994
|
+
}
|
|
2995
|
+
else {
|
|
2996
|
+
generation = await activateCatalogSourceGeneration({
|
|
2997
|
+
sourceId: intent.sourceId,
|
|
2998
|
+
cliVersion: packageInfo.version,
|
|
2999
|
+
cwd: context.cwd,
|
|
3000
|
+
activationMode: expected.activationMode,
|
|
3001
|
+
impactConfirmed: true,
|
|
3002
|
+
expectedImpactDigest: expected.impactDigest,
|
|
3003
|
+
impactMode: expected.impactMode,
|
|
3004
|
+
expectedCandidate: expected.candidate,
|
|
3005
|
+
expectedActive: expected.previousActive ?? null,
|
|
3006
|
+
expectedConfigDigest: expected.proposedConfigDigest ?? expected.configDigest,
|
|
3007
|
+
env: context.env,
|
|
3008
|
+
signal,
|
|
3009
|
+
runtime: {
|
|
3010
|
+
...options.catalogGenerationRuntime,
|
|
3011
|
+
afterCandidateResolved: tuiControllerRuntimeTestHooks
|
|
3012
|
+
?.afterCatalogGenerationCandidateResolved,
|
|
3013
|
+
},
|
|
3014
|
+
});
|
|
3015
|
+
}
|
|
3016
|
+
let primaryChanged = false;
|
|
3017
|
+
if (intent.setPrimaryAfterActivation) {
|
|
3018
|
+
try {
|
|
3019
|
+
await setPrimaryCatalogSourceCommand({
|
|
3020
|
+
sourceId: intent.sourceId,
|
|
3021
|
+
cwd: context.cwd,
|
|
3022
|
+
env: context.env,
|
|
3023
|
+
expectedConfigDigest: expected.primaryConfigDigest,
|
|
3024
|
+
expectedImpactDigest: expected.primaryImpactDigest,
|
|
3025
|
+
requireImpactConfirmation: true,
|
|
3026
|
+
confirmImpact: true,
|
|
3027
|
+
});
|
|
3028
|
+
primaryChanged = true;
|
|
3029
|
+
}
|
|
3030
|
+
catch (error) {
|
|
3031
|
+
return {
|
|
3032
|
+
status: 'partial',
|
|
3033
|
+
partialReason: 'primary',
|
|
3034
|
+
configSaved,
|
|
3035
|
+
generation,
|
|
3036
|
+
activeAvailable: true,
|
|
3037
|
+
errorCode: typeof error === 'object' && error !== null
|
|
3038
|
+
&& 'code' in error && typeof error.code === 'string'
|
|
3039
|
+
? error.code
|
|
3040
|
+
: error instanceof Error ? error.name : 'catalogSourcePrimaryFailed',
|
|
3041
|
+
};
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
return { status: 'succeeded', generation, configSaved, primaryChanged };
|
|
3045
|
+
}
|
|
3046
|
+
catch (error) {
|
|
3047
|
+
if (!configSaved)
|
|
3048
|
+
throw error;
|
|
3049
|
+
return {
|
|
3050
|
+
status: 'partial',
|
|
3051
|
+
partialReason: 'activation',
|
|
3052
|
+
configSaved: true,
|
|
3053
|
+
errorCode: error instanceof CatalogGenerationError ? error.code : 'catalogGenerationRefreshFailed',
|
|
3054
|
+
};
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
756
3057
|
if (intent.kind === 'portableInstall') {
|
|
757
3058
|
if (!expectedEvidence.portable) {
|
|
758
3059
|
throw operationReviewStale('确认信息缺少 Portable evidence。');
|
|
759
3060
|
}
|
|
760
3061
|
const packageInfo = await readCliPackageInfo();
|
|
761
3062
|
try {
|
|
3063
|
+
if (intent.retry) {
|
|
3064
|
+
if (!('retryPairs' in expectedEvidence.portable)) {
|
|
3065
|
+
throw operationReviewStale('确认信息缺少 Portable 重试目标。');
|
|
3066
|
+
}
|
|
3067
|
+
return await executePortableRetry({
|
|
3068
|
+
receipt: intent.retry.receipt,
|
|
3069
|
+
review: expectedEvidence.portable,
|
|
3070
|
+
expectedEvidenceDigest: expectedEvidence.portable.evidenceDigest,
|
|
3071
|
+
cwd: context.cwd,
|
|
3072
|
+
env: context.env,
|
|
3073
|
+
cliVersion: packageInfo.version,
|
|
3074
|
+
signal,
|
|
3075
|
+
onProgress(stage) {
|
|
3076
|
+
const phase = stage === 'prepare' ? '重新检查失败目标'
|
|
3077
|
+
: stage === 'add' ? '重试缺失的 Skills' : '验证重试结果';
|
|
3078
|
+
progressFeed?.setCurrent({ target: 'Skills CLI', phase }, `Portable Skills:${phase}`);
|
|
3079
|
+
},
|
|
3080
|
+
});
|
|
3081
|
+
}
|
|
762
3082
|
return await installPortableSkills({
|
|
763
3083
|
selector: intent.pluginId,
|
|
764
3084
|
scope: intent.scope,
|
|
@@ -769,6 +3089,7 @@ export function createTuiControllerDependencies(context, progressFeed) {
|
|
|
769
3089
|
env: context.env,
|
|
770
3090
|
cliVersion: packageInfo.version,
|
|
771
3091
|
expectedEvidenceDigest: expectedEvidence.portable.evidenceDigest,
|
|
3092
|
+
expectedGenerationSetDigest: expectedEvidence.catalog.generationSetDigest,
|
|
772
3093
|
onProgress(stage) {
|
|
773
3094
|
const phase = stage === 'prepare' ? '检查插件依赖并生成候选'
|
|
774
3095
|
: stage === 'add' ? '安装已选择的 Skills' : '验证安装结果';
|
|
@@ -783,73 +3104,644 @@ export function createTuiControllerDependencies(context, progressFeed) {
|
|
|
783
3104
|
throw error;
|
|
784
3105
|
}
|
|
785
3106
|
}
|
|
3107
|
+
if (intent.kind === 'sourceMutation') {
|
|
3108
|
+
const currentEvidence = await captureEvidence(intent);
|
|
3109
|
+
if (operationEvidenceFingerprint(currentEvidence) !== operationEvidenceFingerprint(expectedEvidence)) {
|
|
3110
|
+
throw new TuiOperationBlockedError('operationReviewStale', '确认后状态发生变化。');
|
|
3111
|
+
}
|
|
3112
|
+
await tuiControllerRuntimeTestHooks?.afterMutationEvidenceCapture?.();
|
|
3113
|
+
const packageInfo = await readCliPackageInfo();
|
|
3114
|
+
const runtime = {
|
|
3115
|
+
onProgress: async (event) => {
|
|
3116
|
+
const phase = SOURCE_PROGRESS_PHASES[event.stage];
|
|
3117
|
+
progressFeed?.setCurrent({ target: 'Catalog 来源配置', phase }, `来源操作:${phase}`);
|
|
3118
|
+
await tuiControllerRuntimeTestHooks?.onCatalogSourceProgress?.(event);
|
|
3119
|
+
},
|
|
3120
|
+
};
|
|
3121
|
+
const common = {
|
|
3122
|
+
cwd: context.cwd,
|
|
3123
|
+
env: context.env,
|
|
3124
|
+
expectedConfigDigest: expectedEvidence.sourceWrite?.configDigest,
|
|
3125
|
+
expectedProposedConfigDigest: expectedEvidence.sourceWrite?.proposedConfigDigest,
|
|
3126
|
+
expectedImpactDigest: expectedEvidence.sourceWrite?.impactDigest,
|
|
3127
|
+
runtime,
|
|
3128
|
+
};
|
|
3129
|
+
if (intent.action === 'add' && intent.source) {
|
|
3130
|
+
return addCatalogSourceCommand({
|
|
3131
|
+
...common,
|
|
3132
|
+
source: intent.source,
|
|
3133
|
+
cliVersion: packageInfo.version,
|
|
3134
|
+
requireImpactConfirmation: true,
|
|
3135
|
+
confirmImpact: true,
|
|
3136
|
+
});
|
|
3137
|
+
}
|
|
3138
|
+
if (intent.action === 'update' && intent.update) {
|
|
3139
|
+
return updateCatalogSourceCommand({
|
|
3140
|
+
...common,
|
|
3141
|
+
sourceId: intent.sourceId,
|
|
3142
|
+
update: intent.update,
|
|
3143
|
+
cliVersion: packageInfo.version,
|
|
3144
|
+
requireIdentityChangeConfirmation: true,
|
|
3145
|
+
confirmIdentityChange: true,
|
|
3146
|
+
});
|
|
3147
|
+
}
|
|
3148
|
+
if (intent.action === 'setPrimary') {
|
|
3149
|
+
return setPrimaryCatalogSourceCommand({
|
|
3150
|
+
...common,
|
|
3151
|
+
sourceId: intent.sourceId,
|
|
3152
|
+
requireImpactConfirmation: true,
|
|
3153
|
+
confirmImpact: true,
|
|
3154
|
+
});
|
|
3155
|
+
}
|
|
3156
|
+
if (intent.action === 'remove') {
|
|
3157
|
+
return removeCatalogSourceCommand({
|
|
3158
|
+
...common,
|
|
3159
|
+
sourceId: intent.sourceId,
|
|
3160
|
+
requireImpactConfirmation: true,
|
|
3161
|
+
confirmImpact: true,
|
|
3162
|
+
});
|
|
3163
|
+
}
|
|
3164
|
+
throw new TuiOperationBlockedError('invalidSourceMutation', '来源操作缺少候选输入。');
|
|
3165
|
+
}
|
|
3166
|
+
const codexObservationSession = intent.kind === 'pluginWrite'
|
|
3167
|
+
|| intent.kind === 'pluginWriteBatch'
|
|
3168
|
+
|| intent.kind === 'pluginWriteTask'
|
|
3169
|
+
? await createCodexNativeObservationSession({
|
|
3170
|
+
exactCwd: context.cwd,
|
|
3171
|
+
env: context.env,
|
|
3172
|
+
runner: context.nativeCliRunner,
|
|
3173
|
+
manager: codexManagerByEvidence.get(expectedEvidence),
|
|
3174
|
+
})
|
|
3175
|
+
: undefined;
|
|
786
3176
|
return withAwHomeMutationLock({
|
|
787
3177
|
env: context.env,
|
|
788
3178
|
action: async (awHomeMutationToken) => {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
3179
|
+
await tuiControllerRuntimeTestHooks?.beforeMutationEvidenceCapture?.();
|
|
3180
|
+
const operationInventory = createOperationInventoryReader();
|
|
3181
|
+
let currentEvidence;
|
|
3182
|
+
try {
|
|
3183
|
+
currentEvidence = await captureEvidence(intent, undefined, undefined, operationInventory, true, undefined, codexObservationSession, intent.kind === 'pluginWriteBatch'
|
|
3184
|
+
? expectedEvidence
|
|
3185
|
+
: undefined);
|
|
792
3186
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
if (intent.
|
|
811
|
-
|
|
3187
|
+
catch (error) {
|
|
3188
|
+
const code = error instanceof TuiOperationBlockedError ? error.code : undefined;
|
|
3189
|
+
if (intent.kind === 'pluginWriteBatch'
|
|
3190
|
+
&& (intent.selection.kind === 'currentProjectIntentRoots'
|
|
3191
|
+
|| intent.selection.kind === 'currentProjectIntentTargets')
|
|
3192
|
+
&& error instanceof TuiOperationBlockedError) {
|
|
3193
|
+
throw operationReviewStale('确认后完整项目 Intent 声明发生变化。');
|
|
3194
|
+
}
|
|
3195
|
+
if (intent.kind === 'pluginWriteTask'
|
|
3196
|
+
&& (code === 'taskHasBlockedTargets' || code === 'taskNoExecutableTargets')) {
|
|
3197
|
+
throw operationReviewStale('确认后任务可执行目标发生变化。', error instanceof TuiOperationBlockedError ? error.diagnostics : undefined);
|
|
3198
|
+
}
|
|
3199
|
+
throw error;
|
|
3200
|
+
}
|
|
3201
|
+
if (intent.kind === 'pluginWriteBatch') {
|
|
3202
|
+
const expectedBatch = expectedEvidence;
|
|
3203
|
+
const currentBatch = currentEvidence;
|
|
3204
|
+
if ((intent.selection.kind === 'currentProjectIntentRoots'
|
|
3205
|
+
|| intent.selection.kind === 'currentProjectIntentTargets')
|
|
3206
|
+
&& (!expectedBatch.projectIntentDigest
|
|
3207
|
+
|| expectedBatch.projectIntentDigest !== currentBatch.projectIntentDigest)) {
|
|
3208
|
+
throw operationReviewStale('确认后完整项目 Intent 声明发生变化。');
|
|
3209
|
+
}
|
|
3210
|
+
const reviewedGroups = expectedBatch.groups.map((group) => ({
|
|
3211
|
+
groupId: group.groupId,
|
|
3212
|
+
sourceId: group.sourceId,
|
|
3213
|
+
platform: group.platform,
|
|
3214
|
+
scope: group.scope,
|
|
3215
|
+
requestedPluginIds: group.requestedPluginIds,
|
|
3216
|
+
executable: group.executable,
|
|
3217
|
+
planFingerprint: group.preview.operationPlan
|
|
3218
|
+
? intent.action === 'cleanup'
|
|
3219
|
+
? group.preview.operationPlan.planFingerprint
|
|
3220
|
+
: createBatchIntentCompatiblePlanFingerprint(group.preview.operationPlan)
|
|
3221
|
+
: undefined,
|
|
3222
|
+
}));
|
|
3223
|
+
const currentGroups = currentBatch.groups.map((group) => ({
|
|
3224
|
+
groupId: group.groupId,
|
|
3225
|
+
sourceId: group.sourceId,
|
|
3226
|
+
platform: group.platform,
|
|
3227
|
+
scope: group.scope,
|
|
3228
|
+
requestedPluginIds: group.requestedPluginIds,
|
|
3229
|
+
executable: expectedBatch.groups.find((reviewed) => reviewed.groupId === group.groupId)
|
|
3230
|
+
?.executable === false ? false : group.executable,
|
|
3231
|
+
planFingerprint: expectedBatch.groups.find((reviewed) => reviewed.groupId === group.groupId)
|
|
3232
|
+
?.executable === false
|
|
3233
|
+
? undefined
|
|
3234
|
+
: group.preview.operationPlan
|
|
3235
|
+
? intent.action === 'cleanup'
|
|
3236
|
+
? group.preview.operationPlan.planFingerprint
|
|
3237
|
+
: createBatchIntentCompatiblePlanFingerprint(group.preview.operationPlan)
|
|
3238
|
+
: undefined,
|
|
3239
|
+
}));
|
|
3240
|
+
if (expectedBatch.catalog.configDigest !== currentBatch.catalog.configDigest
|
|
3241
|
+
|| JSON.stringify(expectedBatch.requestedPluginIds)
|
|
3242
|
+
!== JSON.stringify(currentBatch.requestedPluginIds)
|
|
3243
|
+
|| JSON.stringify(reviewedGroups) !== JSON.stringify(currentGroups)) {
|
|
3244
|
+
throw operationReviewStale('确认后批量目标或写入计划发生变化。');
|
|
3245
|
+
}
|
|
3246
|
+
await tuiControllerRuntimeTestHooks?.afterMutationEvidenceCapture?.();
|
|
3247
|
+
const groupResults = [];
|
|
3248
|
+
const completedTargetKeys = new Set();
|
|
3249
|
+
const expectedIntentDigests = new Map();
|
|
3250
|
+
for (const group of expectedBatch.groups) {
|
|
3251
|
+
for (const intentDigest of group.preview.operationPlan?.evidence.intentDigests ?? []) {
|
|
3252
|
+
expectedIntentDigests.set(intentDigest.scope, intentDigest.digest);
|
|
3253
|
+
}
|
|
812
3254
|
}
|
|
813
|
-
|
|
814
|
-
|
|
3255
|
+
let executedGroupCount = 0;
|
|
3256
|
+
const appendStoppedGroups = (startIndex, staleMessage) => {
|
|
3257
|
+
for (const [offset, stopped] of expectedBatch.groups.slice(startIndex).entries()) {
|
|
3258
|
+
groupResults.push({
|
|
3259
|
+
groupId: stopped.groupId,
|
|
3260
|
+
sourceId: stopped.sourceId,
|
|
3261
|
+
platform: stopped.platform,
|
|
3262
|
+
scope: stopped.scope,
|
|
3263
|
+
requestedPluginIds: stopped.requestedPluginIds,
|
|
3264
|
+
result: !stopped.executable ? stopped.preview : failedWriteCommandResult(intent.action, stopped.requestedPluginIds, { cwd: context.cwd, platform: stopped.platform, scope: stopped.scope }, offset === 0
|
|
3265
|
+
? new TuiOperationBlockedError('operationReviewStale', staleMessage)
|
|
3266
|
+
: new TuiOperationBlockedError('batchNotStarted', '前一个批量组的写入计划已变化,因此这个组没有开始。')),
|
|
3267
|
+
});
|
|
3268
|
+
}
|
|
3269
|
+
};
|
|
3270
|
+
for (const [groupIndex, reviewed] of expectedBatch.groups.entries()) {
|
|
3271
|
+
const current = currentBatch.groups.find((group) => group.groupId === reviewed.groupId);
|
|
3272
|
+
if (!reviewed.executable) {
|
|
3273
|
+
groupResults.push({
|
|
3274
|
+
groupId: reviewed.groupId,
|
|
3275
|
+
sourceId: reviewed.sourceId,
|
|
3276
|
+
platform: reviewed.platform,
|
|
3277
|
+
scope: reviewed.scope,
|
|
3278
|
+
requestedPluginIds: reviewed.requestedPluginIds,
|
|
3279
|
+
result: reviewed.preview,
|
|
3280
|
+
});
|
|
3281
|
+
continue;
|
|
3282
|
+
}
|
|
3283
|
+
const groupInventory = groupResults.length === 0
|
|
3284
|
+
? operationInventory
|
|
3285
|
+
: createOperationInventoryReader();
|
|
3286
|
+
let executionPreview = current.preview;
|
|
3287
|
+
if (completedTargetKeys.size > 0
|
|
3288
|
+
|| (intent.action === 'cleanup' && executedGroupCount > 0)) {
|
|
3289
|
+
executionPreview = await runBatchWriteGroup({
|
|
3290
|
+
intent,
|
|
3291
|
+
group: current,
|
|
3292
|
+
cwd: context.cwd,
|
|
3293
|
+
runtime: {
|
|
3294
|
+
...context,
|
|
3295
|
+
awHomeMutationToken,
|
|
3296
|
+
inventoryCatalogSourceReferences: groupInventory,
|
|
3297
|
+
...(intent.action === 'cleanup' ? {} : {
|
|
3298
|
+
expectedCatalogConfigDigest: expectedBatch.catalog.configDigest,
|
|
3299
|
+
}),
|
|
3300
|
+
previewOperationPlan: true,
|
|
3301
|
+
...(codexObservationSession ? { codexObservationSession } : {}),
|
|
3302
|
+
},
|
|
3303
|
+
});
|
|
3304
|
+
const cleanupIntentDigestChanged = intent.action === 'cleanup'
|
|
3305
|
+
&& executionPreview.operationPlan?.evidence.intentDigests.find((entry) => entry.scope === current.scope)?.digest !== expectedIntentDigests.get(current.scope);
|
|
3306
|
+
if (cleanupIntentDigestChanged
|
|
3307
|
+
|| !reviewed.preview.operationPlan || !executionPreview.operationPlan
|
|
3308
|
+
|| createBatchIntentCompatiblePlanFingerprint(reviewed.preview.operationPlan, completedTargetKeys) !== createBatchIntentCompatiblePlanFingerprint(executionPreview.operationPlan, completedTargetKeys)) {
|
|
3309
|
+
appendStoppedGroups(groupIndex, '前序批量结果改变了这个组的实际写入计划。');
|
|
3310
|
+
break;
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
const requiresManagedCodex = reviewed.preview.codexManagerPreview?.state === 'requires-install';
|
|
3314
|
+
const result = await runBatchWriteGroup({
|
|
3315
|
+
intent,
|
|
3316
|
+
group: current,
|
|
3317
|
+
cwd: context.cwd,
|
|
3318
|
+
runtime: {
|
|
3319
|
+
...context,
|
|
3320
|
+
awHomeMutationToken,
|
|
3321
|
+
preApplyConfirmed: true,
|
|
3322
|
+
inventoryCatalogSourceReferences: groupInventory,
|
|
3323
|
+
...(intent.action === 'cleanup' ? {} : {
|
|
3324
|
+
expectedCatalogConfigDigest: expectedBatch.catalog.configDigest,
|
|
3325
|
+
}),
|
|
3326
|
+
...(executionPreview.operationPlan ? {
|
|
3327
|
+
expectedPlanFingerprint: executionPreview.operationPlan.planFingerprint,
|
|
3328
|
+
} : {}),
|
|
3329
|
+
...(requiresManagedCodex ? {
|
|
3330
|
+
managedCodexAuthorizer: async () => true,
|
|
3331
|
+
} : {}),
|
|
3332
|
+
...(codexObservationSession ? { codexObservationSession } : {}),
|
|
3333
|
+
},
|
|
3334
|
+
});
|
|
3335
|
+
if (writeReviewIsStale(result)) {
|
|
3336
|
+
if (executedGroupCount === 0)
|
|
3337
|
+
throwIfWriteReviewStale(result);
|
|
3338
|
+
appendStoppedGroups(groupIndex, '这个组的实际写入计划发生变化。');
|
|
3339
|
+
break;
|
|
3340
|
+
}
|
|
3341
|
+
if (intent.action === 'cleanup') {
|
|
3342
|
+
for (const change of result.operationPlan?.intentChanges ?? []) {
|
|
3343
|
+
expectedIntentDigests.set(change.scope, change.afterDigest);
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
executedGroupCount += 1;
|
|
3347
|
+
for (const target of result.targetResults) {
|
|
3348
|
+
if (target.state === 'completed') {
|
|
3349
|
+
completedTargetKeys.add([
|
|
3350
|
+
target.target.pluginId,
|
|
3351
|
+
target.target.platform,
|
|
3352
|
+
target.target.scope,
|
|
3353
|
+
].join('\0'));
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
groupResults.push({
|
|
3357
|
+
groupId: reviewed.groupId,
|
|
3358
|
+
sourceId: reviewed.sourceId,
|
|
3359
|
+
platform: reviewed.platform,
|
|
3360
|
+
scope: reviewed.scope,
|
|
3361
|
+
requestedPluginIds: reviewed.requestedPluginIds,
|
|
3362
|
+
result,
|
|
3363
|
+
});
|
|
815
3364
|
}
|
|
816
|
-
|
|
817
|
-
|
|
3365
|
+
const merged = mergeWriteCommandResults(groupResults.map((group) => group.result));
|
|
3366
|
+
return {
|
|
3367
|
+
...merged,
|
|
3368
|
+
requestedPluginIds: expectedBatch.requestedPluginIds,
|
|
3369
|
+
groupResults,
|
|
3370
|
+
};
|
|
3371
|
+
}
|
|
3372
|
+
if (intent.kind === 'pluginWriteTask') {
|
|
3373
|
+
const expectedTask = expectedEvidence;
|
|
3374
|
+
const currentTask = currentEvidence;
|
|
3375
|
+
if (!operationEvidenceMatches(expectedTask, currentTask)) {
|
|
3376
|
+
throw operationReviewStale('确认后任务目标、根因、版本或写入计划发生变化。');
|
|
818
3377
|
}
|
|
819
|
-
|
|
820
|
-
|
|
3378
|
+
await tuiControllerRuntimeTestHooks?.afterMutationEvidenceCapture?.();
|
|
3379
|
+
const operationContexts = taskCatalogOperationsByEvidence.get(currentEvidence)
|
|
3380
|
+
?? new Map();
|
|
3381
|
+
const sourceResults = [];
|
|
3382
|
+
const completedTargetKeys = new Set();
|
|
3383
|
+
const preApplyExecutionCache = new Map();
|
|
3384
|
+
const commandTargets = (source) => source.requestedTargets.map((target) => {
|
|
3385
|
+
const direct = source.causes.filter((cause) => pluginWriteTaskTargetKey(cause.repairTarget) === pluginWriteTaskTargetKey(target));
|
|
3386
|
+
return {
|
|
3387
|
+
pluginId: target.pluginId,
|
|
3388
|
+
platform: target.platform,
|
|
3389
|
+
scope: target.scope,
|
|
3390
|
+
method: direct.some((cause) => cause.method === 'reinstall')
|
|
3391
|
+
? 'reinstall' : 'sync',
|
|
3392
|
+
};
|
|
3393
|
+
});
|
|
3394
|
+
const repairTargets = (source) => [...new Map(source.causes.map((cause) => [
|
|
3395
|
+
pluginWriteTaskTargetKey(cause.repairTarget),
|
|
3396
|
+
{ ...cause.repairTarget, method: cause.method },
|
|
3397
|
+
])).values()];
|
|
3398
|
+
const stoppedResult = (source, error) => mergeWriteCommandResults(source.requestedTargets.map((target) => failedWriteCommandResult('sync', [target.pluginId], {
|
|
3399
|
+
cwd: context.cwd,
|
|
3400
|
+
platform: target.platform,
|
|
3401
|
+
scope: target.scope,
|
|
3402
|
+
}, error)));
|
|
3403
|
+
const appendStoppedSources = (startIndex, message) => {
|
|
3404
|
+
for (const [offset, source] of expectedTask.taskPlan.sources.slice(startIndex).entries()) {
|
|
3405
|
+
sourceResults.push({
|
|
3406
|
+
sourceId: source.sourceId,
|
|
3407
|
+
sourceIdentity: source.sourceIdentity,
|
|
3408
|
+
requestedTargets: source.requestedTargets,
|
|
3409
|
+
result: stoppedResult(source, offset === 0
|
|
3410
|
+
? new TuiOperationBlockedError('operationReviewStale', message)
|
|
3411
|
+
: new TuiOperationBlockedError('taskNotStarted', '前一个来源计划已变化,因此这个来源没有开始。')),
|
|
3412
|
+
});
|
|
3413
|
+
}
|
|
3414
|
+
};
|
|
3415
|
+
for (const [sourceIndex, reviewed] of expectedTask.taskPlan.sources.entries()) {
|
|
3416
|
+
const current = currentTask.taskPlan.sources.find((source) => source.sourceId === reviewed.sourceId);
|
|
3417
|
+
const catalogOperation = operationContexts.get(reviewed.sourceId);
|
|
3418
|
+
if (!current || !catalogOperation || current.sourceIdentity !== reviewed.sourceIdentity) {
|
|
3419
|
+
if (sourceResults.length === 0) {
|
|
3420
|
+
throw operationReviewStale('确认后任务来源身份发生变化。');
|
|
3421
|
+
}
|
|
3422
|
+
appendStoppedSources(sourceIndex, '确认后任务来源身份发生变化。');
|
|
3423
|
+
break;
|
|
3424
|
+
}
|
|
3425
|
+
const sourceInventory = sourceResults.length === 0
|
|
3426
|
+
? operationInventory : createOperationInventoryReader();
|
|
3427
|
+
let executionPreview = current.preview;
|
|
3428
|
+
if (completedTargetKeys.size > 0) {
|
|
3429
|
+
executionPreview = await taskWriteCommand(commandTargets(current), {
|
|
3430
|
+
cwd: context.cwd,
|
|
3431
|
+
repairTargets: repairTargets(current),
|
|
3432
|
+
...(reviewedScriptTimeoutMs(intent) === undefined
|
|
3433
|
+
? {} : { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
|
|
3434
|
+
}, {
|
|
3435
|
+
...context,
|
|
3436
|
+
awHomeMutationToken,
|
|
3437
|
+
catalogOperationContext: catalogOperation,
|
|
3438
|
+
expectedGenerationSetDigest: catalogOperation.generationSetDigest,
|
|
3439
|
+
expectedCatalogConfigDigest: expectedTask.catalog.configDigest,
|
|
3440
|
+
inventoryCatalogSourceReferences: sourceInventory,
|
|
3441
|
+
previewOperationPlan: true,
|
|
3442
|
+
preApplyExecutionCache,
|
|
3443
|
+
...(codexObservationSession ? { codexObservationSession } : {}),
|
|
3444
|
+
});
|
|
3445
|
+
if (!reviewed.preview.operationPlan || !executionPreview.operationPlan
|
|
3446
|
+
|| createBatchIntentCompatiblePlanFingerprint(reviewed.preview.operationPlan, completedTargetKeys) !== createBatchIntentCompatiblePlanFingerprint(executionPreview.operationPlan, completedTargetKeys)) {
|
|
3447
|
+
appendStoppedSources(sourceIndex, '前序结果改变了这个来源的实际写入计划。');
|
|
3448
|
+
break;
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
const requiresManagedCodex = reviewed.preview.codexManagerPreview?.state === 'requires-install';
|
|
3452
|
+
const result = await taskWriteCommand(commandTargets(current), {
|
|
3453
|
+
cwd: context.cwd,
|
|
3454
|
+
repairTargets: repairTargets(current),
|
|
3455
|
+
...(reviewedScriptTimeoutMs(intent) === undefined
|
|
3456
|
+
? {} : { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
|
|
3457
|
+
}, {
|
|
3458
|
+
...context,
|
|
3459
|
+
awHomeMutationToken,
|
|
3460
|
+
catalogOperationContext: catalogOperation,
|
|
3461
|
+
expectedGenerationSetDigest: catalogOperation.generationSetDigest,
|
|
3462
|
+
expectedCatalogConfigDigest: expectedTask.catalog.configDigest,
|
|
3463
|
+
inventoryCatalogSourceReferences: sourceInventory,
|
|
3464
|
+
preApplyConfirmed: true,
|
|
3465
|
+
preApplyExecutionCache,
|
|
3466
|
+
...(executionPreview.operationPlan
|
|
3467
|
+
? { expectedPlanFingerprint: executionPreview.operationPlan.planFingerprint } : {}),
|
|
3468
|
+
...(requiresManagedCodex ? { managedCodexAuthorizer: async () => true } : {}),
|
|
3469
|
+
...(codexObservationSession ? { codexObservationSession } : {}),
|
|
3470
|
+
});
|
|
3471
|
+
if (writeReviewIsStale(result)) {
|
|
3472
|
+
if (sourceResults.length === 0)
|
|
3473
|
+
throwIfWriteReviewStale(result);
|
|
3474
|
+
appendStoppedSources(sourceIndex, '这个来源的实际写入计划发生变化。');
|
|
3475
|
+
break;
|
|
3476
|
+
}
|
|
3477
|
+
for (const target of result.targetResults) {
|
|
3478
|
+
if (target.state === 'completed') {
|
|
3479
|
+
completedTargetKeys.add([
|
|
3480
|
+
target.target.pluginId,
|
|
3481
|
+
target.target.platform,
|
|
3482
|
+
target.target.scope,
|
|
3483
|
+
].join('\0'));
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
sourceResults.push({
|
|
3487
|
+
sourceId: reviewed.sourceId,
|
|
3488
|
+
sourceIdentity: reviewed.sourceIdentity,
|
|
3489
|
+
requestedTargets: reviewed.requestedTargets,
|
|
3490
|
+
result,
|
|
3491
|
+
});
|
|
821
3492
|
}
|
|
822
|
-
|
|
3493
|
+
const merged = mergeWriteCommandResults(sourceResults.map((source) => source.result));
|
|
3494
|
+
return {
|
|
3495
|
+
...merged,
|
|
3496
|
+
taskPlan: expectedTask.taskPlan,
|
|
3497
|
+
sourceResults,
|
|
3498
|
+
};
|
|
3499
|
+
}
|
|
3500
|
+
if (intent.kind !== 'pluginWrite'
|
|
3501
|
+
&& !operationEvidenceMatches(expectedEvidence, currentEvidence)) {
|
|
3502
|
+
throw new TuiOperationBlockedError('operationReviewStale', '确认后状态发生变化。');
|
|
3503
|
+
}
|
|
3504
|
+
const catalogOperation = catalogOperationByEvidence.get(currentEvidence);
|
|
3505
|
+
if (expectedEvidence.catalog.generationSetDigest
|
|
3506
|
+
&& catalogOperation?.generationSetDigest !== expectedEvidence.catalog.generationSetDigest) {
|
|
3507
|
+
throw operationReviewStale('确认后 Active Catalog generation 发生变化。');
|
|
823
3508
|
}
|
|
3509
|
+
await tuiControllerRuntimeTestHooks?.afterMutationEvidenceCapture?.();
|
|
824
3510
|
if (intent.kind === 'pluginWrite') {
|
|
825
3511
|
const options = {
|
|
826
3512
|
cwd: context.cwd,
|
|
827
3513
|
platform: intent.platform,
|
|
828
3514
|
scope: intent.scope,
|
|
3515
|
+
...(reviewedScriptTimeoutMs(intent) === undefined
|
|
3516
|
+
? {}
|
|
3517
|
+
: { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
|
|
3518
|
+
...(intent.action === 'install' && intent.dependencyScopeOverrides
|
|
3519
|
+
? { dependencyScopeOverrides: intent.dependencyScopeOverrides }
|
|
3520
|
+
: {}),
|
|
3521
|
+
...(expectedEvidence.codexManager?.state === 'requires-install'
|
|
3522
|
+
? { allowManagedCodexInstall: true }
|
|
3523
|
+
: {}),
|
|
829
3524
|
...(intent.action === 'cleanup' ? { force: intent.force === true } : {}),
|
|
830
3525
|
};
|
|
831
3526
|
const command = {
|
|
832
3527
|
install: installWriteCommand,
|
|
833
|
-
|
|
3528
|
+
refreshAndSync: syncWriteCommand,
|
|
834
3529
|
reinstall: reinstallWriteCommand,
|
|
835
3530
|
uninstall: uninstallWriteCommand,
|
|
836
3531
|
cleanup: cleanupWriteCommand,
|
|
3532
|
+
sync: syncWriteCommand,
|
|
837
3533
|
}[intent.action];
|
|
3534
|
+
if (intent.action === 'cleanup') {
|
|
3535
|
+
if (!expectedEvidence.operationPlan || !currentEvidence.operationPlan
|
|
3536
|
+
|| expectedEvidence.operationPlan.planFingerprint
|
|
3537
|
+
!== currentEvidence.operationPlan.planFingerprint) {
|
|
3538
|
+
throw operationReviewStale('确认后清理计划发生变化。');
|
|
3539
|
+
}
|
|
3540
|
+
const result = await command([intent.pluginId], options, {
|
|
3541
|
+
...context,
|
|
3542
|
+
...(codexObservationSession ? { codexObservationSession } : {}),
|
|
3543
|
+
awHomeMutationToken,
|
|
3544
|
+
preApplyConfirmed: true,
|
|
3545
|
+
inventoryCatalogSourceReferences: operationInventory,
|
|
3546
|
+
expectedPlanFingerprint: expectedEvidence.operationPlan.planFingerprint,
|
|
3547
|
+
...(expectedEvidence.codexManager?.state === 'requires-install'
|
|
3548
|
+
? { managedCodexAuthorizer: async () => true }
|
|
3549
|
+
: {}),
|
|
3550
|
+
});
|
|
3551
|
+
throwIfWriteReviewStale(result);
|
|
3552
|
+
return result;
|
|
3553
|
+
}
|
|
838
3554
|
const currentSource = currentEvidence.source;
|
|
839
3555
|
if (!expectedEvidence.source || !currentSource) {
|
|
840
3556
|
throw new TuiOperationBlockedError('operationReviewStale', '确认信息缺少来源身份。');
|
|
841
3557
|
}
|
|
842
3558
|
const result = await command([intent.pluginId], options, {
|
|
843
3559
|
...context,
|
|
3560
|
+
...(codexObservationSession ? { codexObservationSession } : {}),
|
|
844
3561
|
awHomeMutationToken,
|
|
3562
|
+
preApplyConfirmed: true,
|
|
3563
|
+
inventoryCatalogSourceReferences: operationInventory,
|
|
3564
|
+
...(expectedEvidence.operationPlan
|
|
3565
|
+
? { expectedPlanFingerprint: expectedEvidence.operationPlan.planFingerprint }
|
|
3566
|
+
: {}),
|
|
845
3567
|
expectedCatalogConfigDigest: expectedEvidence.catalog.configDigest,
|
|
3568
|
+
...(catalogOperation ? {
|
|
3569
|
+
catalogOperationContext: catalogOperation,
|
|
3570
|
+
expectedGenerationSetDigest: expectedEvidence.catalog.generationSetDigest,
|
|
3571
|
+
} : {}),
|
|
3572
|
+
...(expectedEvidence.codexManager?.state === 'requires-install'
|
|
3573
|
+
? { managedCodexAuthorizer: async () => true }
|
|
3574
|
+
: {}),
|
|
846
3575
|
reviewPreflight: (actual) => assertSourceEvidenceMatches(currentSource, actual),
|
|
847
3576
|
});
|
|
848
3577
|
throwIfWriteReviewStale(result);
|
|
849
3578
|
return result;
|
|
850
3579
|
}
|
|
3580
|
+
if (intent.kind === 'configSaveBatch') {
|
|
3581
|
+
assertConfigSaveBatchIntent(intent);
|
|
3582
|
+
const reviewedBatch = expectedEvidence.configBatch;
|
|
3583
|
+
if (!reviewedBatch || !currentEvidence.configBatch || !currentEvidence.source) {
|
|
3584
|
+
throw operationReviewStale('确认信息缺少设置批次证据。');
|
|
3585
|
+
}
|
|
3586
|
+
const approvedApplyTargets = new Set(reviewedBatch.groups.flatMap((group) => group.config.affectedTargets.map((target) => `${target.platform}\0${target.scope}`)));
|
|
3587
|
+
const approvedPreApplyTargets = reviewedBatch.groups.flatMap((group) => group.config.preApplyAuthorization ?? []);
|
|
3588
|
+
const results = [];
|
|
3589
|
+
const packageInfo = await readCliPackageInfo();
|
|
3590
|
+
for (const group of intent.groups) {
|
|
3591
|
+
const groupIntent = configSaveBatchGroupIntent(intent, group);
|
|
3592
|
+
const reviewedGroup = reviewedBatch.groups.find((item) => item.scope === group.scope);
|
|
3593
|
+
if (!reviewedGroup) {
|
|
3594
|
+
throw operationReviewStale('确认信息缺少设置层基线。');
|
|
3595
|
+
}
|
|
3596
|
+
let configSaved = false;
|
|
3597
|
+
const operationRebuildResults = [];
|
|
3598
|
+
try {
|
|
3599
|
+
const freshEvidence = await captureEvidence(groupIntent, undefined, undefined, operationInventory, true);
|
|
3600
|
+
const freshSource = freshEvidence.source;
|
|
3601
|
+
const freshConfig = freshEvidence.config;
|
|
3602
|
+
if (!freshSource || !freshConfig) {
|
|
3603
|
+
throw operationReviewStale('设置目标缺少最新证据。');
|
|
3604
|
+
}
|
|
3605
|
+
assertSourceEvidenceMatches(currentEvidence.source, freshSource);
|
|
3606
|
+
const freshLayerStoreDigest = await configLayerStoreDigest({
|
|
3607
|
+
source: freshSource,
|
|
3608
|
+
pluginName: parsePluginId(intent.pluginId).pluginName,
|
|
3609
|
+
scope: group.scope,
|
|
3610
|
+
cwd: context.cwd,
|
|
3611
|
+
env: context.env,
|
|
3612
|
+
});
|
|
3613
|
+
if (freshLayerStoreDigest !== reviewedGroup.config.storeDigest) {
|
|
3614
|
+
throw operationReviewStale('保存设置期间此设置层的原始内容发生变化。');
|
|
3615
|
+
}
|
|
3616
|
+
if (freshConfig.affectedTargets.some((target) => !approvedApplyTargets.has(`${target.platform}\0${target.scope}`))) {
|
|
3617
|
+
throw operationReviewStale('同批前一层变更扩大了未经确认的应用目标。');
|
|
3618
|
+
}
|
|
3619
|
+
const groupCatalogOperation = catalogOperationByEvidence.get(freshEvidence);
|
|
3620
|
+
if (expectedEvidence.catalog.generationSetDigest
|
|
3621
|
+
&& groupCatalogOperation?.generationSetDigest
|
|
3622
|
+
!== expectedEvidence.catalog.generationSetDigest) {
|
|
3623
|
+
throw operationReviewStale('保存设置期间 Active Catalog generation 发生变化。');
|
|
3624
|
+
}
|
|
3625
|
+
const result = await withResolvedCatalogPlugin({
|
|
3626
|
+
selector: intent.pluginId,
|
|
3627
|
+
cliVersion: packageInfo.version,
|
|
3628
|
+
env: context.env,
|
|
3629
|
+
...(groupCatalogOperation ? { operationContext: groupCatalogOperation } : {}),
|
|
3630
|
+
consume: async ({ plugin, source }) => {
|
|
3631
|
+
assertSourceEvidenceMatches(freshSource, sourceEvidenceForPlugin(source, plugin.manifest.name));
|
|
3632
|
+
const applyTargets = intent.apply
|
|
3633
|
+
? freshConfig.affectedTargets.map((target) => ({
|
|
3634
|
+
pluginName: plugin.manifest.name,
|
|
3635
|
+
platform: target.platform,
|
|
3636
|
+
scope: target.scope,
|
|
3637
|
+
}))
|
|
3638
|
+
: [];
|
|
3639
|
+
return applyPluginConfigWrite({
|
|
3640
|
+
plugin,
|
|
3641
|
+
pluginName: plugin.manifest.name,
|
|
3642
|
+
scope: group.scope,
|
|
3643
|
+
cwd: context.cwd,
|
|
3644
|
+
env: context.env,
|
|
3645
|
+
nativeCliRunner: context.nativeCliRunner,
|
|
3646
|
+
rebuildRuntime: {
|
|
3647
|
+
...context,
|
|
3648
|
+
awHomeMutationToken,
|
|
3649
|
+
operationDiagnosticSink: (entries) => {
|
|
3650
|
+
operationRebuildResults.push(...structuredClone(entries));
|
|
3651
|
+
},
|
|
3652
|
+
...(reviewedScriptTimeoutMs(intent) === undefined ? {} : {
|
|
3653
|
+
scriptTimeoutMs: reviewedScriptTimeoutMs(intent),
|
|
3654
|
+
}),
|
|
3655
|
+
...(groupCatalogOperation ? {
|
|
3656
|
+
catalogOperationContext: groupCatalogOperation,
|
|
3657
|
+
expectedGenerationSetDigest: expectedEvidence.catalog.generationSetDigest,
|
|
3658
|
+
} : {}),
|
|
3659
|
+
},
|
|
3660
|
+
awHomeMutationToken,
|
|
3661
|
+
applyTargets,
|
|
3662
|
+
request: { updates: group.updates, removeKeys: group.removeKeys },
|
|
3663
|
+
sharedNativeApplyConfirmed: true,
|
|
3664
|
+
preApplyConfirmed: true,
|
|
3665
|
+
preApplyAuthorizedTargets: approvedPreApplyTargets,
|
|
3666
|
+
});
|
|
3667
|
+
},
|
|
3668
|
+
});
|
|
3669
|
+
configSaved = result.configWrite === 'succeeded';
|
|
3670
|
+
const applyStatus = !intent.apply
|
|
3671
|
+
? 'notRequested'
|
|
3672
|
+
: result.apply.state === 'succeeded'
|
|
3673
|
+
? 'succeeded'
|
|
3674
|
+
: result.apply.state === 'skipped'
|
|
3675
|
+
? 'notNeeded'
|
|
3676
|
+
: 'failed';
|
|
3677
|
+
results.push({
|
|
3678
|
+
scope: group.scope,
|
|
3679
|
+
status: applyStatus === 'failed' ? 'failed' : 'succeeded',
|
|
3680
|
+
configSaved,
|
|
3681
|
+
applyRequested: intent.apply,
|
|
3682
|
+
applyStatus,
|
|
3683
|
+
execution: { ...result, operationRebuildResults },
|
|
3684
|
+
...(applyStatus === 'failed' ? {
|
|
3685
|
+
errorCode: result.apply.state === 'unavailable'
|
|
3686
|
+
? result.apply.error?.code ?? result.apply.reason
|
|
3687
|
+
: result.apply.targetResults.find((target) => target.errorCode)?.errorCode
|
|
3688
|
+
?? 'runtimeApplyFailed',
|
|
3689
|
+
} : {}),
|
|
3690
|
+
});
|
|
3691
|
+
}
|
|
3692
|
+
catch (error) {
|
|
3693
|
+
configSaved = error?.configWrite === 'succeeded';
|
|
3694
|
+
const errorCode = typeof error === 'object' && error !== null
|
|
3695
|
+
&& 'code' in error && typeof error.code === 'string'
|
|
3696
|
+
? error.code
|
|
3697
|
+
: configSaved ? 'runtimeApplyFailed' : 'configWriteFailed';
|
|
3698
|
+
results.push({
|
|
3699
|
+
scope: group.scope,
|
|
3700
|
+
status: 'failed',
|
|
3701
|
+
configSaved,
|
|
3702
|
+
applyRequested: intent.apply,
|
|
3703
|
+
applyStatus: intent.apply ? 'failed' : 'notRequested',
|
|
3704
|
+
errorCode,
|
|
3705
|
+
...(operationRebuildResults.length > 0
|
|
3706
|
+
? { execution: { operationRebuildResults } }
|
|
3707
|
+
: {}),
|
|
3708
|
+
});
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3711
|
+
const succeeded = results.filter((result) => result.status === 'succeeded').length;
|
|
3712
|
+
return {
|
|
3713
|
+
status: succeeded === results.length
|
|
3714
|
+
? 'succeeded'
|
|
3715
|
+
: succeeded > 0 || results.some((result) => result.configSaved)
|
|
3716
|
+
? 'partial'
|
|
3717
|
+
: 'failed',
|
|
3718
|
+
groups: results,
|
|
3719
|
+
};
|
|
3720
|
+
}
|
|
851
3721
|
if (intent.kind === 'configSave' || intent.kind === 'configReset') {
|
|
3722
|
+
const operationRebuildResults = [];
|
|
3723
|
+
const operationDiagnosticSink = (results) => {
|
|
3724
|
+
operationRebuildResults.push(...structuredClone(results));
|
|
3725
|
+
};
|
|
852
3726
|
const currentSource = currentEvidence.source;
|
|
3727
|
+
if (intent.kind === 'configReset') {
|
|
3728
|
+
const parsed = parsePluginId(intent.pluginId);
|
|
3729
|
+
if (!parsed.sourceId || !parsed.pluginName) {
|
|
3730
|
+
throw operationReviewStale('确认信息缺少 qualified 设置目标。');
|
|
3731
|
+
}
|
|
3732
|
+
return resetPluginConfigWithHelper({
|
|
3733
|
+
sourceId: parsed.sourceId,
|
|
3734
|
+
pluginName: parsed.pluginName,
|
|
3735
|
+
scope: intent.scope,
|
|
3736
|
+
cwd: context.cwd,
|
|
3737
|
+
env: context.env,
|
|
3738
|
+
nativeCliRunner: context.nativeCliRunner,
|
|
3739
|
+
rebuildRuntime: { ...context, awHomeMutationToken },
|
|
3740
|
+
awHomeMutationToken,
|
|
3741
|
+
apply: false,
|
|
3742
|
+
sharedNativeApplyConfirmed: false,
|
|
3743
|
+
});
|
|
3744
|
+
}
|
|
853
3745
|
if (!currentSource) {
|
|
854
3746
|
throw operationReviewStale('确认信息缺少来源身份。');
|
|
855
3747
|
}
|
|
@@ -857,40 +3749,76 @@ export function createTuiControllerDependencies(context, progressFeed) {
|
|
|
857
3749
|
return withResolvedCatalogPlugin({
|
|
858
3750
|
selector: intent.pluginId,
|
|
859
3751
|
cliVersion: packageInfo.version,
|
|
860
|
-
freshnessPolicy: 'requireRemote',
|
|
861
3752
|
env: context.env,
|
|
3753
|
+
...(catalogOperation ? { operationContext: catalogOperation } : {}),
|
|
862
3754
|
consume: async ({ plugin, source }) => {
|
|
863
3755
|
assertSourceEvidenceMatches(currentSource, sourceEvidenceForPlugin(source, plugin.manifest.name));
|
|
864
|
-
const
|
|
865
|
-
?
|
|
866
|
-
plugin,
|
|
3756
|
+
const applyTargets = intent.retryApply || intent.apply
|
|
3757
|
+
? currentEvidence.config?.affectedTargets.map((target) => ({
|
|
867
3758
|
pluginName: plugin.manifest.name,
|
|
3759
|
+
platform: target.platform,
|
|
3760
|
+
scope: target.scope,
|
|
3761
|
+
})) ?? []
|
|
3762
|
+
: [];
|
|
3763
|
+
const result = intent.retryApply
|
|
3764
|
+
? await replayConfigRuntimeWrites({
|
|
3765
|
+
plugin,
|
|
868
3766
|
scope: intent.scope,
|
|
869
|
-
projectLayer: intent.projectLayer,
|
|
870
3767
|
cwd: context.cwd,
|
|
871
3768
|
env: context.env,
|
|
872
3769
|
nativeCliRunner: context.nativeCliRunner,
|
|
873
|
-
rebuildRuntime: {
|
|
3770
|
+
rebuildRuntime: {
|
|
3771
|
+
...context,
|
|
3772
|
+
awHomeMutationToken,
|
|
3773
|
+
operationDiagnosticSink,
|
|
3774
|
+
...(reviewedScriptTimeoutMs(intent) === undefined
|
|
3775
|
+
? {}
|
|
3776
|
+
: { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
|
|
3777
|
+
...(catalogOperation ? {
|
|
3778
|
+
catalogOperationContext: catalogOperation,
|
|
3779
|
+
expectedGenerationSetDigest: expectedEvidence.catalog.generationSetDigest,
|
|
3780
|
+
} : {}),
|
|
3781
|
+
},
|
|
874
3782
|
awHomeMutationToken,
|
|
875
|
-
|
|
3783
|
+
applyTargets,
|
|
3784
|
+
sharedNativeApplyConfirmed: true,
|
|
3785
|
+
preApplyConfirmed: true,
|
|
3786
|
+
preApplyAuthorizedTargets: expectedEvidence.config?.preApplyAuthorization ?? [],
|
|
876
3787
|
})
|
|
877
|
-
: await
|
|
3788
|
+
: await applyPluginConfigWrite({
|
|
878
3789
|
plugin,
|
|
879
3790
|
pluginName: plugin.manifest.name,
|
|
880
3791
|
scope: intent.scope,
|
|
881
|
-
projectLayer: intent.projectLayer,
|
|
882
3792
|
cwd: context.cwd,
|
|
883
3793
|
env: context.env,
|
|
884
3794
|
nativeCliRunner: context.nativeCliRunner,
|
|
885
|
-
rebuildRuntime: {
|
|
3795
|
+
rebuildRuntime: {
|
|
3796
|
+
...context,
|
|
3797
|
+
awHomeMutationToken,
|
|
3798
|
+
operationDiagnosticSink,
|
|
3799
|
+
...(reviewedScriptTimeoutMs(intent) === undefined
|
|
3800
|
+
? {}
|
|
3801
|
+
: { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
|
|
3802
|
+
...(catalogOperation ? {
|
|
3803
|
+
catalogOperationContext: catalogOperation,
|
|
3804
|
+
expectedGenerationSetDigest: expectedEvidence.catalog.generationSetDigest,
|
|
3805
|
+
} : {}),
|
|
3806
|
+
},
|
|
886
3807
|
awHomeMutationToken,
|
|
3808
|
+
applyTargets,
|
|
3809
|
+
request: { updates: intent.updates, removeKeys: intent.removeKeys },
|
|
3810
|
+
sharedNativeApplyConfirmed: true,
|
|
3811
|
+
preApplyConfirmed: true,
|
|
3812
|
+
preApplyAuthorizedTargets: expectedEvidence.config?.preApplyAuthorization ?? [],
|
|
887
3813
|
});
|
|
888
|
-
return result;
|
|
3814
|
+
return { ...result, operationRebuildResults };
|
|
889
3815
|
},
|
|
890
3816
|
});
|
|
891
3817
|
}
|
|
892
3818
|
throw new TuiOperationBlockedError('operationNotImplemented', '这个写操作尚未接入新 Controller。');
|
|
893
3819
|
},
|
|
3820
|
+
}).finally(async () => {
|
|
3821
|
+
await codexObservationSession?.dispose();
|
|
894
3822
|
});
|
|
895
3823
|
},
|
|
896
3824
|
};
|