@lesliechan721/aw 0.1.0-beta.3 → 0.1.0-beta.30
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 +259 -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
|
@@ -2,33 +2,322 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import fs from 'fs-extra';
|
|
4
4
|
import { generateAgentArtifacts } from '../generators/agents.js';
|
|
5
|
-
import { extractCodexHookGroups } from '../generators/hooks.js';
|
|
6
5
|
import { generateInstructionArtifacts, renderCodexManagedInstructionBlock, } from '../generators/instructions.js';
|
|
7
6
|
import { generateCodexPermissionArtifacts, mergeClaudePermissions, } from '../generators/permissions.js';
|
|
8
7
|
import { buildGeneratedName } from '../generators/shared.js';
|
|
9
|
-
import { applyConditionalCodexConfigTransactionFile, applyCodexConfigTransactionFile, composeManagedDeveloperInstructionsBlock, readCodexConfigFile,
|
|
8
|
+
import { applyConditionalCodexConfigTransactionFile, applyCodexConfigTransactionFile, composeManagedDeveloperInstructionsBlock, readCodexConfigFile, } from '../utils/codex-config.js';
|
|
9
|
+
import { canonicalizePathThroughExistingAncestor, resolveClaudeProjectKey, resolveCodexProjectKey, } from '../utils/state-paths.js';
|
|
10
10
|
import { createCodexSkillExpectedTree, createCodexSkillStagingTreeContentDigest, } from '../utils/codex-skill-staging.js';
|
|
11
|
-
import { createDynamicSkillExpectedTree, createDynamicSkillTreeContentDigest,
|
|
12
|
-
import { parseCodexSharedHooksFile,
|
|
13
|
-
import { buildCodexMarketplaceAddInvocation,
|
|
14
|
-
import { OWNED_TEXT_HEADER_PREFIX, createSharedOwnerToken, createSkillOwnerMarkerContent, parseSharedOwnerToken, parseOwnedTextHeader, readSkillOwnerMarker, inspectOwnedComponents, writeOwnedTextFile, } from '../utils/ownership.js';
|
|
15
|
-
import { resolveArtifactRenderConfig, runArtifactRenderer } from '../utils/artifact-renderer.js';
|
|
16
|
-
import { validateArtifactPlan } from '../utils/artifact-plan.js';
|
|
17
|
-
import {
|
|
11
|
+
import { AW_PLUGIN_REQUIREMENTS_RECEIPT_PATH, createDynamicSkillExpectedTree, createDynamicSkillTreeContentDigest, readDynamicSkillTree, validateSkillDirectory, writeDynamicSkillTree, } from '../utils/dynamic-skill-tree.js';
|
|
12
|
+
import { parseCodexSharedHooksFile, serializeTrackedHookGroupIdentity, } from '../utils/hooks-contract.js';
|
|
13
|
+
import { buildCodexMarketplaceAddInvocation, buildCodexMarketplaceRemoveInvocation, buildCodexPluginAddInvocation, buildCodexPluginRemoveInvocation, CodexProjectConfigNotEffectiveError, runNativeCliChecked, SYSTEM_CODEX_MANAGER, } from '../utils/native-cli.js';
|
|
14
|
+
import { OWNED_TEXT_HEADER_PREFIX, createSharedOwnerToken, createSkillOwnerMarkerContent, parseSharedOwnerToken, parseOwnedTextHeader, readSkillOwnerMarker, renderOwnedTextFile, inspectOwnedComponents, writeOwnedTextFile, } from '../utils/ownership.js';
|
|
15
|
+
import { resolveArtifactRenderConfig, resolveArtifactRendererTarget, runArtifactRenderer, } from '../utils/artifact-renderer.js';
|
|
16
|
+
import { createArtifactConfigDigest, validateArtifactPlan, } from '../utils/artifact-plan.js';
|
|
17
|
+
import { resolveAwDeliveredArtifactRequestBatches } from '../utils/artifact-request.js';
|
|
18
|
+
import { createAwPluginRequirementsReceipt, parseAwPluginRequirementsReceipt, tryParseStrictSemVer, } from '../utils/manifest.js';
|
|
18
19
|
import { resolvePlatformEntries } from '../utils/platform-entries.js';
|
|
19
20
|
import { resolveAwHome, resolveClaudePaths, resolveCodexPaths, resolveRepoRoot, getCodexHome, } from '../utils/paths.js';
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
21
|
+
import { assertNoSensitiveConfigInFiles, collectSensitivePluginConfigValues, runPreApplyScript, } from '../utils/plugin-scripts.js';
|
|
22
|
+
import { validateRuntimeSkillClosure } from '../utils/runtime-skill-closure.js';
|
|
23
|
+
import { findSensitiveTokenPath } from '../utils/sensitive-content.js';
|
|
24
|
+
import { atomicRemoveConfigFile, atomicReplaceConfigFile, withConfigFileMutation, } from '../utils/plugin-config.js';
|
|
23
25
|
import { readCatalogSourcesConfigSnapshot } from '../utils/catalog-sources.js';
|
|
24
|
-
import { getNamespacedMarketplaceName, projectMarketplaceManifestForPlatform, replayNamespacedCodexSkillOverlays, } from '../utils/local-marketplace.js';
|
|
25
|
-
import {
|
|
26
|
+
import { getNamespacedMarketplaceName, MarketplaceRootValidationError, projectMarketplaceManifestForPlatform, readStrictNamespacedMarketplaceRoot, replayNamespacedCodexNativeRenderProjections, replayNamespacedCodexSkillOverlays, } from '../utils/local-marketplace.js';
|
|
27
|
+
import { commitMarketplaceGenerationBackup } from '../utils/orphan-marketplace-cleanup.js';
|
|
26
28
|
import { readClaudeSettings, writeClaudeSettings } from '../adapters/helpers.js';
|
|
27
|
-
import {
|
|
29
|
+
import { analyzeCodexSurface } from '../utils/codex-surface-analysis.js';
|
|
30
|
+
import { CODEX_NATIVE_RENDER_RECEIPT_PATH, codexNativeRenderProjectionsEqual, createCodexNativeRenderProjection, createCodexNativeRenderReceipt, parseCodexNativeRenderReceipt, serializeCodexNativeRenderReceipt, } from '../utils/codex-native-render-receipt.js';
|
|
31
|
+
import { inspectCodexSharedNative, } from '../utils/codex-shared-native.js';
|
|
32
|
+
import { calculateCodexNativeRenderPayloadDigest } from '../utils/catalog-digests.js';
|
|
33
|
+
import { createCodexNativeObservationSession, } from '../utils/codex-native-observation.js';
|
|
34
|
+
import { isSafeCodexMarketplaceRoot } from '../utils/codex-marketplace-path.js';
|
|
28
35
|
import { assertCanonicalProjectManagedRoots } from '../utils/project-boundary.js';
|
|
29
36
|
import { findCodexRoleNameConflict, resolveCodexRoleNamespace, } from '../utils/codex-roles.js';
|
|
30
37
|
const MARKETPLACE_NAME = 'aw';
|
|
31
|
-
const
|
|
38
|
+
const LEGACY_MCP_OWNER_ENV = 'AW_AGENT_WORKFLOWS_OWNER';
|
|
39
|
+
function requirementsReceipt(plugin) {
|
|
40
|
+
const receipt = createAwPluginRequirementsReceipt({ name: plugin.manifest.name, version: plugin.manifest.version }, plugin.manifest.requires?.aw.capabilities ?? []);
|
|
41
|
+
return Buffer.from(`${JSON.stringify(receipt, null, 2)}\n`, 'utf8');
|
|
42
|
+
}
|
|
43
|
+
function withoutVerifiedRequirementsReceipt(plugin, files) {
|
|
44
|
+
const receipt = files[AW_PLUGIN_REQUIREMENTS_RECEIPT_PATH];
|
|
45
|
+
if (!receipt)
|
|
46
|
+
return { ...files };
|
|
47
|
+
let actual;
|
|
48
|
+
let expected;
|
|
49
|
+
try {
|
|
50
|
+
actual = parseAwPluginRequirementsReceipt(JSON.parse(receipt.toString('utf8')));
|
|
51
|
+
expected = parseAwPluginRequirementsReceipt(JSON.parse(requirementsReceipt(plugin).toString('utf8')));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return { ...files };
|
|
55
|
+
}
|
|
56
|
+
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
|
57
|
+
return { ...files };
|
|
58
|
+
}
|
|
59
|
+
const output = { ...files };
|
|
60
|
+
delete output[AW_PLUGIN_REQUIREMENTS_RECEIPT_PATH];
|
|
61
|
+
return output;
|
|
62
|
+
}
|
|
63
|
+
export class CodexActivationPostcheckError extends Error {
|
|
64
|
+
code = 'codexActivationPostcheckFailed';
|
|
65
|
+
codexPluginIds;
|
|
66
|
+
constructor(codexPluginIds, detail) {
|
|
67
|
+
super(`codexActivationPostcheckFailed: ${detail}`);
|
|
68
|
+
this.name = 'CodexActivationPostcheckError';
|
|
69
|
+
this.codexPluginIds = [...new Set(typeof codexPluginIds === 'string' ? [codexPluginIds] : codexPluginIds)];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function codexObservationScope(context, marketplaceName, fallbackScope) {
|
|
73
|
+
return context.codexObservationScopeByMarketplace?.get(marketplaceName)
|
|
74
|
+
?? (fallbackScope === 'user' ? 'user' : 'both');
|
|
75
|
+
}
|
|
76
|
+
function codexMarketplaceFromPluginId(pluginId) {
|
|
77
|
+
return pluginId.slice(pluginId.lastIndexOf('@') + 1);
|
|
78
|
+
}
|
|
79
|
+
function requireCodexObservationCoverage(registration, scope, operation) {
|
|
80
|
+
if (!registration.observations.user
|
|
81
|
+
|| (scope === 'both' && !registration.observations.project)) {
|
|
82
|
+
throw new Error(`codexNativeObservationUnavailable: ${operation} requires ${scope === 'user' ? 'neutral' : 'exact and neutral'} Codex plugin list evidence.`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function requireCodexManager(manager) {
|
|
86
|
+
if (!manager) {
|
|
87
|
+
throw new Error('codexManagerRequired: Codex native operation has no resolved manager.');
|
|
88
|
+
}
|
|
89
|
+
return manager;
|
|
90
|
+
}
|
|
91
|
+
async function runCodexMutationWithInvalidation(options) {
|
|
92
|
+
try {
|
|
93
|
+
return await options.mutation();
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
options.observationSession?.invalidate(options.marketplaceName);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async function executeCodexReconcileActivationsInSession(options) {
|
|
100
|
+
const byScope = {
|
|
101
|
+
user: { upserts: {}, removals: new Set() },
|
|
102
|
+
project: { upserts: {}, removals: new Set() },
|
|
103
|
+
};
|
|
104
|
+
for (const plan of options.plans) {
|
|
105
|
+
const rewrittenPluginIds = new Set(plan.sharedNativeActions
|
|
106
|
+
.filter((action) => action.action === 'install'
|
|
107
|
+
|| action.action === 'replace'
|
|
108
|
+
|| action.action === 'repair')
|
|
109
|
+
.map((action) => action.codexPluginId));
|
|
110
|
+
for (const scope of ['user', 'project']) {
|
|
111
|
+
for (const action of plan.activationActions[scope]) {
|
|
112
|
+
if (action.action === 'enable' || action.action === 'disable') {
|
|
113
|
+
byScope[scope].upserts[action.codexPluginId] = action.desired;
|
|
114
|
+
byScope[scope].removals.delete(action.codexPluginId);
|
|
115
|
+
}
|
|
116
|
+
else if (action.action === 'remove') {
|
|
117
|
+
byScope[scope].removals.add(action.codexPluginId);
|
|
118
|
+
}
|
|
119
|
+
else if (rewrittenPluginIds.has(action.codexPluginId)
|
|
120
|
+
&& (action.desired || scope === 'user')) {
|
|
121
|
+
byScope[scope].upserts[action.codexPluginId] = action.desired;
|
|
122
|
+
byScope[scope].removals.delete(action.codexPluginId);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for (const removal of plan.staleActivationRemovals) {
|
|
127
|
+
if (!Object.hasOwn(byScope[removal.scope].upserts, removal.codexPluginId)) {
|
|
128
|
+
byScope[removal.scope].removals.add(removal.codexPluginId);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const recordActivationMutations = (pluginIds) => {
|
|
133
|
+
if (!options.mutationMarketplaces)
|
|
134
|
+
return;
|
|
135
|
+
for (const pluginId of pluginIds) {
|
|
136
|
+
options.mutationMarketplaces.add(pluginId.slice(pluginId.lastIndexOf('@') + 1));
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
const observedActivationPluginIds = [...new Set(options.plans.flatMap((plan) => plan.postcheck.support === 'observed'
|
|
140
|
+
? plan.postcheck.plugins.map((plugin) => plugin.codexPluginId)
|
|
141
|
+
: []))];
|
|
142
|
+
if (!options.preflightCompleted && observedActivationPluginIds.length > 0) {
|
|
143
|
+
const manager = requireCodexManager(options.manager);
|
|
144
|
+
for (const pluginId of observedActivationPluginIds) {
|
|
145
|
+
const marketplaceName = codexMarketplaceFromPluginId(pluginId);
|
|
146
|
+
const observationScope = options.observationScopeByMarketplace?.get(marketplaceName)
|
|
147
|
+
?? (options.plans.some((plan) => plan.requirement.requiresProjectPluginContract)
|
|
148
|
+
? 'both'
|
|
149
|
+
: 'user');
|
|
150
|
+
const registration = await inspectCodexPluginRegistrationLive({
|
|
151
|
+
pluginId,
|
|
152
|
+
cwd: options.cwd,
|
|
153
|
+
env: options.env,
|
|
154
|
+
nativeCliRunner: options.runner,
|
|
155
|
+
manager,
|
|
156
|
+
scope: observationScope,
|
|
157
|
+
observationSession: options.observationSession,
|
|
158
|
+
});
|
|
159
|
+
const observedState = requireObservedCodexNativeState(registration.observation, 'activation precheck');
|
|
160
|
+
requireCodexObservationCoverage(registration, observationScope, 'activation precheck');
|
|
161
|
+
if (observedState !== 'installed') {
|
|
162
|
+
throw new Error('codexNativeObservationFailed: activation precheck did not observe the installed Codex plugin.');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
for (const scope of ['user', 'project']) {
|
|
167
|
+
const enables = Object.fromEntries(Object.entries(byScope[scope].upserts)
|
|
168
|
+
.filter(([, desired]) => desired));
|
|
169
|
+
if (Object.keys(enables).length === 0)
|
|
170
|
+
continue;
|
|
171
|
+
const configPath = resolveCodexPaths(options.cwd, scope, options.env).configTomlPath;
|
|
172
|
+
const update = await applyCodexConfigTransactionFile(configPath, { upsertPluginActivations: enables });
|
|
173
|
+
if (update.wrote)
|
|
174
|
+
recordActivationMutations(Object.keys(enables));
|
|
175
|
+
const enabledConfig = await readCodexConfigFile(configPath);
|
|
176
|
+
const missingEnables = Object.keys(enables).filter((pluginId) => enabledConfig.snapshot.plugins[pluginId]?.enabled !== true);
|
|
177
|
+
if (missingEnables.length > 0) {
|
|
178
|
+
throw new CodexActivationPostcheckError(missingEnables, `${missingEnables.join(', ')} ${scope} activation was not enabled before obsolete activation removal.`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
for (const scope of ['user', 'project']) {
|
|
182
|
+
const transaction = byScope[scope];
|
|
183
|
+
const disables = Object.fromEntries(Object.entries(transaction.upserts)
|
|
184
|
+
.filter(([, desired]) => !desired));
|
|
185
|
+
if (Object.keys(disables).length === 0 && transaction.removals.size === 0)
|
|
186
|
+
continue;
|
|
187
|
+
const update = await applyCodexConfigTransactionFile(resolveCodexPaths(options.cwd, scope, options.env).configTomlPath, {
|
|
188
|
+
...(transaction.removals.size > 0
|
|
189
|
+
? { removePluginActivations: [...transaction.removals].sort() }
|
|
190
|
+
: {}),
|
|
191
|
+
...(Object.keys(disables).length > 0
|
|
192
|
+
? { upsertPluginActivations: disables }
|
|
193
|
+
: {}),
|
|
194
|
+
});
|
|
195
|
+
if (update.wrote) {
|
|
196
|
+
recordActivationMutations([
|
|
197
|
+
...Object.keys(disables),
|
|
198
|
+
...transaction.removals,
|
|
199
|
+
]);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const [userConfig, projectConfig] = await Promise.all([
|
|
203
|
+
readCodexConfigFile(resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath),
|
|
204
|
+
readCodexConfigFile(resolveCodexPaths(options.cwd, 'project', options.env).configTomlPath),
|
|
205
|
+
]);
|
|
206
|
+
const configMismatchIds = [];
|
|
207
|
+
const configMismatchDetails = [];
|
|
208
|
+
for (const plan of options.plans) {
|
|
209
|
+
for (const scope of ['user', 'project']) {
|
|
210
|
+
const snapshot = scope === 'user' ? userConfig.snapshot : projectConfig.snapshot;
|
|
211
|
+
for (const action of plan.activationActions[scope]) {
|
|
212
|
+
const actual = snapshot.plugins[action.codexPluginId]?.enabled;
|
|
213
|
+
const expected = action.desired
|
|
214
|
+
? true
|
|
215
|
+
: action.action === 'disable' || actual === false ? false : undefined;
|
|
216
|
+
if (actual !== expected) {
|
|
217
|
+
configMismatchIds.push(action.codexPluginId);
|
|
218
|
+
configMismatchDetails.push(`${action.codexPluginId} ${scope} activation is ${String(actual)}; expected ${String(expected)}.`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
for (const removal of plan.staleActivationRemovals.filter((entry) => entry.scope === scope)) {
|
|
222
|
+
if (snapshot.plugins[removal.codexPluginId]?.enabled !== undefined) {
|
|
223
|
+
configMismatchIds.push(removal.codexPluginId);
|
|
224
|
+
configMismatchDetails.push(`stale ${removal.codexPluginId} ${scope} activation remains.`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (configMismatchIds.length > 0) {
|
|
230
|
+
throw new CodexActivationPostcheckError(configMismatchIds, configMismatchDetails.join(' '));
|
|
231
|
+
}
|
|
232
|
+
const observedActivationChecks = options.plans.flatMap((plan) => plan.postcheck.support === 'observed'
|
|
233
|
+
? plan.postcheck.plugins
|
|
234
|
+
: []);
|
|
235
|
+
if (observedActivationChecks.length === 0)
|
|
236
|
+
return;
|
|
237
|
+
const ineffectivePlugins = [];
|
|
238
|
+
const ineffectiveDetails = [];
|
|
239
|
+
for (const plugin of observedActivationChecks) {
|
|
240
|
+
const userActive = userConfig.snapshot.plugins[plugin.codexPluginId]?.enabled === true;
|
|
241
|
+
const projectActive = projectConfig.snapshot.plugins[plugin.codexPluginId]?.enabled === true;
|
|
242
|
+
if ((userActive || projectActive) !== (plugin.expectedUserEnabled || plugin.expectedProjectEnabled)
|
|
243
|
+
|| userActive !== plugin.expectedUserEnabled) {
|
|
244
|
+
ineffectivePlugins.push(plugin.codexPluginId);
|
|
245
|
+
ineffectiveDetails.push(new CodexProjectConfigNotEffectiveError(plugin.codexPluginId).message);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (ineffectivePlugins.length > 0) {
|
|
249
|
+
throw new CodexActivationPostcheckError(ineffectivePlugins, ineffectiveDetails.join(' '));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
export async function executeCodexReconcileActivations(options) {
|
|
253
|
+
const ownedSession = options.observationSession ? undefined
|
|
254
|
+
: await createCodexNativeObservationSession({
|
|
255
|
+
exactCwd: options.cwd,
|
|
256
|
+
env: options.env,
|
|
257
|
+
runner: options.runner,
|
|
258
|
+
manager: options.manager,
|
|
259
|
+
});
|
|
260
|
+
try {
|
|
261
|
+
await executeCodexReconcileActivationsInSession({
|
|
262
|
+
...options,
|
|
263
|
+
observationSession: options.observationSession ?? ownedSession,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
finally {
|
|
267
|
+
await ownedSession?.dispose();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
export async function postcheckCodexReconcileActivations(options) {
|
|
271
|
+
const checks = options.plans.flatMap((plan) => plan.postcheck.support === 'observed' ? plan.postcheck.plugins : []);
|
|
272
|
+
if (checks.length === 0)
|
|
273
|
+
return;
|
|
274
|
+
const ownedSession = options.observationSession ? undefined
|
|
275
|
+
: await createCodexNativeObservationSession({
|
|
276
|
+
exactCwd: options.cwd,
|
|
277
|
+
env: options.env,
|
|
278
|
+
runner: options.runner,
|
|
279
|
+
manager: options.manager,
|
|
280
|
+
});
|
|
281
|
+
const session = options.observationSession ?? ownedSession;
|
|
282
|
+
try {
|
|
283
|
+
const failedPluginIds = [];
|
|
284
|
+
const failureDetails = [];
|
|
285
|
+
for (const check of checks) {
|
|
286
|
+
try {
|
|
287
|
+
const marketplaceName = codexMarketplaceFromPluginId(check.codexPluginId);
|
|
288
|
+
const observationScope = options.observationScopeByMarketplace?.get(marketplaceName)
|
|
289
|
+
?? (options.plans.some((plan) => plan.requirement.requiresProjectPluginContract)
|
|
290
|
+
? 'both'
|
|
291
|
+
: 'user');
|
|
292
|
+
const registration = await inspectCodexPluginRegistrationLive({
|
|
293
|
+
pluginId: check.codexPluginId,
|
|
294
|
+
cwd: options.cwd,
|
|
295
|
+
env: options.env,
|
|
296
|
+
nativeCliRunner: options.runner,
|
|
297
|
+
manager: options.manager,
|
|
298
|
+
scope: observationScope,
|
|
299
|
+
observationSession: session,
|
|
300
|
+
});
|
|
301
|
+
const state = requireObservedCodexNativeState(registration.observation, 'activation postcheck');
|
|
302
|
+
requireCodexObservationCoverage(registration, observationScope, 'activation postcheck');
|
|
303
|
+
if (state !== 'installed') {
|
|
304
|
+
throw new Error(`codexNativeObservationFailed: ${check.codexPluginId} was not observed after Codex activation mutation.`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
failedPluginIds.push(check.codexPluginId);
|
|
309
|
+
failureDetails.push(error instanceof Error ? error.message : String(error));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (failedPluginIds.length > 0) {
|
|
313
|
+
throw new CodexActivationPostcheckError(failedPluginIds, failureDetails.join(' '));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
finally {
|
|
317
|
+
await ownedSession?.dispose();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const CODEX_REMOVE_RECOVERY = 'Codex residue was retained. Retry cleanup or uninstall, upgrade AW, or install a compatible official Codex CLI.';
|
|
32
321
|
function ownerIdentity(plugin, artifactKey, platform, scope) {
|
|
33
322
|
return { owner: 'agent-workflows', plugin, artifactKey, platform, scope };
|
|
34
323
|
}
|
|
@@ -37,7 +326,6 @@ function pluginOwnerIdentity(plugin, artifactKey, platform, scope) {
|
|
|
37
326
|
...ownerIdentity(plugin.manifest.name, artifactKey, platform, scope),
|
|
38
327
|
...(plugin.sourceId ? {
|
|
39
328
|
sourceId: plugin.sourceId,
|
|
40
|
-
sourceIdentity: plugin.sourceIdentity,
|
|
41
329
|
pluginId: plugin.pluginId,
|
|
42
330
|
} : {}),
|
|
43
331
|
};
|
|
@@ -47,7 +335,6 @@ function bindNamespacedOwnerIdentity(components, source) {
|
|
|
47
335
|
return;
|
|
48
336
|
for (const component of components) {
|
|
49
337
|
component.identity.sourceId = source.sourceId;
|
|
50
|
-
component.identity.sourceIdentity = source.sourceIdentity;
|
|
51
338
|
component.identity.pluginId = source.pluginId;
|
|
52
339
|
}
|
|
53
340
|
}
|
|
@@ -59,11 +346,76 @@ function pathComponent(options) {
|
|
|
59
346
|
return options;
|
|
60
347
|
return {
|
|
61
348
|
...options,
|
|
62
|
-
validateHealth:
|
|
63
|
-
|
|
349
|
+
validateHealth: true,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
function pristineTreeDescriptor(tree) {
|
|
353
|
+
return Object.entries(tree.files)
|
|
354
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
355
|
+
.map(([relativePath, content]) => ({
|
|
356
|
+
relativePath,
|
|
357
|
+
contentDigest: `sha256:${crypto.createHash('sha256').update(content).digest('hex')}`,
|
|
358
|
+
}));
|
|
359
|
+
}
|
|
360
|
+
function pluginActionId(plugin) {
|
|
361
|
+
return plugin.pluginId ?? plugin.manifest.name;
|
|
362
|
+
}
|
|
363
|
+
function skillWriteAction(options) {
|
|
364
|
+
const files = [
|
|
365
|
+
...Object.entries(options.tree.files).map(([relativePath, content]) => ({
|
|
366
|
+
relativePath,
|
|
367
|
+
contentBase64: content.toString('base64'),
|
|
368
|
+
contentDigest: `sha256:${crypto.createHash('sha256').update(content).digest('hex')}`,
|
|
369
|
+
})),
|
|
370
|
+
{
|
|
371
|
+
relativePath: '.agent-workflows-owner.json',
|
|
372
|
+
contentBase64: Buffer.from(createSkillOwnerMarkerContent(options.identity)).toString('base64'),
|
|
373
|
+
contentDigest: `sha256:${crypto.createHash('sha256')
|
|
374
|
+
.update(createSkillOwnerMarkerContent(options.identity)).digest('hex')}`,
|
|
64
375
|
},
|
|
376
|
+
].sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
377
|
+
const contentDigest = `sha256:${crypto.createHash('sha256').update(JSON.stringify(files)).digest('hex')}`;
|
|
378
|
+
return {
|
|
379
|
+
kind: 'writeOwnedArtifact',
|
|
380
|
+
pluginId: pluginActionId(options.plugin),
|
|
381
|
+
platform: options.platform,
|
|
382
|
+
scope: options.scope,
|
|
383
|
+
path: options.path,
|
|
384
|
+
allowedRoot: options.allowedRoot,
|
|
385
|
+
owner: options.identity,
|
|
386
|
+
artifactKind: 'skill-directory',
|
|
387
|
+
files,
|
|
388
|
+
contentDigest,
|
|
389
|
+
allowPristineUnowned: options.allowPristineUnowned,
|
|
65
390
|
};
|
|
66
391
|
}
|
|
392
|
+
function finalizeOwnedArtifactActions(actions) {
|
|
393
|
+
for (const action of actions) {
|
|
394
|
+
if (action.kind !== 'writeOwnedArtifact' || !action.owner)
|
|
395
|
+
continue;
|
|
396
|
+
if (action.artifactKind === 'owned-text-file') {
|
|
397
|
+
if (action.sourceContent === undefined)
|
|
398
|
+
throw new Error('Owned text action is missing source content.');
|
|
399
|
+
action.content = renderOwnedTextFile(action.sourceContent, action.owner);
|
|
400
|
+
action.contentDigest = `sha256:${crypto.createHash('sha256').update(action.content).digest('hex')}`;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (action.artifactKind === 'skill-directory') {
|
|
404
|
+
const markerContent = createSkillOwnerMarkerContent(action.owner);
|
|
405
|
+
const marker = {
|
|
406
|
+
relativePath: '.agent-workflows-owner.json',
|
|
407
|
+
contentBase64: Buffer.from(markerContent).toString('base64'),
|
|
408
|
+
contentDigest: `sha256:${crypto.createHash('sha256').update(markerContent).digest('hex')}`,
|
|
409
|
+
};
|
|
410
|
+
action.files = [
|
|
411
|
+
...(action.files ?? []).filter((file) => file.relativePath !== marker.relativePath),
|
|
412
|
+
marker,
|
|
413
|
+
].sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
414
|
+
action.contentDigest = `sha256:${crypto.createHash('sha256')
|
|
415
|
+
.update(JSON.stringify(action.files)).digest('hex')}`;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
67
419
|
function generatedTextWrite(options) {
|
|
68
420
|
const artifactPath = path.join(options.configDir, options.relativePath);
|
|
69
421
|
const identity = ownerIdentity(options.plugin.manifest.name, options.artifactKey, options.platform, options.scope);
|
|
@@ -73,6 +425,7 @@ function generatedTextWrite(options) {
|
|
|
73
425
|
allowedRoot: options.configDir,
|
|
74
426
|
identity,
|
|
75
427
|
});
|
|
428
|
+
const content = options.content;
|
|
76
429
|
return {
|
|
77
430
|
artifact: {
|
|
78
431
|
path: artifactPath,
|
|
@@ -81,14 +434,18 @@ function generatedTextWrite(options) {
|
|
|
81
434
|
artifactKey: options.artifactKey,
|
|
82
435
|
},
|
|
83
436
|
component,
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
437
|
+
action: {
|
|
438
|
+
kind: 'writeOwnedArtifact',
|
|
439
|
+
pluginId: pluginActionId(options.plugin),
|
|
440
|
+
platform: options.platform,
|
|
441
|
+
scope: options.scope,
|
|
442
|
+
path: artifactPath,
|
|
443
|
+
allowedRoot: options.configDir,
|
|
444
|
+
owner: identity,
|
|
445
|
+
artifactKind: 'owned-text-file',
|
|
446
|
+
sourceContent: content,
|
|
447
|
+
content,
|
|
448
|
+
contentDigest: `sha256:${crypto.createHash('sha256').update(content).digest('hex')}`,
|
|
92
449
|
},
|
|
93
450
|
};
|
|
94
451
|
}
|
|
@@ -162,15 +519,7 @@ function createClaudePermissionsComponent(options) {
|
|
|
162
519
|
kind: 'shared-config',
|
|
163
520
|
identity: options.identity,
|
|
164
521
|
label: `${options.settingsPath}#awGenerated.permissions.${ownerKey}`,
|
|
165
|
-
|
|
166
|
-
const settings = await readClaudeSettings(options.settingsPath);
|
|
167
|
-
return settings.awGenerated?.permissions?.[ownerKey] ? 'present' : 'absent';
|
|
168
|
-
},
|
|
169
|
-
remove: async () => {
|
|
170
|
-
const settings = await readClaudeSettings(options.settingsPath);
|
|
171
|
-
rebuildClaudePermissions(settings, ownerKey);
|
|
172
|
-
await writeClaudeSettings(options.settingsPath, settings);
|
|
173
|
-
},
|
|
522
|
+
locator: { kind: 'claude-permissions', settingsPath: options.settingsPath, ownerKey },
|
|
174
523
|
};
|
|
175
524
|
}
|
|
176
525
|
function createCodexInstructionComponent(options) {
|
|
@@ -179,97 +528,65 @@ function createCodexInstructionComponent(options) {
|
|
|
179
528
|
kind: 'managed-block',
|
|
180
529
|
identity: options.identity,
|
|
181
530
|
label: `${options.configPath}#developer_instructions:${ownerKey}`,
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
531
|
+
locator: { kind: 'codex-instruction', configPath: options.configPath, ownerKey },
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
function createCodexActivationComponent(options) {
|
|
535
|
+
return {
|
|
536
|
+
kind: 'shared-config',
|
|
537
|
+
identity: options.identity,
|
|
538
|
+
label: `${options.configPath}#plugins.${options.codexPluginId}.enabled`,
|
|
539
|
+
locator: {
|
|
540
|
+
kind: 'codex-activation',
|
|
541
|
+
configPath: options.configPath,
|
|
542
|
+
codexPluginId: options.codexPluginId,
|
|
543
|
+
desired: options.desired,
|
|
187
544
|
},
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
function createCodexActivationCleanupComponent(options) {
|
|
548
|
+
return {
|
|
549
|
+
kind: 'shared-config',
|
|
550
|
+
identity: options.identity,
|
|
551
|
+
label: `${options.configPath}#plugins.${options.codexPluginId}.enabled`,
|
|
552
|
+
locator: {
|
|
553
|
+
kind: 'codex-activation',
|
|
554
|
+
configPath: options.configPath,
|
|
555
|
+
codexPluginId: options.codexPluginId,
|
|
556
|
+
desired: options.disposition === 'remove' ? false : true,
|
|
557
|
+
disposition: options.disposition,
|
|
192
558
|
},
|
|
193
559
|
};
|
|
194
560
|
}
|
|
195
|
-
function
|
|
561
|
+
function legacyCodexMcpOwner(actual) {
|
|
196
562
|
const envValue = actual?.value?.env;
|
|
197
563
|
return envValue
|
|
198
564
|
&& typeof envValue === 'object'
|
|
199
565
|
&& !Array.isArray(envValue)
|
|
200
|
-
&& typeof envValue[
|
|
201
|
-
? envValue[
|
|
566
|
+
&& typeof envValue[LEGACY_MCP_OWNER_ENV] === 'string'
|
|
567
|
+
? envValue[LEGACY_MCP_OWNER_ENV]
|
|
202
568
|
: null;
|
|
203
569
|
}
|
|
204
|
-
function
|
|
570
|
+
function createLegacyCodexMcpCleanupComponent(options) {
|
|
205
571
|
const ownerKey = createSharedOwnerToken(options.identity);
|
|
206
572
|
return {
|
|
207
573
|
kind: 'shared-config',
|
|
208
574
|
identity: options.identity,
|
|
209
575
|
label: `${options.configPath}#mcp_servers.${options.serverKey}`,
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
return codexMcpOwner(actual) === ownerKey ? 'present' : 'foreign';
|
|
216
|
-
},
|
|
217
|
-
remove: async () => {
|
|
218
|
-
await applyConditionalCodexConfigTransactionFile(options.configPath, (snapshot) => {
|
|
219
|
-
const actual = snapshot.mcpServers[options.serverKey];
|
|
220
|
-
return codexMcpOwner(actual) === ownerKey
|
|
221
|
-
? { removeMcpServers: [options.serverKey] }
|
|
222
|
-
: null;
|
|
223
|
-
});
|
|
224
|
-
},
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
async function codexRoleRegistrationOwned(options) {
|
|
228
|
-
if (!options.registration?.configFile)
|
|
229
|
-
return false;
|
|
230
|
-
const expected = path.normalize(path.relative(path.dirname(options.configPath), options.rolePath));
|
|
231
|
-
const actual = path.normalize(path.relative(path.dirname(options.configPath), path.resolve(path.dirname(options.configPath), options.registration.configFile)));
|
|
232
|
-
if (actual !== expected)
|
|
233
|
-
return false;
|
|
234
|
-
const raw = await fs.readFile(options.rolePath, 'utf8').catch(() => '');
|
|
235
|
-
try {
|
|
236
|
-
return Boolean(parseOwnedTextHeader(raw, options.fileIdentity));
|
|
237
|
-
}
|
|
238
|
-
catch {
|
|
239
|
-
return false;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
function createCodexRoleRegistrationComponent(options) {
|
|
243
|
-
const inspect = async () => {
|
|
244
|
-
const config = await readCodexConfigFile(options.configPath);
|
|
245
|
-
const registration = config.snapshot.agents[options.roleKey];
|
|
246
|
-
if (!registration)
|
|
247
|
-
return 'absent';
|
|
248
|
-
return await codexRoleRegistrationOwned({ ...options, registration })
|
|
249
|
-
? 'present'
|
|
250
|
-
: 'foreign';
|
|
251
|
-
};
|
|
252
|
-
return {
|
|
253
|
-
kind: 'shared-config',
|
|
254
|
-
identity: options.identity,
|
|
255
|
-
label: `${options.configPath}#agents.${options.roleKey}`,
|
|
256
|
-
inspect,
|
|
257
|
-
remove: async () => {
|
|
258
|
-
await applyConditionalCodexConfigTransactionFile(options.configPath, async (snapshot) => {
|
|
259
|
-
const registration = snapshot.agents[options.roleKey];
|
|
260
|
-
return await codexRoleRegistrationOwned({ ...options, registration })
|
|
261
|
-
? { removeAgentRegistrations: [options.roleKey] }
|
|
262
|
-
: null;
|
|
263
|
-
});
|
|
576
|
+
locator: {
|
|
577
|
+
kind: 'legacy-codex-mcp',
|
|
578
|
+
configPath: options.configPath,
|
|
579
|
+
serverKey: options.serverKey,
|
|
580
|
+
ownerKey,
|
|
264
581
|
},
|
|
265
582
|
};
|
|
266
583
|
}
|
|
267
|
-
async function
|
|
584
|
+
async function readLegacyCodexHooks(hooksPath) {
|
|
268
585
|
if (!await fs.pathExists(hooksPath))
|
|
269
586
|
return parseCodexSharedHooksFile({ hooks: {} });
|
|
270
587
|
return parseCodexSharedHooksFile(await fs.readJson(hooksPath));
|
|
271
588
|
}
|
|
272
|
-
function
|
|
589
|
+
function renderLegacyCodexHookGroups(groups) {
|
|
273
590
|
const hooks = {};
|
|
274
591
|
for (const group of groups) {
|
|
275
592
|
hooks[group.event] ??= [];
|
|
@@ -277,16 +594,15 @@ function renderCodexHookGroups(groups) {
|
|
|
277
594
|
}
|
|
278
595
|
return `${JSON.stringify({ hooks }, null, 2)}\n`;
|
|
279
596
|
}
|
|
280
|
-
|
|
281
|
-
return
|
|
597
|
+
async function applyLegacyCodexHookCleanupTransactionFile(hooksPath, update) {
|
|
598
|
+
return withConfigFileMutation({
|
|
282
599
|
filePath: hooksPath,
|
|
283
600
|
allowedRoot: path.dirname(hooksPath),
|
|
284
601
|
action: async () => {
|
|
285
|
-
const parsed = await
|
|
602
|
+
const parsed = await readLegacyCodexHooks(hooksPath);
|
|
286
603
|
const groups = update(parsed);
|
|
287
604
|
if (!groups)
|
|
288
605
|
return false;
|
|
289
|
-
await testHooks?.beforeCommit?.();
|
|
290
606
|
if (groups.length === 0) {
|
|
291
607
|
await atomicRemoveConfigFile({
|
|
292
608
|
filePath: hooksPath,
|
|
@@ -297,91 +613,64 @@ export async function applyCodexHookGroupsTransactionFile(hooksPath, update, tes
|
|
|
297
613
|
await atomicReplaceConfigFile({
|
|
298
614
|
filePath: hooksPath,
|
|
299
615
|
allowedRoot: path.dirname(hooksPath),
|
|
300
|
-
content:
|
|
616
|
+
content: renderLegacyCodexHookGroups(groups),
|
|
301
617
|
});
|
|
302
618
|
}
|
|
303
619
|
return true;
|
|
304
620
|
},
|
|
305
621
|
});
|
|
306
622
|
}
|
|
307
|
-
function
|
|
308
|
-
const rendered = renderTrackedHookGroupIdentity(identity);
|
|
309
|
-
const rawGroup = {
|
|
310
|
-
...(rendered.matcher === undefined ? {} : { matcher: rendered.matcher }),
|
|
311
|
-
hooks: rendered.hooks,
|
|
312
|
-
};
|
|
313
|
-
return {
|
|
314
|
-
event: rendered.event,
|
|
315
|
-
matcher: rendered.matcher,
|
|
316
|
-
hooks: rendered.hooks,
|
|
317
|
-
rawGroup,
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
function createCodexHookComponent(options) {
|
|
623
|
+
function createLegacyCodexHookCleanupComponent(options) {
|
|
321
624
|
const serialized = serializeTrackedHookGroupIdentity(options.identity);
|
|
322
625
|
return {
|
|
323
626
|
kind: 'shared-config',
|
|
324
627
|
identity: ownerIdentity(options.pluginName, internalArtifactKey('hook', serialized), 'codex', options.scope),
|
|
325
628
|
label: `${options.hooksPath}#${serialized}`,
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
: 'absent';
|
|
331
|
-
},
|
|
332
|
-
remove: async () => {
|
|
333
|
-
await applyCodexHookGroupsTransactionFile(options.hooksPath, (parsed) => {
|
|
334
|
-
let removed = false;
|
|
335
|
-
const remaining = parsed.groups.flatMap((entry) => {
|
|
336
|
-
if (!removed
|
|
337
|
-
&& entry.managedIdentity
|
|
338
|
-
&& serializeTrackedHookGroupIdentity(entry.managedIdentity) === serialized) {
|
|
339
|
-
removed = true;
|
|
340
|
-
return [];
|
|
341
|
-
}
|
|
342
|
-
return [entry.rawGroup];
|
|
343
|
-
});
|
|
344
|
-
return removed ? remaining : null;
|
|
345
|
-
});
|
|
629
|
+
locator: {
|
|
630
|
+
kind: 'legacy-codex-hook',
|
|
631
|
+
hooksPath: options.hooksPath,
|
|
632
|
+
serializedIdentity: serialized,
|
|
346
633
|
},
|
|
347
634
|
};
|
|
348
635
|
}
|
|
349
|
-
function
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
636
|
+
async function codexRoleRegistrationOwned(options) {
|
|
637
|
+
if (!options.registration?.configFile)
|
|
638
|
+
return false;
|
|
639
|
+
const expected = path.normalize(path.relative(path.dirname(options.configPath), options.rolePath));
|
|
640
|
+
const actual = path.normalize(path.relative(path.dirname(options.configPath), path.resolve(path.dirname(options.configPath), options.registration.configFile)));
|
|
641
|
+
if (actual !== expected)
|
|
642
|
+
return false;
|
|
643
|
+
const raw = await fs.readFile(options.rolePath, 'utf8').catch(() => '');
|
|
644
|
+
try {
|
|
645
|
+
return Boolean(parseOwnedTextHeader(raw, options.fileIdentity));
|
|
646
|
+
}
|
|
647
|
+
catch {
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
function createCodexRoleRegistrationComponent(options) {
|
|
359
652
|
return {
|
|
360
|
-
|
|
361
|
-
|
|
653
|
+
kind: 'shared-config',
|
|
654
|
+
identity: options.identity,
|
|
655
|
+
label: `${options.configPath}#agents.${options.roleKey}`,
|
|
656
|
+
locator: {
|
|
657
|
+
kind: 'codex-role-registration',
|
|
658
|
+
configPath: options.configPath,
|
|
659
|
+
roleKey: options.roleKey,
|
|
660
|
+
rolePath: options.rolePath,
|
|
661
|
+
fileIdentity: options.fileIdentity,
|
|
662
|
+
},
|
|
362
663
|
};
|
|
363
664
|
}
|
|
364
665
|
function sameOwnerIdentity(left, right) {
|
|
365
666
|
return left.owner === right.owner
|
|
366
667
|
&& left.sourceId === right.sourceId
|
|
367
|
-
&& left.sourceIdentity === right.sourceIdentity
|
|
368
668
|
&& left.pluginId === right.pluginId
|
|
369
669
|
&& left.plugin === right.plugin
|
|
370
670
|
&& left.artifactKey === right.artifactKey
|
|
371
671
|
&& left.platform === right.platform
|
|
372
672
|
&& left.scope === right.scope;
|
|
373
673
|
}
|
|
374
|
-
function readOwnedHookIdentity(identity, scope) {
|
|
375
|
-
try {
|
|
376
|
-
const parsed = parseOwnedTextHeader(identity.command);
|
|
377
|
-
return parsed?.identity.platform === 'codex' && parsed.identity.scope === scope
|
|
378
|
-
? parsed.identity
|
|
379
|
-
: null;
|
|
380
|
-
}
|
|
381
|
-
catch {
|
|
382
|
-
return null;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
674
|
function sharedOwnerPluginName(value) {
|
|
386
675
|
return parseSharedOwnerToken(value)?.plugin ?? value;
|
|
387
676
|
}
|
|
@@ -391,7 +680,6 @@ async function scanCodexSharedComponents(options) {
|
|
|
391
680
|
...ownerIdentity(options.pluginName, artifactKey, 'codex', options.scope),
|
|
392
681
|
...(options.sourceId ? {
|
|
393
682
|
sourceId: options.sourceId,
|
|
394
|
-
sourceIdentity: options.sourceIdentity,
|
|
395
683
|
pluginId: options.pluginId,
|
|
396
684
|
} : {}),
|
|
397
685
|
});
|
|
@@ -399,13 +687,12 @@ async function scanCodexSharedComponents(options) {
|
|
|
399
687
|
kind: 'shared-config',
|
|
400
688
|
identity: ownerIdentity(options.pluginName, internalArtifactKey('corrupted-shared-config', filePath), 'codex', options.scope),
|
|
401
689
|
label: filePath,
|
|
402
|
-
|
|
403
|
-
|
|
690
|
+
locator: {
|
|
691
|
+
kind: 'corrupted',
|
|
692
|
+
message: `包含 ${options.pluginName} 的 AW ownership 证据,但共享配置无法解析:${error instanceof Error ? error.message : String(error)}`,
|
|
404
693
|
},
|
|
405
|
-
remove: async () => undefined,
|
|
406
694
|
});
|
|
407
695
|
const containsOwnedEvidence = (raw) => raw.includes(options.pluginName) && (raw.includes('[[aw-managed:start plugin=')
|
|
408
|
-
|| raw.includes(MCP_OWNER_ENV)
|
|
409
696
|
|| raw.includes(OWNED_TEXT_HEADER_PREFIX)
|
|
410
697
|
|| raw.includes('agent-workflows'));
|
|
411
698
|
if (options.scanConfig) {
|
|
@@ -427,24 +714,6 @@ async function scanCodexSharedComponents(options) {
|
|
|
427
714
|
configPath: options.paths.configTomlPath,
|
|
428
715
|
}));
|
|
429
716
|
}
|
|
430
|
-
for (const [serverKey, server] of Object.entries(config?.snapshot.mcpServers ?? {})) {
|
|
431
|
-
const envValue = server.value?.env;
|
|
432
|
-
if (envValue
|
|
433
|
-
&& typeof envValue === 'object'
|
|
434
|
-
&& !Array.isArray(envValue)
|
|
435
|
-
&& parseSharedOwnerToken(String(envValue[MCP_OWNER_ENV]))?.pluginId === options.pluginId) {
|
|
436
|
-
const identity = expectedIdentity(internalArtifactKey('mcp', serverKey));
|
|
437
|
-
if (createSharedOwnerToken(identity) !== envValue[MCP_OWNER_ENV])
|
|
438
|
-
continue;
|
|
439
|
-
output.push(createCodexMcpComponent({
|
|
440
|
-
identity,
|
|
441
|
-
scope: options.scope,
|
|
442
|
-
configPath: options.paths.configTomlPath,
|
|
443
|
-
serverKey,
|
|
444
|
-
owned: true,
|
|
445
|
-
}));
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
717
|
for (const [roleKey, registration] of Object.entries(config?.snapshot.agents ?? {})) {
|
|
449
718
|
if (!registration.configFile)
|
|
450
719
|
continue;
|
|
@@ -469,28 +738,6 @@ async function scanCodexSharedComponents(options) {
|
|
|
469
738
|
}));
|
|
470
739
|
}
|
|
471
740
|
}
|
|
472
|
-
if (options.scope === 'project' && options.scanHooks) {
|
|
473
|
-
const hooks = await readCodexHooks(options.paths.hooksPath).catch(async (error) => {
|
|
474
|
-
if (!options.ignoreReadErrors)
|
|
475
|
-
throw error;
|
|
476
|
-
const raw = await fs.readFile(options.paths.hooksPath, 'utf8').catch(() => '');
|
|
477
|
-
if (containsOwnedEvidence(raw)) {
|
|
478
|
-
output.push(corruptedComponent(options.paths.hooksPath, error));
|
|
479
|
-
}
|
|
480
|
-
return null;
|
|
481
|
-
});
|
|
482
|
-
for (const identity of hooks?.managedIdentities ?? []) {
|
|
483
|
-
const parsedIdentity = readOwnedHookIdentity(identity, options.scope);
|
|
484
|
-
if (!parsedIdentity || !sameOwnerIdentity(parsedIdentity, expectedIdentity(parsedIdentity.artifactKey)))
|
|
485
|
-
continue;
|
|
486
|
-
output.push(createCodexHookComponent({
|
|
487
|
-
pluginName: options.pluginName,
|
|
488
|
-
scope: options.scope,
|
|
489
|
-
hooksPath: options.paths.hooksPath,
|
|
490
|
-
identity,
|
|
491
|
-
}));
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
741
|
return output;
|
|
495
742
|
}
|
|
496
743
|
async function scanClaudeSharedComponents(options) {
|
|
@@ -499,7 +746,6 @@ async function scanClaudeSharedComponents(options) {
|
|
|
499
746
|
...ownerIdentity(options.pluginName, 'permissions', 'claude', options.scope),
|
|
500
747
|
...(options.sourceId ? {
|
|
501
748
|
sourceId: options.sourceId,
|
|
502
|
-
sourceIdentity: options.sourceIdentity,
|
|
503
749
|
pluginId: options.pluginId,
|
|
504
750
|
} : {}),
|
|
505
751
|
};
|
|
@@ -576,7 +822,8 @@ async function scanOwnedSkills(options) {
|
|
|
576
822
|
}));
|
|
577
823
|
continue;
|
|
578
824
|
}
|
|
579
|
-
|
|
825
|
+
if (options.recursive !== false)
|
|
826
|
+
pending.push(skillRoot);
|
|
580
827
|
}
|
|
581
828
|
}
|
|
582
829
|
return output;
|
|
@@ -602,14 +849,19 @@ async function preflightCodexWriteSafety(options) {
|
|
|
602
849
|
const pluginName = options.plugin.manifest.name;
|
|
603
850
|
const userConfigPath = resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath;
|
|
604
851
|
const projectConfigPath = resolveCodexPaths(options.cwd, 'project', options.env).configTomlPath;
|
|
852
|
+
const nativeSurface = analyzeCodexSurface(options.plugin.manifest).nativeKinds.length > 0;
|
|
853
|
+
const needsUserConfig = options.scope === 'user'
|
|
854
|
+
|| nativeSurface;
|
|
605
855
|
let userConfig;
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
856
|
+
if (needsUserConfig) {
|
|
857
|
+
try {
|
|
858
|
+
userConfig = await readCodexConfigFile(userConfigPath);
|
|
859
|
+
}
|
|
860
|
+
catch (error) {
|
|
861
|
+
throw new Error(`codexProjectUserConfigUnreadable: 无法解析用户级 Codex 配置 ${userConfigPath}:${error instanceof Error ? error.message : String(error)}`);
|
|
862
|
+
}
|
|
611
863
|
}
|
|
612
|
-
if (options.scope === 'project') {
|
|
864
|
+
if (options.scope === 'project' && userConfig) {
|
|
613
865
|
const expectedPluginId = `${pluginName}@${options.plugin.marketplaceName ?? MARKETPLACE_NAME}`;
|
|
614
866
|
for (const [key, plugin] of Object.entries(userConfig.snapshot.plugins)) {
|
|
615
867
|
if (key !== expectedPluginId
|
|
@@ -624,7 +876,7 @@ async function preflightCodexWriteSafety(options) {
|
|
|
624
876
|
&& target.target === 'codex.agentRole'
|
|
625
877
|
&& target.roleKey)
|
|
626
878
|
.map((target) => target.roleKey));
|
|
627
|
-
const needsProjectConfig = options.scope === 'project' && Boolean(
|
|
879
|
+
const needsProjectConfig = options.scope === 'project' && Boolean(nativeSurface
|
|
628
880
|
|| resolvePlatformEntries(options.plugin.manifest.extensions?.instructions, 'codex').length > 0
|
|
629
881
|
|| (options.plugin.manifest.artifacts?.targets ?? []).some((target) => target.platform === 'codex'
|
|
630
882
|
&& target.scopes.includes('project')
|
|
@@ -641,7 +893,7 @@ async function preflightCodexWriteSafety(options) {
|
|
|
641
893
|
if (roleKeys.size === 0)
|
|
642
894
|
return;
|
|
643
895
|
for (const configPath of options.scope === 'project'
|
|
644
|
-
? [
|
|
896
|
+
? [projectConfigPath]
|
|
645
897
|
: [userConfigPath]) {
|
|
646
898
|
const configScope = configPath === userConfigPath ? 'user' : 'project';
|
|
647
899
|
const ownedRolePaths = [];
|
|
@@ -692,68 +944,63 @@ function renderCodexRole(options) {
|
|
|
692
944
|
'',
|
|
693
945
|
].join('\n');
|
|
694
946
|
}
|
|
695
|
-
async function resolveCodexMcpServers(plugin, scope) {
|
|
696
|
-
if (!plugin.manifest.mcpServers)
|
|
697
|
-
return {};
|
|
698
|
-
const relative = plugin.manifest.mcpServers.replace(/^\.\//u, '');
|
|
699
|
-
const direct = path.resolve(plugin.rootDir, relative);
|
|
700
|
-
const mcpPath = await fs.pathExists(direct)
|
|
701
|
-
? direct
|
|
702
|
-
: path.resolve(plugin.rootDir, 'codex', relative);
|
|
703
|
-
const servers = await readRenderedCodexMcpServersFile(mcpPath);
|
|
704
|
-
return Object.fromEntries(Object.entries(servers).map(([key, server]) => {
|
|
705
|
-
const existingEnv = server.env;
|
|
706
|
-
if (existingEnv !== undefined && (typeof existingEnv !== 'object' || Array.isArray(existingEnv))) {
|
|
707
|
-
throw new Error(`MCP server ${key} 的 env 必须是 object。`);
|
|
708
|
-
}
|
|
709
|
-
const env = (existingEnv ?? {});
|
|
710
|
-
const ownerKey = createSharedOwnerToken(pluginOwnerIdentity(plugin, internalArtifactKey('mcp', key), 'codex', scope));
|
|
711
|
-
if (env[MCP_OWNER_ENV] !== undefined
|
|
712
|
-
&& env[MCP_OWNER_ENV] !== plugin.manifest.name
|
|
713
|
-
&& env[MCP_OWNER_ENV] !== ownerKey) {
|
|
714
|
-
throw new Error(`MCP server ${key} 不能覆盖保留的 ${MCP_OWNER_ENV} owner marker。`);
|
|
715
|
-
}
|
|
716
|
-
return [key, {
|
|
717
|
-
...server,
|
|
718
|
-
env: { ...env, [MCP_OWNER_ENV]: ownerKey },
|
|
719
|
-
}];
|
|
720
|
-
}));
|
|
721
|
-
}
|
|
722
947
|
async function resolveDynamicArtifacts(options) {
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
948
|
+
const requestBatches = options.requestedArtifactIds
|
|
949
|
+
? [options.requestedArtifactIds]
|
|
950
|
+
: resolveAwDeliveredArtifactRequestBatches({
|
|
951
|
+
manifest: options.plugin.manifest,
|
|
952
|
+
platform: options.platform,
|
|
953
|
+
scope: options.scope,
|
|
954
|
+
});
|
|
955
|
+
if (requestBatches.length === 0) {
|
|
956
|
+
return { artifacts: [], sourceDirs: () => [], sensitiveValues: [] };
|
|
957
|
+
}
|
|
958
|
+
const previousArtifacts = [...options.previousArtifacts];
|
|
959
|
+
const renders = [];
|
|
960
|
+
for (const requestedArtifactIds of requestBatches) {
|
|
961
|
+
renders.push(await runArtifactRenderer({
|
|
962
|
+
event: options.event,
|
|
963
|
+
plugin: options.plugin,
|
|
964
|
+
platform: options.platform,
|
|
965
|
+
scope: options.scope,
|
|
966
|
+
cwd: options.cwd,
|
|
967
|
+
commandEnv: options.env,
|
|
968
|
+
previousArtifacts,
|
|
969
|
+
requestedArtifactIds,
|
|
970
|
+
scriptTimeoutMs: options.scriptTimeoutMs,
|
|
971
|
+
}));
|
|
972
|
+
}
|
|
736
973
|
const config = await resolveArtifactRenderConfig({
|
|
737
974
|
sourceId: options.plugin.sourceId,
|
|
738
975
|
sourceIdentity: options.plugin.sourceIdentity,
|
|
976
|
+
logicalIdentity: options.plugin.catalogLogicalIdentity,
|
|
739
977
|
pluginName: options.plugin.manifest.name,
|
|
740
978
|
manifest: options.plugin.manifest,
|
|
741
979
|
scope: options.scope,
|
|
742
980
|
cwd: options.cwd,
|
|
743
981
|
env: options.env,
|
|
744
982
|
});
|
|
745
|
-
const artifacts =
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
983
|
+
const artifacts = [];
|
|
984
|
+
const sourceDirsByArtifact = new Map();
|
|
985
|
+
for (const render of renders) {
|
|
986
|
+
artifacts.push(...validateArtifactPlan({
|
|
987
|
+
manifest: options.plugin.manifest,
|
|
988
|
+
plan: render.plan,
|
|
989
|
+
platform: options.platform,
|
|
990
|
+
scope: options.scope,
|
|
991
|
+
requestedArtifactIds: render.context.requestedArtifactIds,
|
|
992
|
+
renderConfig: config,
|
|
993
|
+
rendererDigest: render.target.rendererDigest,
|
|
994
|
+
manifestDigest: render.target.manifestDigest,
|
|
995
|
+
}).artifacts);
|
|
996
|
+
for (const source of render.context.artifactSources) {
|
|
997
|
+
sourceDirsByArtifact.set(`${source.id}\0${source.target}`, source.sourceDirs);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
754
1000
|
return {
|
|
755
1001
|
artifacts,
|
|
756
|
-
sourceDirs: (artifact) =>
|
|
1002
|
+
sourceDirs: (artifact) => sourceDirsByArtifact.get(`${artifact.id}\0${artifact.target}`) ?? [],
|
|
1003
|
+
sensitiveValues: collectSensitivePluginConfigValues(options.plugin.manifest, config.values),
|
|
757
1004
|
};
|
|
758
1005
|
}
|
|
759
1006
|
function dynamicSkillWrite(options) {
|
|
@@ -766,7 +1013,7 @@ function dynamicSkillWrite(options) {
|
|
|
766
1013
|
identity,
|
|
767
1014
|
...(options.platform === 'codex' && options.scope === 'user'
|
|
768
1015
|
? {
|
|
769
|
-
|
|
1016
|
+
pristineTree: pristineTreeDescriptor(options.tree),
|
|
770
1017
|
}
|
|
771
1018
|
: {}),
|
|
772
1019
|
});
|
|
@@ -790,17 +1037,19 @@ function dynamicSkillWrite(options) {
|
|
|
790
1037
|
: createDynamicSkillTreeContentDigest(options.tree),
|
|
791
1038
|
},
|
|
792
1039
|
...(options.artifact.state === 'present' ? { component } : {}),
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
1040
|
+
...(options.artifact.state === 'present' ? {
|
|
1041
|
+
action: skillWriteAction({
|
|
1042
|
+
plugin: options.plugin,
|
|
1043
|
+
platform: options.platform,
|
|
1044
|
+
scope: options.scope,
|
|
1045
|
+
path: options.skillPath,
|
|
798
1046
|
allowedRoot: options.allowedRoot,
|
|
799
1047
|
identity,
|
|
1048
|
+
tree: options.tree,
|
|
800
1049
|
allowPristineUnowned: options.platform === 'codex' && options.scope === 'user',
|
|
801
|
-
})
|
|
802
|
-
},
|
|
803
|
-
};
|
|
1050
|
+
}),
|
|
1051
|
+
} : {}),
|
|
1052
|
+
};
|
|
804
1053
|
}
|
|
805
1054
|
async function resolveCodexSkillsRelativeRoot(plugin, marketplaceFallbackRoot) {
|
|
806
1055
|
const manifestPaths = [
|
|
@@ -824,71 +1073,6 @@ async function resolveCodexSkillsRelativeRoot(plugin, marketplaceFallbackRoot) {
|
|
|
824
1073
|
}
|
|
825
1074
|
return normalized;
|
|
826
1075
|
}
|
|
827
|
-
async function buildBundledCodexSkillWrites(options) {
|
|
828
|
-
const relativeRoot = await resolveCodexSkillsRelativeRoot(options.plugin);
|
|
829
|
-
const sourceRoot = path.join(options.plugin.rootDir, ...relativeRoot.split('/'));
|
|
830
|
-
if (!await fs.pathExists(sourceRoot))
|
|
831
|
-
return [];
|
|
832
|
-
const dynamicKeys = new Set((options.plugin.manifest.artifacts?.targets ?? [])
|
|
833
|
-
.filter((target) => target.target === 'codex.skill' && target.skillKey)
|
|
834
|
-
.map((target) => target.skillKey));
|
|
835
|
-
const targetRoot = options.scope === 'project'
|
|
836
|
-
? path.join(options.repoRoot, '.agents', 'skills')
|
|
837
|
-
: path.join(options.marketplaceRoot ?? resolveAwHome(options.env), 'plugins', options.plugin.manifest.name, ...relativeRoot.split('/'));
|
|
838
|
-
const writes = [];
|
|
839
|
-
for (const entry of await fs.readdir(sourceRoot, { withFileTypes: true })) {
|
|
840
|
-
if (entry.isSymbolicLink()) {
|
|
841
|
-
throw new Error(`Codex skills source 不能包含符号链接:${entry.name}`);
|
|
842
|
-
}
|
|
843
|
-
if (!entry.isDirectory() || dynamicKeys.has(entry.name))
|
|
844
|
-
continue;
|
|
845
|
-
const skillSource = path.join(sourceRoot, entry.name);
|
|
846
|
-
if (!await fs.pathExists(path.join(skillSource, 'SKILL.md')))
|
|
847
|
-
continue;
|
|
848
|
-
const tree = await createCodexSkillExpectedTree({
|
|
849
|
-
artifact: {
|
|
850
|
-
target: 'codex.skill',
|
|
851
|
-
skillKey: entry.name,
|
|
852
|
-
state: 'present',
|
|
853
|
-
content: { files: {} },
|
|
854
|
-
},
|
|
855
|
-
sourceDirs: [skillSource],
|
|
856
|
-
});
|
|
857
|
-
const skillPath = path.join(targetRoot, entry.name);
|
|
858
|
-
const canRecoverPristine = options.scope === 'user'
|
|
859
|
-
&& path.resolve(skillSource) !== path.resolve(skillPath);
|
|
860
|
-
const identity = ownerIdentity(options.plugin.manifest.name, entry.name, 'codex', options.scope);
|
|
861
|
-
const component = pathComponent({
|
|
862
|
-
kind: 'skill-directory',
|
|
863
|
-
path: skillPath,
|
|
864
|
-
allowedRoot: targetRoot,
|
|
865
|
-
identity,
|
|
866
|
-
...(canRecoverPristine
|
|
867
|
-
? {
|
|
868
|
-
unownedIsPristine: (actualPath) => skillDirectoryMatchesExpectedTree(actualPath, tree),
|
|
869
|
-
}
|
|
870
|
-
: {}),
|
|
871
|
-
});
|
|
872
|
-
writes.push({
|
|
873
|
-
artifact: {
|
|
874
|
-
path: skillPath,
|
|
875
|
-
paths: Object.keys(tree.files).map((relativePath) => path.join(skillPath, relativePath)),
|
|
876
|
-
kind: 'skill',
|
|
877
|
-
platform: 'codex',
|
|
878
|
-
artifactKey: entry.name,
|
|
879
|
-
},
|
|
880
|
-
component,
|
|
881
|
-
write: async () => {
|
|
882
|
-
await writeDynamicSkillTree(skillPath, tree, {
|
|
883
|
-
allowedRoot: targetRoot,
|
|
884
|
-
identity,
|
|
885
|
-
allowPristineUnowned: canRecoverPristine,
|
|
886
|
-
});
|
|
887
|
-
},
|
|
888
|
-
});
|
|
889
|
-
}
|
|
890
|
-
return writes;
|
|
891
|
-
}
|
|
892
1076
|
async function buildClaudeWrites(options) {
|
|
893
1077
|
const paths = resolveClaudePaths(options.cwd, options.scope, options.env);
|
|
894
1078
|
const writes = [];
|
|
@@ -903,7 +1087,10 @@ async function buildClaudeWrites(options) {
|
|
|
903
1087
|
artifact,
|
|
904
1088
|
sourceDirs: dynamic.sourceDirs(artifact),
|
|
905
1089
|
requireReadme: true,
|
|
1090
|
+
requirementsReceipt: requirementsReceipt(options.plugin),
|
|
1091
|
+
sourceRequirementsReceiptPolicy: 'allow',
|
|
906
1092
|
});
|
|
1093
|
+
assertNoSensitiveConfigInFiles(withoutVerifiedRequirementsReceipt(options.plugin, tree.files), dynamic.sensitiveValues, 'artifactRendererSensitiveConfigExposed: Final dynamic Skill contains sensitive config.');
|
|
907
1094
|
writes.push(dynamicSkillWrite({
|
|
908
1095
|
...options,
|
|
909
1096
|
platform: 'claude',
|
|
@@ -931,13 +1118,20 @@ async function buildClaudeWrites(options) {
|
|
|
931
1118
|
scope: options.scope,
|
|
932
1119
|
settingsPath: paths.settingsPath,
|
|
933
1120
|
});
|
|
1121
|
+
const desiredPermissions = mergeClaudePermissions(permissions);
|
|
1122
|
+
const mutation = { kind: 'claude-permissions', permissions: desiredPermissions };
|
|
934
1123
|
writes.push({
|
|
935
1124
|
artifact: { path: paths.settingsPath, kind: 'permission', platform: 'claude' },
|
|
936
1125
|
component,
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1126
|
+
action: {
|
|
1127
|
+
kind: 'updateSharedConfig',
|
|
1128
|
+
pluginId: pluginActionId(options.plugin),
|
|
1129
|
+
platform: 'claude',
|
|
1130
|
+
scope: options.scope,
|
|
1131
|
+
owner: component.identity,
|
|
1132
|
+
locator: component.locator,
|
|
1133
|
+
mutation,
|
|
1134
|
+
contentDigest: `sha256:${crypto.createHash('sha256').update(JSON.stringify(mutation)).digest('hex')}`,
|
|
941
1135
|
},
|
|
942
1136
|
});
|
|
943
1137
|
}
|
|
@@ -955,23 +1149,18 @@ async function buildCodexWrites(options) {
|
|
|
955
1149
|
const dynamicRoles = dynamic.artifacts.filter((entry) => entry.target === 'codex.agentRole');
|
|
956
1150
|
const dynamicSkills = dynamic.artifacts.filter((entry) => entry.target === 'codex.skill');
|
|
957
1151
|
const repoRoot = await resolveRepoRoot(options.cwd);
|
|
958
|
-
writes.push(...await buildBundledCodexSkillWrites({
|
|
959
|
-
plugin: options.plugin,
|
|
960
|
-
scope: options.scope,
|
|
961
|
-
repoRoot,
|
|
962
|
-
env: options.env,
|
|
963
|
-
marketplaceRoot: options.marketplaceRoot,
|
|
964
|
-
}));
|
|
965
|
-
const relativeSkillsRoot = await resolveCodexSkillsRelativeRoot(options.plugin);
|
|
966
1152
|
const skillRoot = options.scope === 'user'
|
|
967
|
-
? path.join(
|
|
1153
|
+
? path.join(getCodexHome(options.env), 'skills')
|
|
968
1154
|
: path.join(repoRoot, '.agents', 'skills');
|
|
969
1155
|
for (const artifact of dynamicSkills) {
|
|
970
1156
|
const tree = await createCodexSkillExpectedTree({
|
|
971
1157
|
artifact,
|
|
972
1158
|
sourceDirs: dynamic.sourceDirs(artifact),
|
|
973
1159
|
requireReadme: true,
|
|
1160
|
+
requirementsReceipt: requirementsReceipt(options.plugin),
|
|
1161
|
+
sourceRequirementsReceiptPolicy: 'allow',
|
|
974
1162
|
});
|
|
1163
|
+
assertNoSensitiveConfigInFiles(withoutVerifiedRequirementsReceipt(options.plugin, tree.files), dynamic.sensitiveValues, 'artifactRendererSensitiveConfigExposed: Final dynamic Skill contains sensitive config.');
|
|
975
1164
|
writes.push(dynamicSkillWrite({
|
|
976
1165
|
...options,
|
|
977
1166
|
platform: 'codex',
|
|
@@ -984,7 +1173,6 @@ async function buildCodexWrites(options) {
|
|
|
984
1173
|
const staticInstructions = dynamicInstructions.length > 0
|
|
985
1174
|
? []
|
|
986
1175
|
: resolvePlatformEntries(options.plugin.manifest.extensions?.instructions, 'codex');
|
|
987
|
-
const mcpServers = await resolveCodexMcpServers(options.plugin, options.scope);
|
|
988
1176
|
const hasInstructionOutput = dynamicInstructions.length > 0 || staticInstructions.length > 0;
|
|
989
1177
|
if (hasInstructionOutput) {
|
|
990
1178
|
const component = createCodexInstructionComponent({
|
|
@@ -1005,6 +1193,7 @@ async function buildCodexWrites(options) {
|
|
|
1005
1193
|
value: renderCodexManagedInstructionBlock(options.plugin.manifest.name, staticInstructions),
|
|
1006
1194
|
}
|
|
1007
1195
|
: undefined;
|
|
1196
|
+
const instructionMutation = { kind: 'codex-instruction', ...(block ? { block } : {}) };
|
|
1008
1197
|
for (const artifact of dynamicInstructions.length > 0 ? dynamicInstructions : [{
|
|
1009
1198
|
id: undefined,
|
|
1010
1199
|
target: undefined,
|
|
@@ -1026,42 +1215,20 @@ async function buildCodexWrites(options) {
|
|
|
1026
1215
|
...(artifact.disabledReason ? { disabledReason: artifact.disabledReason } : {}),
|
|
1027
1216
|
},
|
|
1028
1217
|
...(block ? { component } : {}),
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1218
|
+
action: {
|
|
1219
|
+
kind: 'updateSharedConfig',
|
|
1220
|
+
pluginId: pluginActionId(options.plugin),
|
|
1221
|
+
platform: 'codex',
|
|
1222
|
+
scope: options.scope,
|
|
1223
|
+
owner: component.identity,
|
|
1224
|
+
locator: component.locator,
|
|
1225
|
+
mutation: instructionMutation,
|
|
1226
|
+
contentDigest: `sha256:${crypto.createHash('sha256')
|
|
1227
|
+
.update(JSON.stringify(instructionMutation)).digest('hex')}`,
|
|
1033
1228
|
},
|
|
1034
1229
|
});
|
|
1035
1230
|
}
|
|
1036
1231
|
}
|
|
1037
|
-
for (const [serverKey, server] of Object.entries(mcpServers)) {
|
|
1038
|
-
const component = createCodexMcpComponent({
|
|
1039
|
-
identity: pluginOwnerIdentity(options.plugin, internalArtifactKey('mcp', serverKey), 'codex', options.scope),
|
|
1040
|
-
scope: options.scope,
|
|
1041
|
-
configPath: paths.configTomlPath,
|
|
1042
|
-
serverKey,
|
|
1043
|
-
owned: false,
|
|
1044
|
-
});
|
|
1045
|
-
writes.push({
|
|
1046
|
-
artifact: {
|
|
1047
|
-
path: paths.configTomlPath,
|
|
1048
|
-
kind: 'mcpServer',
|
|
1049
|
-
platform: 'codex',
|
|
1050
|
-
artifactKey: serverKey,
|
|
1051
|
-
},
|
|
1052
|
-
component,
|
|
1053
|
-
write: async () => {
|
|
1054
|
-
await applyConditionalCodexConfigTransactionFile(paths.configTomlPath, (snapshot) => {
|
|
1055
|
-
const actual = snapshot.mcpServers[serverKey];
|
|
1056
|
-
const actualOwner = codexMcpOwner(actual);
|
|
1057
|
-
if (actual && actualOwner !== createSharedOwnerToken(component.identity)) {
|
|
1058
|
-
throw new Error(`Codex MCP server "${serverKey}" is foreign and cannot be overwritten by plugin "${options.plugin.manifest.name}".`);
|
|
1059
|
-
}
|
|
1060
|
-
return { upsertMcpServers: { [serverKey]: server } };
|
|
1061
|
-
});
|
|
1062
|
-
},
|
|
1063
|
-
});
|
|
1064
|
-
}
|
|
1065
1232
|
for (const artifact of dynamicRoles) {
|
|
1066
1233
|
const roleKey = artifact.roleKey;
|
|
1067
1234
|
const rolePath = path.join(paths.configDir, 'agents', `${buildGeneratedName(options.plugin.manifest.name, roleKey)}.toml`);
|
|
@@ -1102,41 +1269,51 @@ async function buildCodexWrites(options) {
|
|
|
1102
1269
|
},
|
|
1103
1270
|
...(present ? { component: roleComponent } : {}),
|
|
1104
1271
|
...(present ? { additionalComponents: [registrationComponent] } : {}),
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
}
|
|
1109
|
-
await fs.ensureDir(paths.configDir);
|
|
1110
|
-
await writeOwnedTextFile({
|
|
1111
|
-
path: rolePath,
|
|
1112
|
-
allowedRoot: paths.configDir,
|
|
1113
|
-
identity: roleComponent.identity,
|
|
1114
|
-
content: renderCodexRole({
|
|
1272
|
+
...(present ? {
|
|
1273
|
+
action: (() => {
|
|
1274
|
+
const content = renderCodexRole({
|
|
1115
1275
|
roleKey,
|
|
1116
1276
|
description: description,
|
|
1117
1277
|
developerInstructions: roleFile.developer_instructions,
|
|
1118
1278
|
model: roleFile.model,
|
|
1119
1279
|
modelReasoningEffort: roleFile.model_reasoning_effort,
|
|
1120
|
-
})
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1280
|
+
});
|
|
1281
|
+
return {
|
|
1282
|
+
kind: 'writeOwnedArtifact',
|
|
1283
|
+
pluginId: pluginActionId(options.plugin),
|
|
1284
|
+
platform: 'codex',
|
|
1285
|
+
scope: options.scope,
|
|
1286
|
+
path: rolePath,
|
|
1287
|
+
allowedRoot: paths.configDir,
|
|
1288
|
+
owner: roleComponent.identity,
|
|
1289
|
+
artifactKind: 'owned-text-file',
|
|
1290
|
+
sourceContent: content,
|
|
1291
|
+
content,
|
|
1292
|
+
contentDigest: `sha256:${crypto.createHash('sha256').update(content).digest('hex')}`,
|
|
1293
|
+
};
|
|
1294
|
+
})(),
|
|
1295
|
+
} : {}),
|
|
1296
|
+
...(present ? {
|
|
1297
|
+
additionalActions: [(() => {
|
|
1298
|
+
const mutation = {
|
|
1299
|
+
kind: 'codex-role-registration',
|
|
1300
|
+
roleKey,
|
|
1301
|
+
configFile: path.relative(path.dirname(paths.configTomlPath), rolePath),
|
|
1302
|
+
description: description,
|
|
1303
|
+
};
|
|
1304
|
+
return {
|
|
1305
|
+
kind: 'updateSharedConfig',
|
|
1306
|
+
pluginId: pluginActionId(options.plugin),
|
|
1307
|
+
platform: 'codex',
|
|
1308
|
+
scope: options.scope,
|
|
1309
|
+
owner: registrationComponent.identity,
|
|
1310
|
+
locator: registrationComponent.locator,
|
|
1311
|
+
mutation,
|
|
1312
|
+
contentDigest: `sha256:${crypto.createHash('sha256')
|
|
1313
|
+
.update(JSON.stringify(mutation)).digest('hex')}`,
|
|
1314
|
+
};
|
|
1315
|
+
})()],
|
|
1316
|
+
} : {}),
|
|
1140
1317
|
});
|
|
1141
1318
|
}
|
|
1142
1319
|
for (const artifact of generateCodexPermissionArtifacts(options.plugin.manifest.name, resolvePlatformEntries(options.plugin.manifest.extensions?.permissions, 'codex'))) {
|
|
@@ -1161,42 +1338,44 @@ async function buildCodexWrites(options) {
|
|
|
1161
1338
|
kind: 'agent',
|
|
1162
1339
|
}));
|
|
1163
1340
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1341
|
+
const codexPluginId = `${options.plugin.manifest.name}@${options.marketplaceName ?? options.plugin.marketplaceName ?? MARKETPLACE_NAME}`;
|
|
1342
|
+
const nativeSurface = analyzeCodexSurface(options.plugin.manifest).nativeKinds.length > 0;
|
|
1343
|
+
const userActivationDesired = options.scope === 'user'
|
|
1344
|
+
|| options.userActivationDesired?.has(codexPluginId) === true;
|
|
1345
|
+
for (const activation of !nativeSurface ? [] : options.scope === 'project'
|
|
1346
|
+
? [
|
|
1347
|
+
{
|
|
1348
|
+
scope: 'user',
|
|
1349
|
+
desired: userActivationDesired,
|
|
1350
|
+
configPath: resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath,
|
|
1351
|
+
},
|
|
1352
|
+
{ scope: 'project', desired: true, configPath: paths.configTomlPath },
|
|
1353
|
+
]
|
|
1354
|
+
: [{ scope: 'user', desired: true, configPath: paths.configTomlPath }]) {
|
|
1355
|
+
const component = createCodexActivationComponent({
|
|
1356
|
+
identity: pluginOwnerIdentity(options.plugin, internalArtifactKey('activation', activation.scope), 'codex', options.scope),
|
|
1357
|
+
configPath: activation.configPath,
|
|
1358
|
+
codexPluginId,
|
|
1359
|
+
desired: activation.desired,
|
|
1360
|
+
});
|
|
1361
|
+
writes.push({
|
|
1362
|
+
artifact: {
|
|
1363
|
+
path: activation.configPath,
|
|
1364
|
+
kind: 'instruction',
|
|
1365
|
+
platform: 'codex',
|
|
1366
|
+
artifactKey: internalArtifactKey('activation', activation.scope),
|
|
1367
|
+
},
|
|
1368
|
+
component,
|
|
1369
|
+
action: {
|
|
1370
|
+
kind: 'setActivation',
|
|
1371
|
+
pluginId: pluginActionId(options.plugin),
|
|
1372
|
+
platform: 'codex',
|
|
1373
|
+
scope: activation.scope,
|
|
1374
|
+
enabled: activation.desired,
|
|
1375
|
+
configPath: activation.configPath,
|
|
1376
|
+
registration: codexPluginId,
|
|
1377
|
+
},
|
|
1378
|
+
});
|
|
1200
1379
|
}
|
|
1201
1380
|
return writes;
|
|
1202
1381
|
}
|
|
@@ -1222,7 +1401,6 @@ async function buildInspectionWrites(options) {
|
|
|
1222
1401
|
scope: options.scope,
|
|
1223
1402
|
settingsPath: paths.settingsPath,
|
|
1224
1403
|
}),
|
|
1225
|
-
write: async () => undefined,
|
|
1226
1404
|
});
|
|
1227
1405
|
}
|
|
1228
1406
|
for (const target of options.plugin.manifest.artifacts?.targets ?? []) {
|
|
@@ -1253,20 +1431,12 @@ async function buildInspectionWrites(options) {
|
|
|
1253
1431
|
...(target.states.includes('present') && target.states.includes('disabled')
|
|
1254
1432
|
? { optional: true }
|
|
1255
1433
|
: {}),
|
|
1256
|
-
write: async () => undefined,
|
|
1257
1434
|
});
|
|
1258
1435
|
}
|
|
1259
1436
|
return writes;
|
|
1260
1437
|
}
|
|
1261
1438
|
const paths = resolveCodexPaths(options.cwd, options.scope, options.env);
|
|
1262
1439
|
const repoRoot = await resolveRepoRoot(options.cwd);
|
|
1263
|
-
writes.push(...await buildBundledCodexSkillWrites({
|
|
1264
|
-
plugin: options.plugin,
|
|
1265
|
-
scope: options.scope,
|
|
1266
|
-
repoRoot,
|
|
1267
|
-
env: options.env,
|
|
1268
|
-
marketplaceRoot: options.marketplaceRoot,
|
|
1269
|
-
}));
|
|
1270
1440
|
const instructions = resolvePlatformEntries(options.plugin.manifest.extensions?.instructions, 'codex');
|
|
1271
1441
|
const targets = (options.plugin.manifest.artifacts?.targets ?? []).filter((target) => target.platform === 'codex' && target.scopes.includes(options.scope));
|
|
1272
1442
|
const instructionTargets = targets.filter((target) => target.target === 'codex.developerInstructionsBlock');
|
|
@@ -1283,35 +1453,16 @@ async function buildInspectionWrites(options) {
|
|
|
1283
1453
|
configPath: paths.configTomlPath,
|
|
1284
1454
|
}),
|
|
1285
1455
|
...(instructions.length === 0 && instructionOptional ? { optional: true } : {}),
|
|
1286
|
-
write: async () => undefined,
|
|
1287
|
-
});
|
|
1288
|
-
}
|
|
1289
|
-
const mcpServers = await resolveCodexMcpServers(options.plugin, options.scope);
|
|
1290
|
-
for (const serverKey of Object.keys(mcpServers)) {
|
|
1291
|
-
writes.push({
|
|
1292
|
-
artifact: {
|
|
1293
|
-
path: paths.configTomlPath,
|
|
1294
|
-
kind: 'mcpServer',
|
|
1295
|
-
platform: 'codex',
|
|
1296
|
-
artifactKey: serverKey,
|
|
1297
|
-
},
|
|
1298
|
-
component: createCodexMcpComponent({
|
|
1299
|
-
identity: pluginOwnerIdentity(options.plugin, internalArtifactKey('mcp', serverKey), 'codex', options.scope),
|
|
1300
|
-
scope: options.scope,
|
|
1301
|
-
configPath: paths.configTomlPath,
|
|
1302
|
-
serverKey,
|
|
1303
|
-
owned: false,
|
|
1304
|
-
}),
|
|
1305
|
-
write: async () => undefined,
|
|
1306
1456
|
});
|
|
1307
1457
|
}
|
|
1308
|
-
const relativeSkillsRoot = await resolveCodexSkillsRelativeRoot(options.plugin);
|
|
1309
1458
|
const dynamicSkillRoot = options.scope === 'user'
|
|
1310
|
-
? path.join(
|
|
1459
|
+
? path.join(getCodexHome(options.env), 'skills')
|
|
1311
1460
|
: path.join(repoRoot, '.agents', 'skills');
|
|
1312
1461
|
for (const target of targets) {
|
|
1313
1462
|
const state = target.states.includes('present') ? 'present' : 'disabled';
|
|
1314
|
-
if (target.target === 'codex.skill'
|
|
1463
|
+
if (target.target === 'codex.skill'
|
|
1464
|
+
&& target.delivery?.mode === 'aw-extension'
|
|
1465
|
+
&& target.skillKey) {
|
|
1315
1466
|
const skillPath = path.join(dynamicSkillRoot, target.skillKey);
|
|
1316
1467
|
const component = pathComponent({
|
|
1317
1468
|
kind: 'skill-directory',
|
|
@@ -1333,7 +1484,6 @@ async function buildInspectionWrites(options) {
|
|
|
1333
1484
|
...(target.states.includes('present') && target.states.includes('disabled')
|
|
1334
1485
|
? { optional: true }
|
|
1335
1486
|
: {}),
|
|
1336
|
-
write: async () => undefined,
|
|
1337
1487
|
});
|
|
1338
1488
|
}
|
|
1339
1489
|
if (target.target === 'codex.agentRole' && target.roleKey) {
|
|
@@ -1368,7 +1518,6 @@ async function buildInspectionWrites(options) {
|
|
|
1368
1518
|
...(target.states.includes('present') && target.states.includes('disabled')
|
|
1369
1519
|
? { optional: true }
|
|
1370
1520
|
: {}),
|
|
1371
|
-
write: async () => undefined,
|
|
1372
1521
|
});
|
|
1373
1522
|
}
|
|
1374
1523
|
}
|
|
@@ -1392,32 +1541,34 @@ async function buildInspectionWrites(options) {
|
|
|
1392
1541
|
kind: 'agent',
|
|
1393
1542
|
}));
|
|
1394
1543
|
}
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
scope:
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1544
|
+
const nativeSurface = analyzeCodexSurface(options.plugin.manifest).nativeKinds.length > 0;
|
|
1545
|
+
const codexPluginId = `${options.plugin.manifest.name}@${options.marketplaceName ?? options.plugin.marketplaceName ?? MARKETPLACE_NAME}`;
|
|
1546
|
+
const userActivationDesired = options.scope === 'user'
|
|
1547
|
+
|| options.userActivationDesired?.has(codexPluginId) === true;
|
|
1548
|
+
for (const activation of !nativeSurface ? [] : options.scope === 'project'
|
|
1549
|
+
? [
|
|
1550
|
+
{
|
|
1551
|
+
scope: 'user',
|
|
1552
|
+
desired: userActivationDesired,
|
|
1553
|
+
configPath: resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath,
|
|
1554
|
+
},
|
|
1555
|
+
{ scope: 'project', desired: true, configPath: paths.configTomlPath },
|
|
1556
|
+
]
|
|
1557
|
+
: [{ scope: 'user', desired: true, configPath: paths.configTomlPath }]) {
|
|
1558
|
+
writes.push({
|
|
1559
|
+
artifact: {
|
|
1560
|
+
path: activation.configPath,
|
|
1561
|
+
kind: 'instruction',
|
|
1562
|
+
platform: 'codex',
|
|
1563
|
+
artifactKey: internalArtifactKey('activation', activation.scope),
|
|
1564
|
+
},
|
|
1565
|
+
component: createCodexActivationComponent({
|
|
1566
|
+
identity: pluginOwnerIdentity(options.plugin, internalArtifactKey('activation', activation.scope), 'codex', options.scope),
|
|
1567
|
+
configPath: activation.configPath,
|
|
1568
|
+
codexPluginId,
|
|
1569
|
+
desired: activation.desired,
|
|
1570
|
+
}),
|
|
1571
|
+
});
|
|
1421
1572
|
}
|
|
1422
1573
|
return writes;
|
|
1423
1574
|
}
|
|
@@ -1430,7 +1581,6 @@ async function buildHistoricalComponents(options) {
|
|
|
1430
1581
|
...ownerIdentity(options.pluginName, artifactKey, options.platform, options.scope),
|
|
1431
1582
|
...(options.sourceId ? {
|
|
1432
1583
|
sourceId: options.sourceId,
|
|
1433
|
-
sourceIdentity: options.sourceIdentity,
|
|
1434
1584
|
pluginId: options.pluginId,
|
|
1435
1585
|
} : {}),
|
|
1436
1586
|
});
|
|
@@ -1439,24 +1589,35 @@ async function buildHistoricalComponents(options) {
|
|
|
1439
1589
|
continue;
|
|
1440
1590
|
if (artifact.state === 'disabled')
|
|
1441
1591
|
continue;
|
|
1442
|
-
if (
|
|
1592
|
+
if (options.platform === 'codex'
|
|
1593
|
+
&& artifact.artifactKey !== undefined
|
|
1594
|
+
&& new Set([
|
|
1595
|
+
internalArtifactKey('activation', 'user'),
|
|
1596
|
+
internalArtifactKey('activation', 'project'),
|
|
1597
|
+
]).has(artifact.artifactKey)) {
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1600
|
+
if (options.command === 'cleanup'
|
|
1601
|
+
&& artifact.kind === 'hook'
|
|
1602
|
+
&& options.platform === 'codex') {
|
|
1443
1603
|
for (const identity of artifact.hookGroups ?? []) {
|
|
1444
|
-
components.push(
|
|
1604
|
+
components.push(createLegacyCodexHookCleanupComponent({
|
|
1445
1605
|
pluginName: options.pluginName,
|
|
1446
1606
|
scope: options.scope,
|
|
1447
|
-
hooksPath: resolveCodexPaths(options.cwd, options.scope, options.env).
|
|
1607
|
+
hooksPath: path.join(resolveCodexPaths(options.cwd, options.scope, options.env).configDir, 'hooks.json'),
|
|
1448
1608
|
identity,
|
|
1449
1609
|
}));
|
|
1450
1610
|
}
|
|
1451
1611
|
continue;
|
|
1452
1612
|
}
|
|
1453
|
-
if (
|
|
1454
|
-
|
|
1613
|
+
if (options.command === 'cleanup'
|
|
1614
|
+
&& artifact.kind === 'mcpServer'
|
|
1615
|
+
&& options.platform === 'codex'
|
|
1616
|
+
&& artifact.artifactKey) {
|
|
1617
|
+
components.push(createLegacyCodexMcpCleanupComponent({
|
|
1455
1618
|
identity: historicalIdentity(internalArtifactKey('mcp', artifact.artifactKey)),
|
|
1456
|
-
scope: options.scope,
|
|
1457
1619
|
configPath: resolveCodexPaths(options.cwd, options.scope, options.env).configTomlPath,
|
|
1458
1620
|
serverKey: artifact.artifactKey,
|
|
1459
|
-
owned: false,
|
|
1460
1621
|
}));
|
|
1461
1622
|
continue;
|
|
1462
1623
|
}
|
|
@@ -1500,230 +1661,519 @@ async function buildHistoricalComponents(options) {
|
|
|
1500
1661
|
}
|
|
1501
1662
|
return components;
|
|
1502
1663
|
}
|
|
1503
|
-
function
|
|
1664
|
+
function isAbsentPath(error) {
|
|
1504
1665
|
const code = error.code;
|
|
1505
1666
|
return code === 'ENOENT';
|
|
1506
1667
|
}
|
|
1668
|
+
async function lstatIfPresent(filePath) {
|
|
1669
|
+
return fs.lstat(filePath).catch((error) => {
|
|
1670
|
+
if (isAbsentPath(error))
|
|
1671
|
+
return null;
|
|
1672
|
+
throw error;
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1507
1675
|
async function lstatCodexResidueIfPresent(filePath) {
|
|
1508
1676
|
return fs.lstat(filePath).catch((error) => {
|
|
1509
|
-
if (
|
|
1677
|
+
if (isAbsentPath(error))
|
|
1510
1678
|
return null;
|
|
1511
1679
|
throw error;
|
|
1512
1680
|
});
|
|
1513
1681
|
}
|
|
1514
|
-
|
|
1515
|
-
const
|
|
1516
|
-
|
|
1517
|
-
return [];
|
|
1518
|
-
if (!stats.isDirectory() || stats.isSymbolicLink()) {
|
|
1519
|
-
throw new Error(`Codex residue root is not a canonical directory: ${directory}`);
|
|
1520
|
-
}
|
|
1521
|
-
return fs.readdir(directory, { withFileTypes: true });
|
|
1682
|
+
function isPathWithin(root, candidate) {
|
|
1683
|
+
const relative = path.relative(path.resolve(root), path.resolve(candidate));
|
|
1684
|
+
return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
|
|
1522
1685
|
}
|
|
1523
|
-
async function
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
const
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
const
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
const
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
const
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
const version = typeof manifest?.version === 'string' ? manifest.version : null;
|
|
1554
|
-
if (!canonicalMarketplace
|
|
1555
|
-
|| (identity !== options.pluginName && identity !== pluginId)
|
|
1556
|
-
|| !version
|
|
1557
|
-
|| (directoryVersion !== undefined && version !== directoryVersion)) {
|
|
1558
|
-
foreignLabels.push(manifestPath);
|
|
1559
|
-
return;
|
|
1560
|
-
}
|
|
1561
|
-
labels.push(manifestPath);
|
|
1562
|
-
versions.add(version);
|
|
1563
|
-
};
|
|
1564
|
-
for (const entry of await readdirCodexResidueIfPresent(cacheRoot)) {
|
|
1565
|
-
if (!entry.isDirectory() || entry.isSymbolicLink()) {
|
|
1566
|
-
foreignLabels.push(path.join(cacheRoot, entry.name));
|
|
1567
|
-
continue;
|
|
1568
|
-
}
|
|
1569
|
-
const versionRoot = path.join(cacheRoot, entry.name);
|
|
1570
|
-
const candidates = [
|
|
1571
|
-
path.join(versionRoot, 'plugin.json'),
|
|
1572
|
-
path.join(versionRoot, '.codex-plugin', 'plugin.json'),
|
|
1573
|
-
];
|
|
1574
|
-
const existing = [];
|
|
1575
|
-
for (const candidate of candidates) {
|
|
1576
|
-
if (await lstatCodexResidueIfPresent(candidate))
|
|
1577
|
-
existing.push(candidate);
|
|
1578
|
-
}
|
|
1579
|
-
if (existing.length === 0)
|
|
1580
|
-
foreignLabels.push(versionRoot);
|
|
1581
|
-
for (const candidate of existing)
|
|
1582
|
-
await inspectManifest(candidate, entry.name);
|
|
1583
|
-
}
|
|
1584
|
-
const installedStats = await lstatCodexResidueIfPresent(installedRoot);
|
|
1585
|
-
if (installedStats && (!installedStats.isDirectory() || installedStats.isSymbolicLink())) {
|
|
1586
|
-
foreignLabels.push(installedRoot);
|
|
1587
|
-
}
|
|
1588
|
-
else if (installedStats) {
|
|
1589
|
-
await inspectManifest(installedManifest);
|
|
1590
|
-
}
|
|
1591
|
-
return foreignLabels.length > 0
|
|
1592
|
-
? {
|
|
1593
|
-
state: 'foreign', labels: [...labels, ...foreignLabels], versions: [...versions],
|
|
1594
|
-
enabled: configEntry?.enabled === true,
|
|
1595
|
-
}
|
|
1596
|
-
: labels.length > 0
|
|
1597
|
-
? { state: 'present', labels, versions: [...versions], enabled: configEntry?.enabled === true }
|
|
1598
|
-
: { state: 'absent', labels: [], versions: [], enabled: false };
|
|
1599
|
-
}
|
|
1600
|
-
export async function discoverCodexNativeResidueNames(options) {
|
|
1601
|
-
const marketplaceName = options.marketplaceName ?? MARKETPLACE_NAME;
|
|
1602
|
-
const marketplaceRoot = options.marketplaceRoot ?? resolveAwHome(options.env);
|
|
1603
|
-
const names = new Set();
|
|
1604
|
-
const config = await readCodexConfigFile(resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath);
|
|
1605
|
-
const marketplace = config.snapshot.marketplaces[marketplaceName];
|
|
1606
|
-
const canonicalMarketplace = marketplace?.sourceType === 'local'
|
|
1607
|
-
&& !marketplace.sourcePointsToManifest
|
|
1608
|
-
&& path.normalize(marketplace.source ?? '') === path.normalize(marketplaceRoot);
|
|
1609
|
-
const probeFailure = (location, cause, pluginName) => {
|
|
1610
|
-
if (cause instanceof CodexNativeResidueProbeError)
|
|
1611
|
-
throw cause;
|
|
1612
|
-
throw new CodexNativeResidueProbeError(location, cause, pluginName);
|
|
1613
|
-
};
|
|
1614
|
-
for (const pluginId of canonicalMarketplace ? Object.keys(config.snapshot.plugins) : []) {
|
|
1615
|
-
const suffix = `@${marketplaceName}`;
|
|
1616
|
-
const match = pluginId.endsWith(suffix)
|
|
1617
|
-
? /^([a-z0-9]+(?:-[a-z0-9]+)*)$/u.exec(pluginId.slice(0, -suffix.length))
|
|
1618
|
-
: null;
|
|
1619
|
-
if (match)
|
|
1620
|
-
names.add(match[1]);
|
|
1621
|
-
}
|
|
1622
|
-
const cacheRoot = path.join(getCodexHome(options.env), 'plugins', 'cache', marketplaceName);
|
|
1623
|
-
const cacheEntries = await readdirCodexResidueIfPresent(cacheRoot).catch((error) => probeFailure(cacheRoot, error));
|
|
1624
|
-
for (const entry of cacheEntries) {
|
|
1625
|
-
const entryPath = path.join(cacheRoot, entry.name);
|
|
1626
|
-
const pluginName = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(entry.name)
|
|
1627
|
-
? entry.name
|
|
1628
|
-
: probeFailure(entryPath, new Error('Codex cache residue has an invalid plugin identity.'));
|
|
1629
|
-
if (!entry.isDirectory() || entry.isSymbolicLink()) {
|
|
1630
|
-
probeFailure(entryPath, new Error('Codex cache residue is not a canonical plugin directory.'), pluginName);
|
|
1631
|
-
}
|
|
1632
|
-
let versionEntries;
|
|
1633
|
-
try {
|
|
1634
|
-
versionEntries = await fs.readdir(entryPath, { withFileTypes: true });
|
|
1635
|
-
}
|
|
1636
|
-
catch (error) {
|
|
1637
|
-
throw new CodexNativeResidueProbeError(entryPath, error, pluginName);
|
|
1638
|
-
}
|
|
1639
|
-
if (versionEntries.length === 0) {
|
|
1640
|
-
if (names.has(pluginName)) {
|
|
1641
|
-
probeFailure(entryPath, new Error('Codex cache residue manifest is missing.'), pluginName);
|
|
1686
|
+
async function codexReadPathSafe(root, candidate) {
|
|
1687
|
+
if (!isPathWithin(root, candidate))
|
|
1688
|
+
return false;
|
|
1689
|
+
const rootStats = await lstatCodexResidueIfPresent(root);
|
|
1690
|
+
if (rootStats && (!rootStats.isDirectory() || rootStats.isSymbolicLink()))
|
|
1691
|
+
return false;
|
|
1692
|
+
const [physicalRoot, physicalCandidate] = await Promise.all([
|
|
1693
|
+
canonicalizePathThroughExistingAncestor(root),
|
|
1694
|
+
canonicalizePathThroughExistingAncestor(candidate),
|
|
1695
|
+
]);
|
|
1696
|
+
return isPathWithin(physicalRoot, physicalCandidate);
|
|
1697
|
+
}
|
|
1698
|
+
async function codexPayloadPathSafe(payloadRoot, allowedRoot = payloadRoot) {
|
|
1699
|
+
if (!await codexReadPathSafe(allowedRoot, payloadRoot))
|
|
1700
|
+
return false;
|
|
1701
|
+
const rootStats = await lstatCodexResidueIfPresent(payloadRoot);
|
|
1702
|
+
if (!rootStats?.isDirectory() || rootStats.isSymbolicLink())
|
|
1703
|
+
return false;
|
|
1704
|
+
const pending = [payloadRoot];
|
|
1705
|
+
while (pending.length > 0) {
|
|
1706
|
+
const current = pending.pop();
|
|
1707
|
+
for (const entry of await fs.readdir(current, { withFileTypes: true })) {
|
|
1708
|
+
const entryPath = path.join(current, entry.name);
|
|
1709
|
+
if (entry.isSymbolicLink())
|
|
1710
|
+
return false;
|
|
1711
|
+
if (entry.isDirectory()) {
|
|
1712
|
+
pending.push(entryPath);
|
|
1713
|
+
}
|
|
1714
|
+
else if (!entry.isFile()) {
|
|
1715
|
+
return false;
|
|
1642
1716
|
}
|
|
1643
|
-
continue;
|
|
1644
|
-
}
|
|
1645
|
-
let residue;
|
|
1646
|
-
try {
|
|
1647
|
-
residue = await inspectCodexNativeResidue({ ...options, pluginName: entry.name });
|
|
1648
|
-
}
|
|
1649
|
-
catch (error) {
|
|
1650
|
-
throw new CodexNativeResidueProbeError(entryPath, error, pluginName);
|
|
1651
|
-
}
|
|
1652
|
-
if (residue.state !== 'present') {
|
|
1653
|
-
probeFailure(entryPath, new Error('Codex cache residue has foreign or incomplete manifest evidence.'), pluginName);
|
|
1654
1717
|
}
|
|
1655
|
-
names.add(pluginName);
|
|
1656
1718
|
}
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1719
|
+
return true;
|
|
1720
|
+
}
|
|
1721
|
+
class CodexNativeProjectionUnsafeError extends Error {
|
|
1722
|
+
}
|
|
1723
|
+
async function resolveCodexSkillsRoot(payloadRoot, rawSkillsRoot) {
|
|
1724
|
+
const normalizedSeparators = rawSkillsRoot.replace(/\\/gu, '/');
|
|
1725
|
+
const relativeRoot = path.posix.normalize(normalizedSeparators.replace(/^\.\//u, ''));
|
|
1726
|
+
if (!relativeRoot
|
|
1727
|
+
|| relativeRoot === '.'
|
|
1728
|
+
|| relativeRoot === '..'
|
|
1729
|
+
|| relativeRoot.startsWith('../')
|
|
1730
|
+
|| path.posix.isAbsolute(normalizedSeparators)
|
|
1731
|
+
|| path.win32.isAbsolute(rawSkillsRoot)) {
|
|
1732
|
+
throw new CodexNativeProjectionUnsafeError('codexNativeSkillsRootInvalid: Codex native Skills root is invalid.');
|
|
1733
|
+
}
|
|
1734
|
+
const root = path.join(payloadRoot, ...relativeRoot.split('/'));
|
|
1735
|
+
if (!await codexReadPathSafe(payloadRoot, root)) {
|
|
1736
|
+
throw new CodexNativeProjectionUnsafeError('codexNativeSkillsRootInvalid: Codex native Skills root is unsafe.');
|
|
1737
|
+
}
|
|
1738
|
+
return { relativeRoot, root };
|
|
1739
|
+
}
|
|
1740
|
+
function codexNativeRenderTargets(plugin) {
|
|
1741
|
+
return (plugin.manifest.artifacts?.targets ?? []).filter((target) => target.platform === 'codex'
|
|
1742
|
+
&& target.target === 'codex.skill'
|
|
1743
|
+
&& target.delivery?.mode === 'codex-native');
|
|
1744
|
+
}
|
|
1745
|
+
async function readCodexNativeRenderReceiptEvidence(payloadRoot) {
|
|
1746
|
+
const receiptPath = path.join(payloadRoot, CODEX_NATIVE_RENDER_RECEIPT_PATH);
|
|
1747
|
+
const stats = await lstatIfPresent(receiptPath);
|
|
1748
|
+
if (!stats)
|
|
1749
|
+
return { state: 'absent' };
|
|
1750
|
+
if (!stats.isFile() || stats.isSymbolicLink())
|
|
1751
|
+
return { state: 'unsafe' };
|
|
1752
|
+
let content;
|
|
1753
|
+
try {
|
|
1754
|
+
content = await fs.readFile(receiptPath, 'utf8');
|
|
1755
|
+
}
|
|
1756
|
+
catch {
|
|
1757
|
+
return { state: 'unreadable' };
|
|
1758
|
+
}
|
|
1759
|
+
try {
|
|
1760
|
+
return { state: 'present', receipt: parseCodexNativeRenderReceipt(content) };
|
|
1761
|
+
}
|
|
1762
|
+
catch {
|
|
1763
|
+
return { state: 'invalid' };
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
function codexNativeRenderEvidenceIssue(reason, diagnostic) {
|
|
1767
|
+
return { state: 'issue', reason, diagnostic };
|
|
1768
|
+
}
|
|
1769
|
+
async function readCodexNativeProjectionManifest(manifestPath) {
|
|
1770
|
+
let stats;
|
|
1771
|
+
try {
|
|
1772
|
+
stats = await lstatIfPresent(manifestPath);
|
|
1773
|
+
}
|
|
1774
|
+
catch {
|
|
1775
|
+
return { state: 'unreadable' };
|
|
1776
|
+
}
|
|
1777
|
+
if (!stats)
|
|
1778
|
+
return { state: 'missing' };
|
|
1779
|
+
if (!stats.isFile() || stats.isSymbolicLink())
|
|
1780
|
+
return { state: 'unsafe' };
|
|
1781
|
+
try {
|
|
1782
|
+
const value = await fs.readJson(manifestPath);
|
|
1783
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
1784
|
+
? { state: 'present', value: value }
|
|
1785
|
+
: { state: 'invalid' };
|
|
1786
|
+
}
|
|
1787
|
+
catch (error) {
|
|
1788
|
+
return { state: error instanceof SyntaxError ? 'invalid' : 'unreadable' };
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
async function resolveCodexNativeRenderExpectedEvidence(options) {
|
|
1792
|
+
const logicalPluginId = options.plugin.pluginId
|
|
1793
|
+
?? (options.plugin.sourceId ? `${options.plugin.sourceId}/${options.plugin.manifest.name}` : undefined);
|
|
1794
|
+
if (!logicalPluginId || !options.plugin.sourceId || !options.plugin.sourceIdentity) {
|
|
1795
|
+
throw new Error('Codex native render projection requires namespaced source identity.');
|
|
1796
|
+
}
|
|
1797
|
+
const marketplace = await readStrictNamespacedMarketplaceRoot({
|
|
1798
|
+
trustedMarketplaceRoot: path.join(resolveAwHome(options.env), 'marketplaces'),
|
|
1799
|
+
rootDir: options.marketplaceRoot,
|
|
1800
|
+
});
|
|
1801
|
+
if (!marketplace) {
|
|
1802
|
+
return codexNativeRenderEvidenceIssue('damaged', 'projectionReceiptMissing');
|
|
1803
|
+
}
|
|
1804
|
+
if (marketplace.config.sourceId !== options.plugin.sourceId
|
|
1805
|
+
|| marketplace.config.sourceIdentity !== options.plugin.sourceIdentity
|
|
1806
|
+
|| marketplace.config.marketplaceName !== options.marketplaceName) {
|
|
1807
|
+
return codexNativeRenderEvidenceIssue('foreign', 'projectionReceiptIdentityMismatch');
|
|
1808
|
+
}
|
|
1809
|
+
const payloadRoot = path.join(options.marketplaceRoot, 'plugins', options.plugin.manifest.name);
|
|
1810
|
+
const payloadStats = await lstatIfPresent(payloadRoot);
|
|
1811
|
+
if (!payloadStats) {
|
|
1812
|
+
return codexNativeRenderEvidenceIssue('damaged', 'projectionReceiptMissing');
|
|
1813
|
+
}
|
|
1814
|
+
if (!payloadStats.isDirectory() || payloadStats.isSymbolicLink()) {
|
|
1815
|
+
return codexNativeRenderEvidenceIssue('foreign', 'payloadPathUnsafe');
|
|
1816
|
+
}
|
|
1817
|
+
if (!await codexPayloadPathSafe(payloadRoot, options.marketplaceRoot)) {
|
|
1818
|
+
return codexNativeRenderEvidenceIssue('foreign', 'payloadPathUnsafe');
|
|
1819
|
+
}
|
|
1820
|
+
const codexManifestPath = path.join(payloadRoot, '.codex-plugin', 'plugin.json');
|
|
1821
|
+
const [sourceManifestEvidence, codexManifestEvidence] = await Promise.all([
|
|
1822
|
+
readCodexNativeProjectionManifest(path.join(payloadRoot, '.agents-plugin', 'plugin.json')),
|
|
1823
|
+
readCodexNativeProjectionManifest(codexManifestPath),
|
|
1824
|
+
]);
|
|
1825
|
+
if (sourceManifestEvidence.state === 'unsafe'
|
|
1826
|
+
|| codexManifestEvidence.state === 'unsafe') {
|
|
1827
|
+
return codexNativeRenderEvidenceIssue('foreign', 'payloadPathUnsafe');
|
|
1828
|
+
}
|
|
1829
|
+
if ((sourceManifestEvidence.state === 'present'
|
|
1830
|
+
&& sourceManifestEvidence.value.name !== options.plugin.manifest.name)
|
|
1831
|
+
|| (codexManifestEvidence.state === 'present'
|
|
1832
|
+
&& codexManifestEvidence.value.name !== options.plugin.manifest.name)
|
|
1833
|
+
|| (sourceManifestEvidence.state === 'present'
|
|
1834
|
+
&& codexManifestEvidence.state === 'present'
|
|
1835
|
+
&& sourceManifestEvidence.value.version !== codexManifestEvidence.value.version)) {
|
|
1836
|
+
return codexNativeRenderEvidenceIssue('foreign', 'manifestIdentityMismatch');
|
|
1837
|
+
}
|
|
1838
|
+
if (sourceManifestEvidence.state === 'unreadable'
|
|
1839
|
+
|| codexManifestEvidence.state === 'unreadable') {
|
|
1840
|
+
return codexNativeRenderEvidenceIssue('unreadable', 'manifestUnreadable');
|
|
1841
|
+
}
|
|
1842
|
+
if (sourceManifestEvidence.state === 'missing'
|
|
1843
|
+
|| codexManifestEvidence.state === 'missing') {
|
|
1844
|
+
return codexNativeRenderEvidenceIssue('damaged', 'manifestMissing');
|
|
1845
|
+
}
|
|
1846
|
+
if (sourceManifestEvidence.state === 'invalid'
|
|
1847
|
+
|| codexManifestEvidence.state === 'invalid') {
|
|
1848
|
+
return codexNativeRenderEvidenceIssue('damaged', 'manifestUnreadable');
|
|
1849
|
+
}
|
|
1850
|
+
if (sourceManifestEvidence.state !== 'present'
|
|
1851
|
+
|| codexManifestEvidence.state !== 'present') {
|
|
1852
|
+
return codexNativeRenderEvidenceIssue('damaged', 'manifestUnreadable');
|
|
1853
|
+
}
|
|
1854
|
+
const sourceManifest = sourceManifestEvidence.value;
|
|
1855
|
+
const codexManifest = codexManifestEvidence.value;
|
|
1856
|
+
if (typeof sourceManifest.version !== 'string'
|
|
1857
|
+
|| !tryParseStrictSemVer(sourceManifest.version)) {
|
|
1858
|
+
return codexNativeRenderEvidenceIssue('damaged', 'manifestVersionInvalid');
|
|
1859
|
+
}
|
|
1860
|
+
const rawSkillsRoot = typeof codexManifest.skills === 'string' && codexManifest.skills.trim() !== ''
|
|
1861
|
+
? codexManifest.skills
|
|
1862
|
+
: './codex/skills';
|
|
1863
|
+
const { relativeRoot: skillsRelativeRoot } = await resolveCodexSkillsRoot(payloadRoot, rawSkillsRoot);
|
|
1864
|
+
const skillsRoot = path.join(payloadRoot, ...skillsRelativeRoot.split('/'));
|
|
1865
|
+
const skillsRootStats = await lstatIfPresent(skillsRoot);
|
|
1866
|
+
if (skillsRootStats && (!skillsRootStats.isDirectory() || skillsRootStats.isSymbolicLink())) {
|
|
1867
|
+
return codexNativeRenderEvidenceIssue('foreign', 'payloadPathUnsafe');
|
|
1868
|
+
}
|
|
1869
|
+
const targets = codexNativeRenderTargets(options.plugin);
|
|
1870
|
+
for (const target of targets) {
|
|
1871
|
+
if (await lstatIfPresent(path.join(skillsRoot, target.skillKey, '.agent-workflows-owner.json'))) {
|
|
1872
|
+
return codexNativeRenderEvidenceIssue('foreign', 'payloadPathUnsafe');
|
|
1669
1873
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1874
|
+
}
|
|
1875
|
+
const receiptEvidence = await readCodexNativeRenderReceiptEvidence(payloadRoot);
|
|
1876
|
+
if (receiptEvidence.state === 'absent') {
|
|
1877
|
+
return codexNativeRenderEvidenceIssue('damaged', 'projectionReceiptMissing');
|
|
1878
|
+
}
|
|
1879
|
+
if (receiptEvidence.state === 'unsafe') {
|
|
1880
|
+
return codexNativeRenderEvidenceIssue('foreign', 'payloadPathUnsafe');
|
|
1881
|
+
}
|
|
1882
|
+
if (receiptEvidence.state === 'invalid') {
|
|
1883
|
+
return codexNativeRenderEvidenceIssue('damaged', 'projectionReceiptUnreadable');
|
|
1884
|
+
}
|
|
1885
|
+
if (receiptEvidence.state === 'unreadable') {
|
|
1886
|
+
return codexNativeRenderEvidenceIssue('unreadable', 'projectionReceiptUnreadable');
|
|
1887
|
+
}
|
|
1888
|
+
const receipt = receiptEvidence.receipt;
|
|
1889
|
+
if (receipt.logicalPluginId !== logicalPluginId
|
|
1890
|
+
|| receipt.strategy !== 'shared-user'
|
|
1891
|
+
|| receipt.sourceIdentity !== marketplace.config.sourceIdentity
|
|
1892
|
+
|| receipt.pluginVersion !== sourceManifest.version) {
|
|
1893
|
+
return codexNativeRenderEvidenceIssue('foreign', 'projectionReceiptIdentityMismatch');
|
|
1894
|
+
}
|
|
1895
|
+
const emptyArtifactDigest = createCodexSkillStagingTreeContentDigest({
|
|
1896
|
+
skillKey: 'empty',
|
|
1897
|
+
files: {},
|
|
1898
|
+
});
|
|
1899
|
+
for (const receiptTarget of receipt.targets) {
|
|
1900
|
+
const skillRoot = path.join(payloadRoot, ...skillsRelativeRoot.split('/'), receiptTarget.skillKey);
|
|
1901
|
+
if (await lstatIfPresent(path.join(skillRoot, '.agent-workflows-owner.json'))) {
|
|
1902
|
+
return codexNativeRenderEvidenceIssue('foreign', 'payloadPathUnsafe');
|
|
1673
1903
|
}
|
|
1674
|
-
const
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
probeFailure(manifestPath, new Error('Codex installed residue manifest is missing or nonregular.'), match[1]);
|
|
1904
|
+
const skillStats = await lstatIfPresent(skillRoot);
|
|
1905
|
+
if (skillStats && (!skillStats.isDirectory() || skillStats.isSymbolicLink())) {
|
|
1906
|
+
return codexNativeRenderEvidenceIssue('foreign', 'payloadPathUnsafe');
|
|
1678
1907
|
}
|
|
1679
|
-
let
|
|
1908
|
+
let tree;
|
|
1680
1909
|
try {
|
|
1681
|
-
|
|
1910
|
+
tree = skillStats ? await readDynamicSkillTree(skillRoot, 'codex.skill') : null;
|
|
1682
1911
|
}
|
|
1683
1912
|
catch (error) {
|
|
1684
|
-
|
|
1913
|
+
return codexNativeRenderEvidenceIssue(error instanceof Error && typeof error.code !== 'string'
|
|
1914
|
+
? 'damaged'
|
|
1915
|
+
: 'unreadable', 'projectionPayloadDigestMismatch');
|
|
1685
1916
|
}
|
|
1686
|
-
|
|
1687
|
-
|
|
1917
|
+
const artifactDigest = tree
|
|
1918
|
+
? createCodexSkillStagingTreeContentDigest(tree)
|
|
1919
|
+
: emptyArtifactDigest;
|
|
1920
|
+
if (artifactDigest !== receiptTarget.artifactDigest) {
|
|
1921
|
+
return codexNativeRenderEvidenceIssue('damaged', 'projectionPayloadDigestMismatch');
|
|
1688
1922
|
}
|
|
1689
|
-
const manifest = rawManifest;
|
|
1690
|
-
const identity = manifest.id ?? manifest.name;
|
|
1691
|
-
if (!canonicalMarketplace
|
|
1692
|
-
|| (identity !== match[1] && identity !== entry.name)
|
|
1693
|
-
|| typeof manifest.version !== 'string'
|
|
1694
|
-
|| manifest.version.trim() === '') {
|
|
1695
|
-
probeFailure(manifestPath, new Error('Codex installed residue manifest has foreign identity or version.'), match[1]);
|
|
1696
|
-
}
|
|
1697
|
-
names.add(match[1]);
|
|
1698
1923
|
}
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
code = 'codexNativeResidueProbeFailed';
|
|
1703
|
-
location;
|
|
1704
|
-
pluginName;
|
|
1705
|
-
constructor(location, cause, pluginName) {
|
|
1706
|
-
super(`codexNativeResidueProbeFailed: cannot classify Codex native residue at ${location}.`, {
|
|
1707
|
-
cause,
|
|
1708
|
-
});
|
|
1709
|
-
this.name = 'CodexNativeResidueProbeError';
|
|
1710
|
-
this.location = location;
|
|
1711
|
-
this.pluginName = pluginName;
|
|
1924
|
+
const payloadDigest = await calculateCodexNativeRenderPayloadDigest(payloadRoot);
|
|
1925
|
+
if (payloadDigest !== receipt.payloadDigest) {
|
|
1926
|
+
return codexNativeRenderEvidenceIssue('damaged', 'projectionPayloadDigestMismatch');
|
|
1712
1927
|
}
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
throw new Error(`Codex required Skills root 不存在:${skillsRoot}`);
|
|
1928
|
+
const receiptTargets = new Map(receipt.targets.map((target) => [target.id, target]));
|
|
1929
|
+
if (targets.length === 0
|
|
1930
|
+
|| targets.length !== receipt.targets.length
|
|
1931
|
+
|| targets.some((target) => receiptTargets.get(target.id)?.skillKey !== target.skillKey)) {
|
|
1932
|
+
return { state: 'present', payloadDigest, receipt, sourceProjection: 'drifted' };
|
|
1719
1933
|
}
|
|
1720
|
-
const
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1934
|
+
const renderer = await resolveArtifactRendererTarget(options.plugin);
|
|
1935
|
+
if (renderer.noOp || !renderer.rendererDigest) {
|
|
1936
|
+
throw new Error('Codex native render projection requires an artifact renderer.');
|
|
1937
|
+
}
|
|
1938
|
+
const config = await resolveArtifactRenderConfig({
|
|
1939
|
+
sourceId: options.plugin.sourceId,
|
|
1940
|
+
sourceIdentity: options.plugin.sourceIdentity,
|
|
1941
|
+
logicalIdentity: options.plugin.catalogLogicalIdentity,
|
|
1942
|
+
pluginName: options.plugin.manifest.name,
|
|
1943
|
+
manifest: options.plugin.manifest,
|
|
1944
|
+
scope: 'user',
|
|
1945
|
+
cwd: options.plugin.rootDir,
|
|
1946
|
+
env: options.env,
|
|
1725
1947
|
});
|
|
1726
|
-
|
|
1948
|
+
if (config.issues.length > 0) {
|
|
1949
|
+
throw new Error(`Invalid AW config for ${options.plugin.manifest.name}.`);
|
|
1950
|
+
}
|
|
1951
|
+
const projection = createCodexNativeRenderProjection({
|
|
1952
|
+
logicalPluginId,
|
|
1953
|
+
sourceIdentity: options.plugin.sourceIdentity,
|
|
1954
|
+
pluginVersion: options.plugin.manifest.version,
|
|
1955
|
+
rendererDigest: renderer.rendererDigest,
|
|
1956
|
+
targets: targets.map((target) => ({
|
|
1957
|
+
id: target.id,
|
|
1958
|
+
skillKey: target.skillKey,
|
|
1959
|
+
configDigest: createArtifactConfigDigest({
|
|
1960
|
+
artifact: {
|
|
1961
|
+
id: target.id,
|
|
1962
|
+
platform: 'codex',
|
|
1963
|
+
target: 'codex.skill',
|
|
1964
|
+
refreshKeys: target.refreshKeys,
|
|
1965
|
+
},
|
|
1966
|
+
renderConfig: config,
|
|
1967
|
+
digestSalt: renderer.rendererDigest,
|
|
1968
|
+
}),
|
|
1969
|
+
artifactDigest: receiptTargets.get(target.id).artifactDigest,
|
|
1970
|
+
})),
|
|
1971
|
+
});
|
|
1972
|
+
return {
|
|
1973
|
+
state: 'present',
|
|
1974
|
+
receipt,
|
|
1975
|
+
projection,
|
|
1976
|
+
payloadDigest,
|
|
1977
|
+
sourceProjection: codexNativeRenderProjectionsEqual(receipt, projection)
|
|
1978
|
+
? 'current'
|
|
1979
|
+
: 'drifted',
|
|
1980
|
+
};
|
|
1981
|
+
}
|
|
1982
|
+
async function assertNoSensitiveValuesInCanonicalCodexNativePayload(options) {
|
|
1983
|
+
if (options.sensitiveValues.length === 0
|
|
1984
|
+
|| !pluginHasCodexNativeSkillSurface(options.plugin))
|
|
1985
|
+
return;
|
|
1986
|
+
const sourceId = options.plugin.sourceId;
|
|
1987
|
+
if (!sourceId) {
|
|
1988
|
+
throw new Error('Codex native sensitive scan requires namespaced source identity.');
|
|
1989
|
+
}
|
|
1990
|
+
const awHome = resolveAwHome(options.env);
|
|
1991
|
+
if (!await isSafeCodexMarketplaceRoot({
|
|
1992
|
+
awHome,
|
|
1993
|
+
sourceId,
|
|
1994
|
+
marketplaceRoot: options.marketplaceRoot,
|
|
1995
|
+
})) {
|
|
1996
|
+
throw new Error('Codex native sensitive scan canonical marketplace path is unsafe.');
|
|
1997
|
+
}
|
|
1998
|
+
const payloadRoot = path.join(options.marketplaceRoot, 'plugins', options.plugin.manifest.name);
|
|
1999
|
+
if (!await codexPayloadPathSafe(payloadRoot, options.marketplaceRoot)) {
|
|
2000
|
+
throw new Error('Codex native sensitive scan payload path is unsafe.');
|
|
2001
|
+
}
|
|
2002
|
+
const receiptEvidence = await readCodexNativeRenderReceiptEvidence(payloadRoot);
|
|
2003
|
+
if (receiptEvidence.state === 'unsafe') {
|
|
2004
|
+
throw new Error('codexNativeRenderReceiptInvalid: Codex native render receipt path is unsafe.');
|
|
2005
|
+
}
|
|
2006
|
+
let verifiedRenderReceipt;
|
|
2007
|
+
if (receiptEvidence.state === 'present') {
|
|
2008
|
+
const expected = await resolveCodexNativeRenderExpectedEvidence({
|
|
2009
|
+
plugin: options.plugin,
|
|
2010
|
+
marketplaceRoot: options.marketplaceRoot,
|
|
2011
|
+
marketplaceName: options.marketplaceName,
|
|
2012
|
+
env: options.env,
|
|
2013
|
+
}).catch(() => undefined);
|
|
2014
|
+
if (expected?.state === 'present' && expected.sourceProjection === 'current') {
|
|
2015
|
+
verifiedRenderReceipt = expected.receipt;
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
await assertNoSensitiveValuesInCodexNativePayload({
|
|
2019
|
+
plugin: options.plugin,
|
|
2020
|
+
payloadRoot,
|
|
2021
|
+
allowedRoot: options.marketplaceRoot,
|
|
2022
|
+
sensitiveValues: options.sensitiveValues,
|
|
2023
|
+
verifiedRenderReceipt,
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
function observedCodexNativeState(observation, operation) {
|
|
2027
|
+
if (observation.state === 'observed') {
|
|
2028
|
+
return observation.value === 'installed' ? 'present' : 'absent';
|
|
2029
|
+
}
|
|
2030
|
+
if (observation.state === 'unobserved')
|
|
2031
|
+
return 'partial';
|
|
2032
|
+
throw new Error(`codexNativeObservationFailed: ${operation} requires Codex plugin list evidence.`);
|
|
2033
|
+
}
|
|
2034
|
+
function requireObservedCodexNativeState(observation, operation) {
|
|
2035
|
+
if (observation.state === 'observed')
|
|
2036
|
+
return observation.value;
|
|
2037
|
+
const code = observation.state === 'failed'
|
|
2038
|
+
? 'codexNativeObservationFailed'
|
|
2039
|
+
: 'codexNativeObservationUnavailable';
|
|
2040
|
+
throw new Error(`${code}: ${operation} requires Codex plugin list evidence.`);
|
|
2041
|
+
}
|
|
2042
|
+
export async function inspectCodexPluginRegistrationLive(options) {
|
|
2043
|
+
if (!options.nativeCliRunner) {
|
|
2044
|
+
return {
|
|
2045
|
+
observation: { state: 'unobserved' },
|
|
2046
|
+
observations: { user: false, project: false },
|
|
2047
|
+
activations: {},
|
|
2048
|
+
};
|
|
2049
|
+
}
|
|
2050
|
+
const ownedSession = options.observationSession ? undefined
|
|
2051
|
+
: await createCodexNativeObservationSession({
|
|
2052
|
+
exactCwd: options.cwd,
|
|
2053
|
+
env: options.env,
|
|
2054
|
+
runner: options.nativeCliRunner,
|
|
2055
|
+
manager: options.manager ?? SYSTEM_CODEX_MANAGER,
|
|
2056
|
+
});
|
|
2057
|
+
const session = options.observationSession ?? ownedSession;
|
|
2058
|
+
try {
|
|
2059
|
+
const inspected = await session.inspectPlugin({
|
|
2060
|
+
pluginId: options.pluginId,
|
|
2061
|
+
scope: options.scope ?? 'both',
|
|
2062
|
+
});
|
|
2063
|
+
return {
|
|
2064
|
+
observation: inspected.observation,
|
|
2065
|
+
observations: inspected.observations,
|
|
2066
|
+
activations: inspected.activations,
|
|
2067
|
+
};
|
|
2068
|
+
}
|
|
2069
|
+
finally {
|
|
2070
|
+
await ownedSession?.dispose();
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
/**
|
|
2074
|
+
* Read-only shared-native inspection. Official Codex list evidence owns installation
|
|
2075
|
+
* state. Filesystem validation is limited to the AW-owned marketplace source.
|
|
2076
|
+
*/
|
|
2077
|
+
export async function inspectCodexSharedNativeLive(options) {
|
|
2078
|
+
const inspectionMode = options.inspectionMode ?? 'payload';
|
|
2079
|
+
const dynamicProjectionExpected = options.nativeRenderPlugin !== undefined
|
|
2080
|
+
&& codexNativeRenderTargets(options.nativeRenderPlugin).length > 0;
|
|
2081
|
+
if (inspectionMode === 'payload'
|
|
2082
|
+
&& options.expectedPayloadDigest === undefined
|
|
2083
|
+
&& !dynamicProjectionExpected) {
|
|
2084
|
+
throw new Error('Payload inspection requires the expected shared native digest.');
|
|
2085
|
+
}
|
|
2086
|
+
const marketplaceName = options.marketplaceName ?? MARKETPLACE_NAME;
|
|
2087
|
+
const marketplaceRoot = options.marketplaceRoot ?? path.join(resolveAwHome(options.env), 'marketplaces', options.sourceId);
|
|
2088
|
+
const marketplacePathSafe = await isSafeCodexMarketplaceRoot({
|
|
2089
|
+
awHome: resolveAwHome(options.env),
|
|
2090
|
+
sourceId: options.sourceId,
|
|
2091
|
+
marketplaceRoot,
|
|
2092
|
+
});
|
|
2093
|
+
const pluginId = `${options.pluginName}@${marketplaceName}`;
|
|
2094
|
+
const config = await readCodexConfigFile(resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath);
|
|
2095
|
+
const pluginList = options.pluginRegistration ?? await inspectCodexPluginRegistrationLive({
|
|
2096
|
+
pluginId,
|
|
2097
|
+
cwd: options.cwd,
|
|
2098
|
+
env: options.env,
|
|
2099
|
+
nativeCliRunner: options.nativeCliRunner,
|
|
2100
|
+
manager: options.manager,
|
|
2101
|
+
scope: options.observationScope,
|
|
2102
|
+
observationSession: options.observationSession,
|
|
2103
|
+
});
|
|
2104
|
+
let expectedPayloadDigest = dynamicProjectionExpected ? undefined : options.expectedPayloadDigest;
|
|
2105
|
+
let sourceProjection;
|
|
2106
|
+
let sourceProjectionIssue;
|
|
2107
|
+
const installed = pluginList.observation.state === 'observed'
|
|
2108
|
+
&& pluginList.observation.value === 'installed';
|
|
2109
|
+
if (installed && dynamicProjectionExpected && options.nativeRenderPlugin) {
|
|
2110
|
+
try {
|
|
2111
|
+
const expected = await resolveCodexNativeRenderExpectedEvidence({
|
|
2112
|
+
plugin: options.nativeRenderPlugin,
|
|
2113
|
+
marketplaceRoot,
|
|
2114
|
+
marketplaceName,
|
|
2115
|
+
env: options.env,
|
|
2116
|
+
});
|
|
2117
|
+
if (expected.state === 'issue') {
|
|
2118
|
+
sourceProjectionIssue = expected;
|
|
2119
|
+
}
|
|
2120
|
+
else {
|
|
2121
|
+
expectedPayloadDigest = expected.payloadDigest;
|
|
2122
|
+
sourceProjection = expected.sourceProjection;
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
catch (error) {
|
|
2126
|
+
if (error instanceof MarketplaceRootValidationError) {
|
|
2127
|
+
sourceProjectionIssue = {
|
|
2128
|
+
reason: error.reason === 'unsafe'
|
|
2129
|
+
? 'foreign'
|
|
2130
|
+
: error.reason === 'invalid' ? 'damaged' : 'unreadable',
|
|
2131
|
+
diagnostic: error.reason === 'unsafe'
|
|
2132
|
+
? 'payloadPathUnsafe'
|
|
2133
|
+
: 'projectionReceiptUnreadable',
|
|
2134
|
+
};
|
|
2135
|
+
}
|
|
2136
|
+
else {
|
|
2137
|
+
sourceProjectionIssue = {
|
|
2138
|
+
reason: error instanceof CodexNativeProjectionUnsafeError ? 'foreign' : 'unreadable',
|
|
2139
|
+
diagnostic: error instanceof CodexNativeProjectionUnsafeError
|
|
2140
|
+
? 'payloadPathUnsafe'
|
|
2141
|
+
: 'projectionReceiptUnreadable',
|
|
2142
|
+
};
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
const sharedNative = inspectCodexSharedNative({
|
|
2147
|
+
pluginName: options.pluginName,
|
|
2148
|
+
pluginId,
|
|
2149
|
+
marketplaceName,
|
|
2150
|
+
canonicalMarketplaceRoot: marketplaceRoot,
|
|
2151
|
+
desiredVersion: options.desiredVersion,
|
|
2152
|
+
expectedPayloadDigest,
|
|
2153
|
+
marketplaceRegistration: config.snapshot.marketplaces[marketplaceName],
|
|
2154
|
+
marketplacePathSafe,
|
|
2155
|
+
observation: pluginList.observation,
|
|
2156
|
+
...(sourceProjection ? { sourceProjection } : {}),
|
|
2157
|
+
...(sourceProjectionIssue ? { sourceProjectionIssue } : {}),
|
|
2158
|
+
});
|
|
2159
|
+
return {
|
|
2160
|
+
sharedNative,
|
|
2161
|
+
...(expectedPayloadDigest ? { expectedPayloadDigest } : {}),
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
async function ensureCodexActiveSkillsManifest(options) {
|
|
2165
|
+
const relativeSkillsRoot = await resolveCodexSkillsRelativeRoot(options.plugin);
|
|
2166
|
+
const skillsRoot = path.join(options.marketplaceRoot, 'plugins', options.plugin.manifest.name, ...relativeSkillsRoot.split('/'));
|
|
2167
|
+
if (options.hasRequiredSkills && !await fs.pathExists(skillsRoot)) {
|
|
2168
|
+
throw new Error(`Codex required Skills root 不存在:${skillsRoot}`);
|
|
2169
|
+
}
|
|
2170
|
+
const manifestPath = path.join(options.marketplaceRoot, 'plugins', options.plugin.manifest.name, '.codex-plugin', 'plugin.json');
|
|
2171
|
+
const manifest = await fs.readJson(manifestPath).catch((error) => {
|
|
2172
|
+
if (error.code === 'ENOENT')
|
|
2173
|
+
return {};
|
|
2174
|
+
throw error;
|
|
2175
|
+
});
|
|
2176
|
+
const updated = {
|
|
1727
2177
|
...manifest,
|
|
1728
2178
|
name: options.plugin.manifest.name,
|
|
1729
2179
|
version: options.plugin.manifest.version,
|
|
@@ -1737,174 +2187,18 @@ async function ensureCodexActiveSkillsManifest(options) {
|
|
|
1737
2187
|
}
|
|
1738
2188
|
function createCodexMarketplaceComponent(options) {
|
|
1739
2189
|
const configPath = resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath;
|
|
1740
|
-
const inspectCanonicalMarketplace = async () => {
|
|
1741
|
-
const config = await readCodexConfigFile(configPath);
|
|
1742
|
-
const marketplace = config.snapshot.marketplaces[options.marketplaceName];
|
|
1743
|
-
if (!marketplace)
|
|
1744
|
-
return 'absent';
|
|
1745
|
-
if (marketplace.sourceType !== 'local'
|
|
1746
|
-
|| marketplace.sourcePointsToManifest
|
|
1747
|
-
|| path.normalize(marketplace.source ?? '') !== path.normalize(options.marketplaceRoot))
|
|
1748
|
-
return 'foreign';
|
|
1749
|
-
return 'present';
|
|
1750
|
-
};
|
|
1751
|
-
const hasRemainingPlugins = async () => {
|
|
1752
|
-
return (await discoverCodexNativeResidueNames({
|
|
1753
|
-
cwd: options.cwd,
|
|
1754
|
-
env: options.env,
|
|
1755
|
-
marketplaceName: options.marketplaceName,
|
|
1756
|
-
marketplaceRoot: options.marketplaceRoot,
|
|
1757
|
-
})).some((name) => name !== options.pluginName);
|
|
1758
|
-
};
|
|
1759
|
-
const inspect = async () => {
|
|
1760
|
-
const canonical = await inspectCanonicalMarketplace();
|
|
1761
|
-
const plugin = await inspectCodexNativeResidue({
|
|
1762
|
-
pluginName: options.pluginName,
|
|
1763
|
-
cwd: options.cwd,
|
|
1764
|
-
env: options.env,
|
|
1765
|
-
marketplaceName: options.marketplaceName,
|
|
1766
|
-
marketplaceRoot: options.marketplaceRoot,
|
|
1767
|
-
});
|
|
1768
|
-
if (plugin.state === 'foreign')
|
|
1769
|
-
return 'foreign';
|
|
1770
|
-
if (options.desired) {
|
|
1771
|
-
return canonical === 'present' && plugin.state === 'present' && plugin.enabled
|
|
1772
|
-
? 'present'
|
|
1773
|
-
: 'absent';
|
|
1774
|
-
}
|
|
1775
|
-
if (canonical !== 'present')
|
|
1776
|
-
return canonical;
|
|
1777
|
-
return await hasRemainingPlugins() ? 'absent' : 'present';
|
|
1778
|
-
};
|
|
1779
2190
|
return {
|
|
1780
2191
|
kind: 'shared-config',
|
|
1781
2192
|
identity: ownerIdentity(options.pluginName, `marketplace-${options.marketplaceName}`, 'codex', 'user'),
|
|
1782
2193
|
label: `${configPath}#marketplaces.${options.marketplaceName}`,
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
env: options.env,
|
|
1789
|
-
marketplaceName: options.marketplaceName,
|
|
1790
|
-
marketplaceRoot: options.marketplaceRoot,
|
|
1791
|
-
});
|
|
1792
|
-
if (currentResidue.state === 'foreign') {
|
|
1793
|
-
throw new Error(`Codex native owner 为 foreign,拒绝移除 marketplace:${currentResidue.labels.join(',')}`);
|
|
1794
|
-
}
|
|
1795
|
-
if (await inspectCanonicalMarketplace() !== 'present'
|
|
1796
|
-
|| currentResidue.state === 'present'
|
|
1797
|
-
|| await hasRemainingPlugins())
|
|
1798
|
-
return;
|
|
1799
|
-
await runNativeCliChecked(buildCodexMarketplaceRemoveInvocation(options.marketplaceName, options.cwd, options.env), options.runner);
|
|
1800
|
-
await runNativeCliChecked(buildCodexMarketplaceListInvocation(options.cwd, options.env), options.runner);
|
|
2194
|
+
locator: {
|
|
2195
|
+
kind: 'codex-marketplace',
|
|
2196
|
+
marketplaceName: options.marketplaceName,
|
|
2197
|
+
marketplaceRoot: options.marketplaceRoot,
|
|
2198
|
+
desired: options.desired,
|
|
1801
2199
|
},
|
|
1802
2200
|
};
|
|
1803
2201
|
}
|
|
1804
|
-
async function assertCodexNativeInstallState(options) {
|
|
1805
|
-
const marketplaceName = options.marketplaceName ?? MARKETPLACE_NAME;
|
|
1806
|
-
const pluginId = `${options.pluginName}@${marketplaceName}`;
|
|
1807
|
-
const configPath = resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath;
|
|
1808
|
-
const config = await readCodexConfigFile(configPath);
|
|
1809
|
-
const marketplace = config.snapshot.marketplaces[marketplaceName];
|
|
1810
|
-
if (marketplace?.sourceType !== 'local'
|
|
1811
|
-
|| marketplace.sourcePointsToManifest
|
|
1812
|
-
|| path.normalize(marketplace.source ?? '') !== path.normalize(options.marketplaceRoot)) {
|
|
1813
|
-
throw new Error(`Codex marketplace aw 未指向当前 AW_HOME:${options.marketplaceRoot}`);
|
|
1814
|
-
}
|
|
1815
|
-
if (config.snapshot.plugins[pluginId]?.enabled !== true) {
|
|
1816
|
-
throw new Error(`Codex enabled config 缺少 ${pluginId}。`);
|
|
1817
|
-
}
|
|
1818
|
-
const cacheRoot = path.join(getCodexHome(options.env), 'plugins', 'cache', marketplaceName, options.pluginName, options.pluginVersion);
|
|
1819
|
-
const candidates = [
|
|
1820
|
-
path.join(cacheRoot, 'plugin.json'),
|
|
1821
|
-
path.join(cacheRoot, '.codex-plugin', 'plugin.json'),
|
|
1822
|
-
];
|
|
1823
|
-
const expectedRoot = path.join(options.marketplaceRoot, 'plugins', options.pluginName);
|
|
1824
|
-
const assertExpectedPayload = async () => {
|
|
1825
|
-
const collectFiles = async (root) => {
|
|
1826
|
-
const output = [];
|
|
1827
|
-
const pending = [''];
|
|
1828
|
-
while (pending.length > 0) {
|
|
1829
|
-
const relativeRoot = pending.pop();
|
|
1830
|
-
for (const entry of await fs.readdir(path.join(root, relativeRoot), { withFileTypes: true })) {
|
|
1831
|
-
const relativePath = path.join(relativeRoot, entry.name);
|
|
1832
|
-
if (entry.isSymbolicLink()) {
|
|
1833
|
-
throw new Error(`Codex native payload 不能包含符号链接:${path.join(root, relativePath)}`);
|
|
1834
|
-
}
|
|
1835
|
-
if (entry.isDirectory())
|
|
1836
|
-
pending.push(relativePath);
|
|
1837
|
-
else if (entry.isFile())
|
|
1838
|
-
output.push(relativePath);
|
|
1839
|
-
else
|
|
1840
|
-
throw new Error(`Codex native payload 包含不支持的节点:${path.join(root, relativePath)}`);
|
|
1841
|
-
}
|
|
1842
|
-
}
|
|
1843
|
-
return output;
|
|
1844
|
-
};
|
|
1845
|
-
const manifestPaths = new Set(['plugin.json', path.join('.codex-plugin', 'plugin.json')]);
|
|
1846
|
-
const allowedMetadata = new Set(['metadata.json', '.metadata.json']);
|
|
1847
|
-
const [expectedFiles, actualFiles] = await Promise.all([
|
|
1848
|
-
collectFiles(expectedRoot),
|
|
1849
|
-
collectFiles(cacheRoot),
|
|
1850
|
-
]);
|
|
1851
|
-
const expectedSet = new Set(expectedFiles);
|
|
1852
|
-
for (const relativePath of expectedFiles) {
|
|
1853
|
-
if (manifestPaths.has(relativePath))
|
|
1854
|
-
continue;
|
|
1855
|
-
const expectedPath = path.join(expectedRoot, relativePath);
|
|
1856
|
-
const actualPath = path.join(cacheRoot, relativePath);
|
|
1857
|
-
const actualStats = await fs.lstat(actualPath).catch(() => null);
|
|
1858
|
-
if (!actualStats?.isFile() || actualStats.isSymbolicLink()) {
|
|
1859
|
-
throw new Error(`Codex native cache 缺少 expected payload:${actualPath}`);
|
|
1860
|
-
}
|
|
1861
|
-
const [expectedContent, actualContent] = await Promise.all([
|
|
1862
|
-
fs.readFile(expectedPath),
|
|
1863
|
-
fs.readFile(actualPath),
|
|
1864
|
-
]);
|
|
1865
|
-
if (!expectedContent.equals(actualContent)) {
|
|
1866
|
-
throw new Error(`Codex native cache expected payload digest 不匹配:${actualPath}`);
|
|
1867
|
-
}
|
|
1868
|
-
}
|
|
1869
|
-
for (const relativePath of actualFiles) {
|
|
1870
|
-
if (expectedSet.has(relativePath)
|
|
1871
|
-
|| manifestPaths.has(relativePath)
|
|
1872
|
-
|| allowedMetadata.has(relativePath))
|
|
1873
|
-
continue;
|
|
1874
|
-
throw new Error(`Codex native cache 包含 unexpected loadable payload:${path.join(cacheRoot, relativePath)}`);
|
|
1875
|
-
}
|
|
1876
|
-
};
|
|
1877
|
-
let matchedManifest = false;
|
|
1878
|
-
for (const candidate of candidates) {
|
|
1879
|
-
if (!await fs.pathExists(candidate))
|
|
1880
|
-
continue;
|
|
1881
|
-
const manifest = await fs.readJson(candidate).catch(() => null);
|
|
1882
|
-
if (!manifest) {
|
|
1883
|
-
throw new Error(`Codex native cache manifest 无法解析:${candidate}`);
|
|
1884
|
-
}
|
|
1885
|
-
const identity = manifest.id ?? manifest.name;
|
|
1886
|
-
const matches = ((identity === options.pluginName || identity === pluginId)
|
|
1887
|
-
&& manifest.version === options.pluginVersion
|
|
1888
|
-
&& (options.skillsRelativeRoot === null
|
|
1889
|
-
? typeof manifest.skills !== 'string'
|
|
1890
|
-
: typeof manifest.skills === 'string'
|
|
1891
|
-
&& path.posix.normalize(manifest.skills.replace(/^\.\//u, '')) === options.skillsRelativeRoot));
|
|
1892
|
-
if (!matches) {
|
|
1893
|
-
throw new Error(`Codex native cache manifest 不匹配:${candidate}`);
|
|
1894
|
-
}
|
|
1895
|
-
matchedManifest = true;
|
|
1896
|
-
}
|
|
1897
|
-
if (!matchedManifest) {
|
|
1898
|
-
throw new Error(`Codex native cache 缺少匹配 ${pluginId}@${options.pluginVersion} 的 manifest:${cacheRoot}`);
|
|
1899
|
-
}
|
|
1900
|
-
for (const skillKey of options.requiredSkillKeys) {
|
|
1901
|
-
const skillPath = path.join(cacheRoot, ...options.skillsRelativeRoot.split('/'), skillKey, 'SKILL.md');
|
|
1902
|
-
if (!await fs.pathExists(skillPath)) {
|
|
1903
|
-
throw new Error(`Codex native cache 缺少 required Skill:${skillPath}`);
|
|
1904
|
-
}
|
|
1905
|
-
}
|
|
1906
|
-
await assertExpectedPayload();
|
|
1907
|
-
}
|
|
1908
2202
|
async function readJsonObjectIfExists(filePath, label) {
|
|
1909
2203
|
let value;
|
|
1910
2204
|
try {
|
|
@@ -2210,128 +2504,459 @@ export async function inspectNativeMarketplaceRegistrations(options) {
|
|
|
2210
2504
|
return { codex, claude };
|
|
2211
2505
|
}
|
|
2212
2506
|
function createClaudeMarketplaceComponent(options) {
|
|
2213
|
-
const inspect = async () => {
|
|
2214
|
-
const state = await readClaudeNativeState(options);
|
|
2215
|
-
if (state.pluginState === 'foreign')
|
|
2216
|
-
return 'foreign';
|
|
2217
|
-
if (options.desired) {
|
|
2218
|
-
return state.marketplaceDeclaredState === 'canonical'
|
|
2219
|
-
&& state.marketplaceMaterializedState === 'canonical'
|
|
2220
|
-
&& state.pluginState === 'present'
|
|
2221
|
-
&& state.pluginEnabled
|
|
2222
|
-
? 'present'
|
|
2223
|
-
: 'absent';
|
|
2224
|
-
}
|
|
2225
|
-
if (state.marketplaceState !== 'canonical')
|
|
2226
|
-
return state.marketplaceState;
|
|
2227
|
-
const projectPath = options.scope === 'project'
|
|
2228
|
-
? path.normalize(await resolveRepoRoot(options.cwd))
|
|
2229
|
-
: undefined;
|
|
2230
|
-
return state.marketplaceConsumers.some((consumer) => !(consumer.name === options.pluginName
|
|
2231
|
-
&& consumer.scope === options.scope
|
|
2232
|
-
&& (options.scope === 'user' || path.normalize(consumer.projectPath ?? '') === projectPath)))
|
|
2233
|
-
? 'absent'
|
|
2234
|
-
: 'present';
|
|
2235
|
-
};
|
|
2236
2507
|
return {
|
|
2237
2508
|
kind: 'shared-config',
|
|
2238
2509
|
identity: ownerIdentity(options.pluginName, `marketplace-${options.marketplaceName}`, 'claude', options.scope),
|
|
2239
2510
|
label: `${resolveClaudePaths(options.cwd, options.scope, options.env).settingsPath}#marketplaces.${options.marketplaceName}`,
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
if (before.pluginState !== 'absent')
|
|
2246
|
-
return;
|
|
2247
|
-
if (before.marketplaceConsumers.length > 0)
|
|
2248
|
-
return;
|
|
2249
|
-
await runNativeCliChecked({
|
|
2250
|
-
command: 'claude',
|
|
2251
|
-
args: ['plugin', 'marketplace', 'remove', options.marketplaceName],
|
|
2252
|
-
cwd: options.cwd,
|
|
2253
|
-
env: options.env,
|
|
2254
|
-
}, options.runner);
|
|
2255
|
-
const after = await readClaudeNativeState(options);
|
|
2256
|
-
if (after.marketplaceState !== 'absent') {
|
|
2257
|
-
throw new Error('Claude marketplace remove 后 canonical aw source 仍存在。');
|
|
2258
|
-
}
|
|
2511
|
+
locator: {
|
|
2512
|
+
kind: 'claude-marketplace',
|
|
2513
|
+
marketplaceName: options.marketplaceName,
|
|
2514
|
+
marketplaceRoot: options.marketplaceRoot,
|
|
2515
|
+
desired: options.desired,
|
|
2259
2516
|
},
|
|
2260
2517
|
};
|
|
2261
2518
|
}
|
|
2262
|
-
|
|
2519
|
+
const codexNativeRenderPreparationCaches = new WeakMap();
|
|
2520
|
+
function codexNativeRenderPreparationCache(context) {
|
|
2521
|
+
let cache = codexNativeRenderPreparationCaches.get(context);
|
|
2522
|
+
if (!cache) {
|
|
2523
|
+
cache = new WeakMap();
|
|
2524
|
+
codexNativeRenderPreparationCaches.set(context, cache);
|
|
2525
|
+
}
|
|
2526
|
+
return cache;
|
|
2527
|
+
}
|
|
2528
|
+
function pluginHasCodexNativeSkillSurface(plugin) {
|
|
2529
|
+
return plugin.manifest.skills !== undefined
|
|
2530
|
+
|| codexNativeRenderTargets(plugin).length > 0;
|
|
2531
|
+
}
|
|
2532
|
+
async function resolveSensitiveUserConfigValues(plugin, env) {
|
|
2533
|
+
const hasSensitiveFields = Object.values(plugin.manifest.config?.fields ?? {}).some((field) => field.sensitive === true
|
|
2534
|
+
|| (field.type === 'object'
|
|
2535
|
+
&& (Object.values(field.properties ?? {}).some((child) => child.sensitive === true)
|
|
2536
|
+
|| (field.additionalProperties !== false
|
|
2537
|
+
&& field.additionalProperties?.sensitive === true))));
|
|
2538
|
+
if (!hasSensitiveFields)
|
|
2539
|
+
return [];
|
|
2540
|
+
const config = await resolveArtifactRenderConfig({
|
|
2541
|
+
sourceId: plugin.sourceId,
|
|
2542
|
+
sourceIdentity: plugin.sourceIdentity,
|
|
2543
|
+
pluginName: plugin.manifest.name,
|
|
2544
|
+
manifest: plugin.manifest,
|
|
2545
|
+
scope: 'user',
|
|
2546
|
+
cwd: plugin.rootDir,
|
|
2547
|
+
env,
|
|
2548
|
+
});
|
|
2549
|
+
return collectSensitivePluginConfigValues(plugin.manifest, config.values);
|
|
2550
|
+
}
|
|
2551
|
+
async function assertNoSensitiveValuesInCodexNativePayload(options) {
|
|
2552
|
+
if (options.sensitiveValues.length === 0)
|
|
2553
|
+
return;
|
|
2554
|
+
if (!await codexPayloadPathSafe(options.payloadRoot, options.allowedRoot)) {
|
|
2555
|
+
throw new Error('Codex native sensitive scan payload path is unsafe.');
|
|
2556
|
+
}
|
|
2557
|
+
const codexManifestPath = path.join(options.payloadRoot, '.codex-plugin', 'plugin.json');
|
|
2558
|
+
const codexManifestStats = await lstatCodexResidueIfPresent(codexManifestPath);
|
|
2559
|
+
if (!codexManifestStats?.isFile()) {
|
|
2560
|
+
throw new Error('Codex native sensitive scan manifest is missing or unsafe.');
|
|
2561
|
+
}
|
|
2562
|
+
const codexManifest = await fs.readJson(codexManifestPath).catch(() => null);
|
|
2563
|
+
if (!codexManifest
|
|
2564
|
+
|| codexManifest.name !== options.plugin.manifest.name
|
|
2565
|
+
|| codexManifest.version !== options.plugin.manifest.version) {
|
|
2566
|
+
throw new Error('Codex native sensitive scan manifest identity is invalid.');
|
|
2567
|
+
}
|
|
2568
|
+
const rawSkillsRoot = codexManifest.skills;
|
|
2569
|
+
if (rawSkillsRoot !== undefined) {
|
|
2570
|
+
if (typeof rawSkillsRoot !== 'string' || rawSkillsRoot.trim() === '') {
|
|
2571
|
+
throw new Error('Codex native sensitive scan Skills root is invalid.');
|
|
2572
|
+
}
|
|
2573
|
+
const { root: skillsRoot } = await resolveCodexSkillsRoot(options.payloadRoot, rawSkillsRoot);
|
|
2574
|
+
const skillsRootStats = await lstatCodexResidueIfPresent(skillsRoot);
|
|
2575
|
+
if (!skillsRootStats?.isDirectory()) {
|
|
2576
|
+
throw new Error('Codex native sensitive scan Skills root is missing or unsafe.');
|
|
2577
|
+
}
|
|
2578
|
+
for (const entry of await fs.readdir(skillsRoot, { withFileTypes: true })) {
|
|
2579
|
+
if (!entry.isDirectory())
|
|
2580
|
+
continue;
|
|
2581
|
+
const skillRoot = path.join(skillsRoot, entry.name);
|
|
2582
|
+
const skillFileStats = await lstatCodexResidueIfPresent(path.join(skillRoot, 'SKILL.md'));
|
|
2583
|
+
if (!skillFileStats)
|
|
2584
|
+
continue;
|
|
2585
|
+
if (!skillFileStats.isFile()) {
|
|
2586
|
+
throw new Error('Codex native sensitive scan Skill entry is unsafe.');
|
|
2587
|
+
}
|
|
2588
|
+
const tree = await readDynamicSkillTree(skillRoot, 'codex.skill');
|
|
2589
|
+
if (!tree)
|
|
2590
|
+
continue;
|
|
2591
|
+
const leakedPath = findSensitiveTokenPath(withoutVerifiedRequirementsReceipt(options.plugin, tree.files), options.sensitiveValues);
|
|
2592
|
+
if (leakedPath) {
|
|
2593
|
+
throw new Error(`codexNativeRenderSensitiveValue: Codex native Skill "${entry.name}" exposes ` +
|
|
2594
|
+
`sensitive config in "${leakedPath}".`);
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
const receiptPath = path.join(options.payloadRoot, CODEX_NATIVE_RENDER_RECEIPT_PATH);
|
|
2599
|
+
const receiptStats = await lstatCodexResidueIfPresent(receiptPath);
|
|
2600
|
+
if (receiptStats) {
|
|
2601
|
+
if (!receiptStats.isFile()) {
|
|
2602
|
+
throw new Error('Codex native sensitive scan render receipt path is unsafe.');
|
|
2603
|
+
}
|
|
2604
|
+
const receiptContent = await fs.readFile(receiptPath);
|
|
2605
|
+
let actualReceipt;
|
|
2606
|
+
try {
|
|
2607
|
+
actualReceipt = parseCodexNativeRenderReceipt(receiptContent.toString('utf8'));
|
|
2608
|
+
}
|
|
2609
|
+
catch {
|
|
2610
|
+
actualReceipt = undefined;
|
|
2611
|
+
}
|
|
2612
|
+
const verified = options.verifiedRenderReceipt
|
|
2613
|
+
&& JSON.stringify(actualReceipt) === JSON.stringify(options.verifiedRenderReceipt);
|
|
2614
|
+
if (!verified) {
|
|
2615
|
+
const receiptLeak = findSensitiveTokenPath({ [CODEX_NATIVE_RENDER_RECEIPT_PATH]: receiptContent }, options.sensitiveValues);
|
|
2616
|
+
if (receiptLeak) {
|
|
2617
|
+
throw new Error(`codexNativeRenderSensitiveValue: Codex native render receipt exposes sensitive config in ` +
|
|
2618
|
+
`"${receiptLeak}".`);
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
async function prepareCodexNativeRender(options) {
|
|
2624
|
+
const requested = options.requestedArtifactIds
|
|
2625
|
+
? new Set(options.requestedArtifactIds)
|
|
2626
|
+
: undefined;
|
|
2627
|
+
const targets = codexNativeRenderTargets(options.plugin)
|
|
2628
|
+
.filter((target) => !requested || requested.has(target.id));
|
|
2629
|
+
if (targets.length === 0)
|
|
2630
|
+
return null;
|
|
2631
|
+
const byScope = new Map();
|
|
2632
|
+
for (const target of targets) {
|
|
2633
|
+
const requestScope = target.scopes.includes('user') ? 'user' : 'project';
|
|
2634
|
+
const group = byScope.get(requestScope) ?? [];
|
|
2635
|
+
group.push(target);
|
|
2636
|
+
byScope.set(requestScope, group);
|
|
2637
|
+
}
|
|
2638
|
+
const renderedArtifacts = [];
|
|
2639
|
+
const receiptTargets = [];
|
|
2640
|
+
const presentSkillKeys = [];
|
|
2641
|
+
let rendererDigest;
|
|
2642
|
+
for (const scope of ['user', 'project']) {
|
|
2643
|
+
const group = byScope.get(scope);
|
|
2644
|
+
if (!group || group.length === 0)
|
|
2645
|
+
continue;
|
|
2646
|
+
const rendered = await runArtifactRenderer({
|
|
2647
|
+
event: 'install',
|
|
2648
|
+
plugin: options.plugin,
|
|
2649
|
+
platform: 'codex',
|
|
2650
|
+
scope,
|
|
2651
|
+
configScope: 'user',
|
|
2652
|
+
cwd: options.plugin.rootDir,
|
|
2653
|
+
commandEnv: options.env,
|
|
2654
|
+
previousArtifacts: [],
|
|
2655
|
+
requestedArtifactIds: group.map((target) => target.id),
|
|
2656
|
+
scriptTimeoutMs: options.scriptTimeoutMs,
|
|
2657
|
+
});
|
|
2658
|
+
rendererDigest = rendered.target.rendererDigest;
|
|
2659
|
+
const validated = validateArtifactPlan({
|
|
2660
|
+
manifest: options.plugin.manifest,
|
|
2661
|
+
plan: rendered.plan,
|
|
2662
|
+
platform: 'codex',
|
|
2663
|
+
scope,
|
|
2664
|
+
requestedArtifactIds: rendered.context.requestedArtifactIds,
|
|
2665
|
+
renderConfig: rendered.context.config,
|
|
2666
|
+
rendererDigest: rendered.target.rendererDigest,
|
|
2667
|
+
manifestDigest: rendered.target.manifestDigest,
|
|
2668
|
+
});
|
|
2669
|
+
for (const artifact of validated.artifacts) {
|
|
2670
|
+
if (artifact.target !== 'codex.skill') {
|
|
2671
|
+
throw new Error(`Codex native renderer returned non-Skill artifact "${artifact.id}".`);
|
|
2672
|
+
}
|
|
2673
|
+
const sourceDirs = rendered.context.artifactSources.find((entry) => entry.id === artifact.id && entry.target === artifact.target)?.sourceDirs ?? [];
|
|
2674
|
+
const tree = await createCodexSkillExpectedTree({
|
|
2675
|
+
artifact,
|
|
2676
|
+
sourceDirs,
|
|
2677
|
+
requireReadme: true,
|
|
2678
|
+
requirementsReceipt: requirementsReceipt(options.plugin),
|
|
2679
|
+
sourceRequirementsReceiptPolicy: 'allow',
|
|
2680
|
+
});
|
|
2681
|
+
assertNoSensitiveConfigInFiles(withoutVerifiedRequirementsReceipt(options.plugin, tree.files), collectSensitivePluginConfigValues(options.plugin.manifest, rendered.context.config.values), 'artifactRendererSensitiveConfigExposed: Final dynamic Skill contains sensitive config.');
|
|
2682
|
+
const skillKey = artifact.skillKey;
|
|
2683
|
+
renderedArtifacts.push({
|
|
2684
|
+
artifact,
|
|
2685
|
+
skillKey,
|
|
2686
|
+
tree,
|
|
2687
|
+
});
|
|
2688
|
+
if (artifact.state === 'present')
|
|
2689
|
+
presentSkillKeys.push(skillKey);
|
|
2690
|
+
receiptTargets.push({
|
|
2691
|
+
id: artifact.id,
|
|
2692
|
+
skillKey,
|
|
2693
|
+
configDigest: artifact.configDigest,
|
|
2694
|
+
artifactDigest: createCodexSkillStagingTreeContentDigest(tree),
|
|
2695
|
+
});
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
if (!rendererDigest || renderedArtifacts.length !== targets.length) {
|
|
2699
|
+
throw new Error('Codex native renderer did not produce the complete declared target set.');
|
|
2700
|
+
}
|
|
2701
|
+
const skillsRelativeRoot = await resolveCodexSkillsRelativeRoot(options.plugin);
|
|
2702
|
+
const logicalPluginId = options.plugin.pluginId
|
|
2703
|
+
?? (options.plugin.sourceId ? `${options.plugin.sourceId}/${options.plugin.manifest.name}` : undefined);
|
|
2704
|
+
if (!logicalPluginId || !options.plugin.sourceIdentity) {
|
|
2705
|
+
throw new Error('Codex native render materialization requires namespaced source identity.');
|
|
2706
|
+
}
|
|
2707
|
+
const projection = createCodexNativeRenderProjection({
|
|
2708
|
+
logicalPluginId,
|
|
2709
|
+
sourceIdentity: options.plugin.sourceIdentity,
|
|
2710
|
+
pluginVersion: options.plugin.manifest.version,
|
|
2711
|
+
rendererDigest,
|
|
2712
|
+
targets: receiptTargets,
|
|
2713
|
+
});
|
|
2714
|
+
return {
|
|
2715
|
+
logicalPluginId,
|
|
2716
|
+
sourceIdentity: options.plugin.sourceIdentity,
|
|
2717
|
+
rendererDigest,
|
|
2718
|
+
skillsRelativeRoot,
|
|
2719
|
+
renderedArtifacts,
|
|
2720
|
+
receiptTargets,
|
|
2721
|
+
projection,
|
|
2722
|
+
presentSkillKeys: presentSkillKeys.sort(),
|
|
2723
|
+
targetSkillKeys: targets.map((target) => target.skillKey).sort(),
|
|
2724
|
+
};
|
|
2725
|
+
}
|
|
2726
|
+
function prepareCodexNativeRenderCached(options) {
|
|
2727
|
+
const cache = codexNativeRenderPreparationCache(options.context);
|
|
2728
|
+
let preparation = cache.get(options.plugin);
|
|
2729
|
+
if (!preparation) {
|
|
2730
|
+
preparation = prepareCodexNativeRender({
|
|
2731
|
+
plugin: options.plugin,
|
|
2732
|
+
env: options.context.env,
|
|
2733
|
+
scriptTimeoutMs: options.scriptTimeoutMs,
|
|
2734
|
+
});
|
|
2735
|
+
cache.set(options.plugin, preparation);
|
|
2736
|
+
}
|
|
2737
|
+
return preparation;
|
|
2738
|
+
}
|
|
2739
|
+
async function requirePreparedCodexNativeRender(options) {
|
|
2740
|
+
if (codexNativeRenderTargets(options.plugin).length === 0)
|
|
2741
|
+
return null;
|
|
2742
|
+
const preparation = codexNativeRenderPreparationCache(options.context).get(options.plugin);
|
|
2743
|
+
if (!preparation) {
|
|
2744
|
+
throw new Error('codexNativeRenderPreflightMissing: Codex native renderer was not prepared.');
|
|
2745
|
+
}
|
|
2746
|
+
return preparation;
|
|
2747
|
+
}
|
|
2748
|
+
async function materializeCodexNativeRender(options) {
|
|
2749
|
+
const skillsRoot = path.join(options.stagingRoot, 'plugins', options.plugin.manifest.name, ...options.prepared.skillsRelativeRoot.split('/'));
|
|
2750
|
+
for (const { artifact, skillKey, tree } of options.prepared.renderedArtifacts) {
|
|
2751
|
+
const skillRoot = path.join(skillsRoot, skillKey);
|
|
2752
|
+
if (artifact.state === 'present') {
|
|
2753
|
+
await writeDynamicSkillTree(skillRoot, tree);
|
|
2754
|
+
}
|
|
2755
|
+
else {
|
|
2756
|
+
await fs.remove(skillRoot);
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
await ensureCodexActiveSkillsManifest({
|
|
2760
|
+
plugin: options.plugin,
|
|
2761
|
+
marketplaceRoot: options.stagingRoot,
|
|
2762
|
+
hasRequiredSkills: options.hasStaticSkills || options.prepared.presentSkillKeys.length > 0,
|
|
2763
|
+
});
|
|
2764
|
+
const pluginRoot = path.join(options.stagingRoot, 'plugins', options.plugin.manifest.name);
|
|
2765
|
+
const payloadDigest = await calculateCodexNativeRenderPayloadDigest(pluginRoot);
|
|
2766
|
+
const receipt = createCodexNativeRenderReceipt({
|
|
2767
|
+
logicalPluginId: options.prepared.logicalPluginId,
|
|
2768
|
+
sourceIdentity: options.prepared.sourceIdentity,
|
|
2769
|
+
pluginVersion: options.plugin.manifest.version,
|
|
2770
|
+
rendererDigest: options.prepared.rendererDigest,
|
|
2771
|
+
targets: options.prepared.receiptTargets,
|
|
2772
|
+
payloadDigest,
|
|
2773
|
+
});
|
|
2774
|
+
await fs.outputFile(path.join(pluginRoot, CODEX_NATIVE_RENDER_RECEIPT_PATH), serializeCodexNativeRenderReceipt(receipt), 'utf8');
|
|
2263
2775
|
return {
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2776
|
+
payloadDigest,
|
|
2777
|
+
projection: options.prepared.projection,
|
|
2778
|
+
receipt,
|
|
2779
|
+
presentSkillKeys: options.prepared.presentSkillKeys,
|
|
2780
|
+
targetSkillKeys: options.prepared.targetSkillKeys,
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
/**
|
|
2784
|
+
* Materialize only the selected Codex or Claude Skills into an isolated review root.
|
|
2785
|
+
* The caller owns outputRoot and is responsible for its cleanup.
|
|
2786
|
+
*/
|
|
2787
|
+
export async function materializeReviewNativeSkills(options) {
|
|
2788
|
+
if (options.skills.length === 0) {
|
|
2789
|
+
throw new Error('Review materialization requires at least one selected Skill.');
|
|
2790
|
+
}
|
|
2791
|
+
const skillsRoot = path.join(options.outputRoot, 'skills');
|
|
2792
|
+
await fs.ensureDir(skillsRoot);
|
|
2793
|
+
const declarations = options.plugin.manifest.artifacts?.targets ?? [];
|
|
2794
|
+
const dynamicSelections = options.skills.filter((skill) => skill.targetId !== null);
|
|
2795
|
+
const selectedDeclarations = dynamicSelections.map((selection) => {
|
|
2796
|
+
const matches = declarations.filter((target) => target.id === selection.targetId
|
|
2797
|
+
&& target.platform === options.platform
|
|
2798
|
+
&& target.scopes.includes(options.scope)
|
|
2799
|
+
&& target.skillKey === selection.skill
|
|
2800
|
+
&& target.target === `${options.platform}.skill`);
|
|
2801
|
+
if (matches.length !== 1) {
|
|
2802
|
+
throw new Error(`Selected ${options.platform} Skill target "${selection.targetId}" is missing or ambiguous.`);
|
|
2803
|
+
}
|
|
2804
|
+
return matches[0];
|
|
2805
|
+
});
|
|
2806
|
+
const selectedNames = options.skills.map((skill) => skill.skill);
|
|
2807
|
+
const disabledNativeSkillNames = new Set();
|
|
2808
|
+
if (new Set(selectedNames).size !== selectedNames.length) {
|
|
2809
|
+
throw new Error('Review materialization selected the same Skill more than once.');
|
|
2810
|
+
}
|
|
2811
|
+
for (const selection of options.skills.filter((skill) => skill.targetId === null)) {
|
|
2812
|
+
const sourceRoot = path.join(options.plugin.rootDir, options.platform, 'skills', selection.skill);
|
|
2813
|
+
const destination = path.join(skillsRoot, selection.skill);
|
|
2814
|
+
await fs.copy(sourceRoot, destination, {
|
|
2815
|
+
overwrite: false,
|
|
2816
|
+
errorOnExist: true,
|
|
2817
|
+
dereference: false,
|
|
2818
|
+
});
|
|
2819
|
+
}
|
|
2820
|
+
const directTargetIds = selectedDeclarations
|
|
2821
|
+
.filter((target) => target.delivery?.mode !== 'codex-native')
|
|
2822
|
+
.map((target) => target.id);
|
|
2823
|
+
if (directTargetIds.length > 0) {
|
|
2824
|
+
const dynamic = await resolveDynamicArtifacts({
|
|
2825
|
+
plugin: options.plugin,
|
|
2826
|
+
platform: options.platform,
|
|
2827
|
+
scope: options.scope,
|
|
2828
|
+
cwd: options.cwd,
|
|
2829
|
+
env: options.env,
|
|
2830
|
+
event: 'install',
|
|
2831
|
+
previousArtifacts: [],
|
|
2832
|
+
requestedArtifactIds: directTargetIds,
|
|
2833
|
+
scriptTimeoutMs: options.scriptTimeoutMs,
|
|
2834
|
+
});
|
|
2835
|
+
for (const artifact of dynamic.artifacts) {
|
|
2836
|
+
if (artifact.state !== 'present') {
|
|
2837
|
+
throw new Error(`Selected Skill target "${artifact.id}" did not produce a present Skill.`);
|
|
2838
|
+
}
|
|
2839
|
+
if (artifact.target !== `${options.platform}.skill`) {
|
|
2840
|
+
throw new Error(`Selected target "${artifact.id}" did not produce a ${options.platform} Skill.`);
|
|
2841
|
+
}
|
|
2842
|
+
let tree;
|
|
2843
|
+
if (artifact.target === 'codex.skill') {
|
|
2844
|
+
tree = await createCodexSkillExpectedTree({
|
|
2845
|
+
artifact,
|
|
2846
|
+
sourceDirs: dynamic.sourceDirs(artifact),
|
|
2847
|
+
requireReadme: true,
|
|
2848
|
+
requirementsReceipt: requirementsReceipt(options.plugin),
|
|
2849
|
+
sourceRequirementsReceiptPolicy: 'allow',
|
|
2276
2850
|
});
|
|
2277
|
-
versions = residue.enabled && residue.state === 'present' ? residue.versions : [];
|
|
2278
2851
|
}
|
|
2279
|
-
else {
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
marketplaceRoot: options.marketplaceRoot,
|
|
2852
|
+
else if (artifact.target === 'claude.skill') {
|
|
2853
|
+
tree = await createDynamicSkillExpectedTree({
|
|
2854
|
+
artifact,
|
|
2855
|
+
sourceDirs: dynamic.sourceDirs(artifact),
|
|
2856
|
+
requireReadme: true,
|
|
2857
|
+
requirementsReceipt: requirementsReceipt(options.plugin),
|
|
2858
|
+
sourceRequirementsReceiptPolicy: 'allow',
|
|
2287
2859
|
});
|
|
2288
|
-
versions = state.pluginEnabled && state.pluginState === 'present'
|
|
2289
|
-
? state.pluginVersions
|
|
2290
|
-
: [];
|
|
2291
2860
|
}
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2861
|
+
else {
|
|
2862
|
+
throw new Error(`Selected target "${artifact.id}" is not a Skill.`);
|
|
2863
|
+
}
|
|
2864
|
+
assertNoSensitiveConfigInFiles(withoutVerifiedRequirementsReceipt(options.plugin, tree.files), dynamic.sensitiveValues, 'artifactRendererSensitiveConfigExposed: Final dynamic Skill contains sensitive config.');
|
|
2865
|
+
await writeDynamicSkillTree(path.join(skillsRoot, tree.skillKey), tree);
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
const nativeTargetIds = selectedDeclarations
|
|
2869
|
+
.filter((target) => target.delivery?.mode === 'codex-native')
|
|
2870
|
+
.map((target) => target.id);
|
|
2871
|
+
if (nativeTargetIds.length > 0) {
|
|
2872
|
+
if (options.platform !== 'codex') {
|
|
2873
|
+
throw new Error('Codex native Skill targets are only valid for the Codex platform.');
|
|
2874
|
+
}
|
|
2875
|
+
const marketplaceRoot = path.join(options.outputRoot, '.native-marketplace');
|
|
2876
|
+
const payloadRoot = path.join(marketplaceRoot, 'plugins', options.plugin.manifest.name);
|
|
2877
|
+
await fs.copy(options.plugin.rootDir, payloadRoot, {
|
|
2878
|
+
overwrite: false,
|
|
2879
|
+
errorOnExist: true,
|
|
2880
|
+
dereference: false,
|
|
2881
|
+
});
|
|
2882
|
+
const prepared = await prepareCodexNativeRender({
|
|
2883
|
+
plugin: options.plugin,
|
|
2884
|
+
env: options.env,
|
|
2885
|
+
requestedArtifactIds: nativeTargetIds,
|
|
2886
|
+
scriptTimeoutMs: options.scriptTimeoutMs,
|
|
2887
|
+
});
|
|
2888
|
+
if (!prepared) {
|
|
2889
|
+
throw new Error('Selected Codex native Skill targets did not produce a projection.');
|
|
2890
|
+
}
|
|
2891
|
+
const staged = await materializeCodexNativeRender({
|
|
2892
|
+
plugin: options.plugin,
|
|
2893
|
+
stagingRoot: marketplaceRoot,
|
|
2894
|
+
prepared,
|
|
2895
|
+
hasStaticSkills: options.plugin.manifest.skills !== undefined,
|
|
2896
|
+
});
|
|
2897
|
+
await assertNoSensitiveValuesInCodexNativePayload({
|
|
2898
|
+
plugin: options.plugin,
|
|
2899
|
+
payloadRoot,
|
|
2900
|
+
allowedRoot: marketplaceRoot,
|
|
2901
|
+
sensitiveValues: await resolveSensitiveUserConfigValues(options.plugin, options.env),
|
|
2902
|
+
verifiedRenderReceipt: staged.receipt,
|
|
2903
|
+
});
|
|
2904
|
+
const presentSkillKeys = new Set(staged.presentSkillKeys);
|
|
2905
|
+
if (options.allowDisabledNativeSkills) {
|
|
2906
|
+
for (const target of selectedDeclarations.filter((entry) => entry.delivery?.mode === 'codex-native' && !presentSkillKeys.has(entry.skillKey))) {
|
|
2907
|
+
disabledNativeSkillNames.add(target.skillKey);
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
for (const target of selectedDeclarations.filter((entry) => entry.delivery?.mode === 'codex-native' && presentSkillKeys.has(entry.skillKey))) {
|
|
2911
|
+
const sourceRoot = path.join(payloadRoot, ...prepared.skillsRelativeRoot.split('/'), target.skillKey);
|
|
2912
|
+
await fs.copy(sourceRoot, path.join(skillsRoot, target.skillKey), {
|
|
2913
|
+
overwrite: false,
|
|
2914
|
+
errorOnExist: true,
|
|
2915
|
+
dereference: false,
|
|
2916
|
+
});
|
|
2917
|
+
}
|
|
2918
|
+
await fs.remove(marketplaceRoot);
|
|
2919
|
+
}
|
|
2920
|
+
const skills = [];
|
|
2921
|
+
for (const skill of selectedNames.sort((left, right) => left.localeCompare(right))) {
|
|
2922
|
+
const skillRoot = path.join(skillsRoot, skill);
|
|
2923
|
+
if (disabledNativeSkillNames.has(skill))
|
|
2924
|
+
continue;
|
|
2925
|
+
await validateSkillDirectory(skillRoot, `${options.platform}.skill`, {
|
|
2926
|
+
requirementsReceiptPolicy: 'allow',
|
|
2927
|
+
});
|
|
2928
|
+
await validateRuntimeSkillClosure(skillRoot);
|
|
2929
|
+
skills.push({ skill, path: skillRoot });
|
|
2930
|
+
}
|
|
2931
|
+
return { rootDir: options.outputRoot, skills };
|
|
2296
2932
|
}
|
|
2297
|
-
async function mirrorCatalog(catalog, env, target) {
|
|
2933
|
+
async function mirrorCatalog(catalog, env, target, logicalIdentity, prepareStaging, rematerializedCodexNativePluginIds = new Set()) {
|
|
2298
2934
|
if (!target?.sourceId || !target.marketplaceRoot) {
|
|
2299
2935
|
const rootDir = resolveAwHome(env);
|
|
2300
2936
|
await fs.ensureDir(rootDir);
|
|
2301
2937
|
await fs.copy(catalog.rootDir, rootDir, { overwrite: true });
|
|
2938
|
+
await prepareStaging?.(rootDir);
|
|
2302
2939
|
return { rootDir, commit: async () => undefined, rollback: async () => undefined };
|
|
2303
2940
|
}
|
|
2304
2941
|
const awHome = path.resolve(resolveAwHome(env));
|
|
2305
|
-
const awHomeStats = await fs.lstat(awHome).catch((error) => {
|
|
2306
|
-
if (error.code === 'ENOENT')
|
|
2307
|
-
return null;
|
|
2308
|
-
throw error;
|
|
2309
|
-
});
|
|
2310
|
-
if (!awHomeStats?.isDirectory() || awHomeStats.isSymbolicLink()) {
|
|
2311
|
-
throw new Error(`marketplaceRootInvalid: AW_HOME must be a regular directory: ${awHome}`);
|
|
2312
|
-
}
|
|
2313
|
-
const canonicalAwHome = await fs.realpath(awHome);
|
|
2314
|
-
const expectedParent = path.join(canonicalAwHome, 'marketplaces');
|
|
2315
2942
|
const destination = target.marketplaceRoot;
|
|
2316
2943
|
const parent = path.dirname(destination);
|
|
2317
|
-
if (
|
|
2318
|
-
|
|
2944
|
+
if (!await isSafeCodexMarketplaceRoot({
|
|
2945
|
+
awHome,
|
|
2946
|
+
sourceId: target.sourceId,
|
|
2947
|
+
marketplaceRoot: destination,
|
|
2948
|
+
})) {
|
|
2949
|
+
throw new Error(`marketplaceRootInvalid: marketplace root is unsafe: ${destination}`);
|
|
2319
2950
|
}
|
|
2320
|
-
|
|
2321
|
-
if (error.code === 'ENOENT')
|
|
2322
|
-
return null;
|
|
2323
|
-
throw error;
|
|
2324
|
-
});
|
|
2325
|
-
if (!parentStats) {
|
|
2951
|
+
if (!await fs.pathExists(parent)) {
|
|
2326
2952
|
await fs.mkdir(parent);
|
|
2327
|
-
parentStats = await fs.lstat(parent);
|
|
2328
2953
|
}
|
|
2329
|
-
if (!
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
throw new Error(`marketplaceRootInvalid: marketplace
|
|
2954
|
+
if (!await isSafeCodexMarketplaceRoot({
|
|
2955
|
+
awHome,
|
|
2956
|
+
sourceId: target.sourceId,
|
|
2957
|
+
marketplaceRoot: destination,
|
|
2958
|
+
})) {
|
|
2959
|
+
throw new Error(`marketplaceRootInvalid: marketplace root became unsafe: ${destination}`);
|
|
2335
2960
|
}
|
|
2336
2961
|
const staging = await fs.mkdtemp(path.join(parent, `.${target.sourceId}-staging-`));
|
|
2337
2962
|
try {
|
|
@@ -2353,6 +2978,7 @@ async function mirrorCatalog(catalog, env, target) {
|
|
|
2353
2978
|
schemaVersion: 2,
|
|
2354
2979
|
sourceId: target.sourceId,
|
|
2355
2980
|
sourceIdentity: target.sourceIdentity,
|
|
2981
|
+
logicalIdentity,
|
|
2356
2982
|
marketplaceName,
|
|
2357
2983
|
}, { spaces: 2 });
|
|
2358
2984
|
const destinationExists = await fs.lstat(destination).then(() => true, (error) => {
|
|
@@ -2368,7 +2994,16 @@ async function mirrorCatalog(catalog, env, target) {
|
|
|
2368
2994
|
sourceIdentity: target.sourceIdentity,
|
|
2369
2995
|
marketplaceName,
|
|
2370
2996
|
});
|
|
2997
|
+
await replayNamespacedCodexNativeRenderProjections({
|
|
2998
|
+
sourceRoot: destination,
|
|
2999
|
+
stagingRoot: staging,
|
|
3000
|
+
sourceId: target.sourceId,
|
|
3001
|
+
sourceIdentity: target.sourceIdentity,
|
|
3002
|
+
marketplaceName,
|
|
3003
|
+
rematerializedPluginIds: rematerializedCodexNativePluginIds,
|
|
3004
|
+
});
|
|
2371
3005
|
}
|
|
3006
|
+
await prepareStaging?.(staging);
|
|
2372
3007
|
const backup = `${destination}.backup-${crypto.randomUUID()}`;
|
|
2373
3008
|
if (destinationExists)
|
|
2374
3009
|
await fs.move(destination, backup);
|
|
@@ -2434,70 +3069,651 @@ async function mirrorCatalog(catalog, env, target) {
|
|
|
2434
3069
|
throw error;
|
|
2435
3070
|
}
|
|
2436
3071
|
}
|
|
3072
|
+
async function applyPlannedOperationAction(action) {
|
|
3073
|
+
switch (action.kind) {
|
|
3074
|
+
case 'writeOwnedArtifact': {
|
|
3075
|
+
if (!action.owner || !action.allowedRoot || !action.artifactKind) {
|
|
3076
|
+
throw new Error('Owned artifact action is missing owner or locator data.');
|
|
3077
|
+
}
|
|
3078
|
+
if (action.artifactKind === 'owned-text-file') {
|
|
3079
|
+
if (action.content === undefined)
|
|
3080
|
+
throw new Error('Owned text action is missing content.');
|
|
3081
|
+
const digest = `sha256:${crypto.createHash('sha256').update(action.content).digest('hex')}`;
|
|
3082
|
+
if (digest !== action.contentDigest)
|
|
3083
|
+
throw new Error('Owned text action content digest mismatch.');
|
|
3084
|
+
if (action.sourceContent === undefined)
|
|
3085
|
+
throw new Error('Owned text action is missing source content.');
|
|
3086
|
+
await fs.ensureDir(action.allowedRoot);
|
|
3087
|
+
await writeOwnedTextFile({
|
|
3088
|
+
path: action.path,
|
|
3089
|
+
allowedRoot: action.allowedRoot,
|
|
3090
|
+
identity: action.owner,
|
|
3091
|
+
content: action.sourceContent,
|
|
3092
|
+
});
|
|
3093
|
+
return;
|
|
3094
|
+
}
|
|
3095
|
+
const files = action.files ?? [];
|
|
3096
|
+
const digest = `sha256:${crypto.createHash('sha256').update(JSON.stringify(files)).digest('hex')}`;
|
|
3097
|
+
if (digest !== action.contentDigest)
|
|
3098
|
+
throw new Error('Skill action content digest mismatch.');
|
|
3099
|
+
const treeFiles = {};
|
|
3100
|
+
for (const file of files) {
|
|
3101
|
+
const content = Buffer.from(file.contentBase64, 'base64');
|
|
3102
|
+
if (`sha256:${crypto.createHash('sha256').update(content).digest('hex')}` !== file.contentDigest) {
|
|
3103
|
+
throw new Error(`Skill action file digest mismatch: ${file.relativePath}`);
|
|
3104
|
+
}
|
|
3105
|
+
if (file.relativePath !== '.agent-workflows-owner.json')
|
|
3106
|
+
treeFiles[file.relativePath] = content;
|
|
3107
|
+
}
|
|
3108
|
+
await writeDynamicSkillTree(action.path, {
|
|
3109
|
+
skillKey: action.owner.artifactKey,
|
|
3110
|
+
files: treeFiles,
|
|
3111
|
+
}, {
|
|
3112
|
+
allowedRoot: action.allowedRoot,
|
|
3113
|
+
identity: action.owner,
|
|
3114
|
+
allowPristineUnowned: action.allowPristineUnowned === true,
|
|
3115
|
+
});
|
|
3116
|
+
return;
|
|
3117
|
+
}
|
|
3118
|
+
case 'updateSharedConfig': {
|
|
3119
|
+
const mutation = action.mutation;
|
|
3120
|
+
switch (mutation.kind) {
|
|
3121
|
+
case 'claude-permissions': {
|
|
3122
|
+
if (action.locator.kind !== 'claude-permissions')
|
|
3123
|
+
throw new Error('Claude permission locator mismatch.');
|
|
3124
|
+
const settings = await readClaudeSettings(action.locator.settingsPath);
|
|
3125
|
+
rebuildClaudePermissions(settings, action.locator.ownerKey, mutation.permissions);
|
|
3126
|
+
await writeClaudeSettings(action.locator.settingsPath, settings);
|
|
3127
|
+
return;
|
|
3128
|
+
}
|
|
3129
|
+
case 'codex-instruction': {
|
|
3130
|
+
if (action.locator.kind !== 'codex-instruction')
|
|
3131
|
+
throw new Error('Codex instruction locator mismatch.');
|
|
3132
|
+
await applyCodexConfigTransactionFile(action.locator.configPath, mutation.block
|
|
3133
|
+
? { upsertManagedInstructionBlock: mutation.block }
|
|
3134
|
+
: { removeManagedInstructionPluginName: action.locator.ownerKey });
|
|
3135
|
+
return;
|
|
3136
|
+
}
|
|
3137
|
+
case 'codex-role-registration': {
|
|
3138
|
+
if (action.locator.kind !== 'codex-role-registration')
|
|
3139
|
+
throw new Error('Codex role locator mismatch.');
|
|
3140
|
+
const locator = action.locator;
|
|
3141
|
+
const registration = {
|
|
3142
|
+
description: mutation.description,
|
|
3143
|
+
configFile: mutation.configFile.split(path.sep).join('/'),
|
|
3144
|
+
};
|
|
3145
|
+
await applyConditionalCodexConfigTransactionFile(locator.configPath, async (snapshot) => {
|
|
3146
|
+
const actual = snapshot.agents[mutation.roleKey];
|
|
3147
|
+
if (actual && !await codexRoleRegistrationOwned({
|
|
3148
|
+
fileIdentity: locator.fileIdentity,
|
|
3149
|
+
configPath: locator.configPath,
|
|
3150
|
+
roleKey: mutation.roleKey,
|
|
3151
|
+
rolePath: locator.rolePath,
|
|
3152
|
+
registration: actual,
|
|
3153
|
+
})) {
|
|
3154
|
+
throw new Error(`Codex agent registration "${mutation.roleKey}" is foreign and cannot be overwritten by plugin "${action.owner.plugin}".`);
|
|
3155
|
+
}
|
|
3156
|
+
return { upsertAgentRegistrations: { [mutation.roleKey]: registration } };
|
|
3157
|
+
});
|
|
3158
|
+
return;
|
|
3159
|
+
}
|
|
3160
|
+
case 'codex-activation':
|
|
3161
|
+
throw new Error('Codex activation must use a setActivation action.');
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
case 'setActivation':
|
|
3165
|
+
if (!action.configPath || !action.registration) {
|
|
3166
|
+
throw new Error('Activation action is missing configPath or registration.');
|
|
3167
|
+
}
|
|
3168
|
+
await applyCodexConfigTransactionFile(action.configPath, {
|
|
3169
|
+
upsertPluginActivations: { [action.registration]: action.enabled },
|
|
3170
|
+
});
|
|
3171
|
+
return;
|
|
3172
|
+
default:
|
|
3173
|
+
throw new Error(`Operation action ${action.kind} is not an artifact write action.`);
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
2437
3176
|
function createNativeDriver(options) {
|
|
2438
3177
|
const runner = options.context.nativeCliRunner;
|
|
2439
|
-
const
|
|
2440
|
-
const
|
|
2441
|
-
const
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
3178
|
+
const currentCodexManager = () => options.context.codexManager;
|
|
3179
|
+
const nativeGroupPreparations = new Map();
|
|
3180
|
+
const nativeTargetPreparations = new WeakMap();
|
|
3181
|
+
const codexNativeStates = new Map();
|
|
3182
|
+
const pendingCodexNativeStates = new Map();
|
|
3183
|
+
const codexMutationMarketplaces = new Set();
|
|
3184
|
+
const committedNativeGroups = new Set();
|
|
3185
|
+
let nativeMarketplaceEligibleTargets;
|
|
3186
|
+
let deferredPostchecks = [];
|
|
3187
|
+
const nativeGroupKey = (target) => [
|
|
3188
|
+
target.identity.sourceId ?? '',
|
|
3189
|
+
target.identity.sourceIdentity ?? '',
|
|
3190
|
+
target.identity.marketplaceName ?? MARKETPLACE_NAME,
|
|
3191
|
+
path.normalize(path.resolve(target.identity.marketplaceRoot ?? resolveAwHome(options.context.env))),
|
|
3192
|
+
target.identity.platform,
|
|
3193
|
+
].join('\0');
|
|
3194
|
+
const observationScopeFor = (target) => codexObservationScope(options.context, target.identity.marketplaceName ?? MARKETPLACE_NAME, target.identity.scope);
|
|
3195
|
+
const groupTargets = (target) => {
|
|
3196
|
+
if (!options.operationScopedNativePhases)
|
|
3197
|
+
return [target];
|
|
3198
|
+
const key = nativeGroupKey(target);
|
|
3199
|
+
return options.targets.filter((candidate) => nativeGroupKey(candidate) === key
|
|
3200
|
+
&& (nativeMarketplaceEligibleTargets?.has(candidate) ?? true));
|
|
3201
|
+
};
|
|
3202
|
+
const asNativeGroupFailure = (error) => {
|
|
3203
|
+
const result = error instanceof Error ? error : new Error(String(error));
|
|
3204
|
+
return Object.assign(result, { nativeMarketplaceGroupFailure: true });
|
|
3205
|
+
};
|
|
3206
|
+
const findTarget = (component) => {
|
|
3207
|
+
const target = options.targets.find((entry) => entry.identity.pluginName === component.identity.plugin
|
|
3208
|
+
&& entry.identity.sourceId === component.identity.sourceId
|
|
3209
|
+
&& entry.identity.platform === component.identity.platform
|
|
3210
|
+
&& entry.identity.scope === component.identity.scope);
|
|
3211
|
+
if (!target)
|
|
3212
|
+
throw new Error('Ownership executor cannot resolve its desired target.');
|
|
3213
|
+
return target;
|
|
3214
|
+
};
|
|
3215
|
+
const codexStateKey = (target, pluginId) => `${nativeGroupKey(target)}\0${pluginId}`;
|
|
3216
|
+
const runObservedCodexMutation = async (options_) => runCodexMutationWithInvalidation({
|
|
3217
|
+
...options_,
|
|
3218
|
+
...(options.operationScopedNativePhases
|
|
3219
|
+
? {}
|
|
3220
|
+
: { observationSession: options.context.codexObservationSession }),
|
|
3221
|
+
});
|
|
3222
|
+
const prepareNativeGroup = async (target) => {
|
|
3223
|
+
const key = nativeGroupKey(target);
|
|
3224
|
+
let preparation = options.operationScopedNativePhases
|
|
3225
|
+
? nativeGroupPreparations.get(key)
|
|
3226
|
+
: nativeTargetPreparations.get(target);
|
|
3227
|
+
if (!preparation) {
|
|
3228
|
+
preparation = (async () => {
|
|
3229
|
+
const members = groupTargets(target);
|
|
3230
|
+
const materializing = members.filter((candidate) => candidate.writeNativeRequired ?? candidate.requiresNative);
|
|
3231
|
+
const overlayTargets = members.filter((candidate) => candidate.nativeOverlayDesired ?? candidate.requiresNative);
|
|
3232
|
+
if (target.identity.platform === 'codex') {
|
|
3233
|
+
const observationScope = codexObservationScope(options.context, target.identity.marketplaceName ?? MARKETPLACE_NAME, members.some((candidate) => candidate.identity.scope === 'project') ? 'project' : 'user');
|
|
3234
|
+
const nativePluginIds = [...new Set(members.flatMap((candidate) => candidate.ownedComponents.flatMap((component) => component.kind === 'native-plugin' ? [component.pluginId] : [])))].sort();
|
|
3235
|
+
for (const pluginId of nativePluginIds) {
|
|
3236
|
+
const member = members.find((candidate) => candidate.ownedComponents.some((component) => component.kind === 'native-plugin' && component.pluginId === pluginId));
|
|
3237
|
+
const registration = await inspectCodexPluginRegistrationLive({
|
|
3238
|
+
pluginId,
|
|
3239
|
+
cwd: member.identity.cwd,
|
|
3240
|
+
env: options.context.env,
|
|
3241
|
+
nativeCliRunner: runner,
|
|
3242
|
+
manager: currentCodexManager(),
|
|
3243
|
+
scope: observationScope,
|
|
3244
|
+
observationSession: options.context.codexObservationSession,
|
|
3245
|
+
});
|
|
3246
|
+
codexNativeStates.set(codexStateKey(member, pluginId), requireObservedCodexNativeState(registration.observation, 'native group precheck') === 'installed' ? 'present' : 'absent');
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
if (materializing.length === 0)
|
|
3250
|
+
return;
|
|
3251
|
+
const first = materializing[0];
|
|
3252
|
+
if (!first.catalog)
|
|
3253
|
+
throw new Error('Missing rebuild target catalog data.');
|
|
3254
|
+
const catalogRoot = path.resolve(first.catalog.rootDir);
|
|
3255
|
+
if (overlayTargets.some((candidate) => !candidate.catalog || path.resolve(candidate.catalog.rootDir) !== catalogRoot)) {
|
|
3256
|
+
throw new Error('nativeMarketplaceGroupInvalid: grouped targets do not share one Catalog root.');
|
|
3257
|
+
}
|
|
3258
|
+
const materializationTargets = [...overlayTargets].sort((left, right) => [
|
|
3259
|
+
left.identity.pluginId ?? left.identity.pluginName,
|
|
3260
|
+
left.identity.scope,
|
|
3261
|
+
left.identity.cwd,
|
|
3262
|
+
].join('\0').localeCompare([
|
|
3263
|
+
right.identity.pluginId ?? right.identity.pluginName,
|
|
3264
|
+
right.identity.scope,
|
|
3265
|
+
right.identity.cwd,
|
|
3266
|
+
].join('\0'), 'en'));
|
|
3267
|
+
const transaction = await mirrorCatalog(first.catalog, options.context.env, first.identity, first.plugin.catalogLogicalIdentity, first.identity.platform === 'codex' ? async (stagingRoot) => {
|
|
3268
|
+
for (const candidate of materializationTargets) {
|
|
3269
|
+
const hasStaticSkills = candidate.plugin.manifest.skills !== undefined;
|
|
3270
|
+
const preparedNativeRender = await requirePreparedCodexNativeRender({
|
|
3271
|
+
context: options.context,
|
|
3272
|
+
plugin: candidate.plugin,
|
|
3273
|
+
});
|
|
3274
|
+
const nativeRender = preparedNativeRender
|
|
3275
|
+
? await materializeCodexNativeRender({
|
|
3276
|
+
plugin: candidate.plugin,
|
|
3277
|
+
stagingRoot,
|
|
3278
|
+
prepared: preparedNativeRender,
|
|
3279
|
+
hasStaticSkills,
|
|
3280
|
+
})
|
|
3281
|
+
: null;
|
|
3282
|
+
if (!nativeRender) {
|
|
3283
|
+
await ensureCodexActiveSkillsManifest({
|
|
3284
|
+
plugin: candidate.plugin,
|
|
3285
|
+
marketplaceRoot: stagingRoot,
|
|
3286
|
+
hasRequiredSkills: hasStaticSkills,
|
|
3287
|
+
});
|
|
3288
|
+
}
|
|
3289
|
+
if (pluginHasCodexNativeSkillSurface(candidate.plugin)) {
|
|
3290
|
+
await assertNoSensitiveValuesInCodexNativePayload({
|
|
3291
|
+
plugin: candidate.plugin,
|
|
3292
|
+
payloadRoot: path.join(stagingRoot, 'plugins', candidate.identity.pluginName),
|
|
3293
|
+
allowedRoot: stagingRoot,
|
|
3294
|
+
sensitiveValues: await resolveSensitiveUserConfigValues(candidate.plugin, options.context.env),
|
|
3295
|
+
...(nativeRender ? { verifiedRenderReceipt: nativeRender.receipt } : {}),
|
|
3296
|
+
});
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
} : undefined, new Set(first.identity.platform === 'codex'
|
|
3300
|
+
? materializationTargets.flatMap((candidate) => pluginHasCodexNativeSkillSurface(candidate.plugin)
|
|
3301
|
+
? [candidate.identity.pluginId ?? `${candidate.identity.sourceId}/${candidate.identity.pluginName}`]
|
|
3302
|
+
: [])
|
|
3303
|
+
: []));
|
|
3304
|
+
try {
|
|
3305
|
+
await transaction.commit();
|
|
3306
|
+
}
|
|
3307
|
+
catch (error) {
|
|
3308
|
+
if (error.durableMaterializeCommit !== true) {
|
|
3309
|
+
try {
|
|
3310
|
+
await transaction.rollback();
|
|
3311
|
+
}
|
|
3312
|
+
catch (rollbackError) {
|
|
3313
|
+
throw new AggregateError([error, rollbackError], 'nativeMarketplaceGroupCommitFailed: marketplace commit and rollback failed.');
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
throw error;
|
|
3317
|
+
}
|
|
3318
|
+
committedNativeGroups.add(key);
|
|
3319
|
+
})().catch((error) => {
|
|
3320
|
+
throw asNativeGroupFailure(error);
|
|
3321
|
+
});
|
|
3322
|
+
if (options.operationScopedNativePhases) {
|
|
3323
|
+
nativeGroupPreparations.set(key, preparation);
|
|
3324
|
+
}
|
|
3325
|
+
else {
|
|
3326
|
+
nativeTargetPreparations.set(target, preparation);
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
await preparation;
|
|
3330
|
+
};
|
|
3331
|
+
const inspectCodexMarketplace = async (component) => {
|
|
3332
|
+
if (component.locator.kind !== 'codex-marketplace')
|
|
3333
|
+
throw new Error('Invalid Codex marketplace locator.');
|
|
3334
|
+
const target = findTarget(component);
|
|
3335
|
+
const configPath = resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath;
|
|
3336
|
+
const config = await readCodexConfigFile(configPath);
|
|
3337
|
+
const marketplace = config.snapshot.marketplaces[component.locator.marketplaceName];
|
|
3338
|
+
const canonical = !marketplace
|
|
3339
|
+
? 'absent'
|
|
3340
|
+
: marketplace.sourceType === 'local'
|
|
3341
|
+
&& !marketplace.sourcePointsToManifest
|
|
3342
|
+
&& path.normalize(marketplace.source ?? '') === path.normalize(component.locator.marketplaceRoot)
|
|
3343
|
+
? 'present'
|
|
3344
|
+
: 'foreign';
|
|
3345
|
+
if (component.locator.desired) {
|
|
3346
|
+
if (canonical !== 'present')
|
|
3347
|
+
return canonical;
|
|
3348
|
+
const pluginId = `${component.identity.plugin}@${component.locator.marketplaceName}`;
|
|
3349
|
+
return config.snapshot.plugins[pluginId]?.enabled === true ? 'present' : 'absent';
|
|
3350
|
+
}
|
|
3351
|
+
if (canonical !== 'present')
|
|
3352
|
+
return canonical;
|
|
3353
|
+
const suffix = `@${component.locator.marketplaceName}`;
|
|
3354
|
+
const currentPluginId = `${component.identity.plugin}${suffix}`;
|
|
3355
|
+
const projectConfig = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'project', options.context.env).configTomlPath);
|
|
3356
|
+
const configuredPluginIds = [
|
|
3357
|
+
...Object.keys(config.snapshot.plugins),
|
|
3358
|
+
...Object.keys(projectConfig.snapshot.plugins),
|
|
3359
|
+
].filter((pluginId) => pluginId.endsWith(suffix));
|
|
3360
|
+
if (configuredPluginIds.some((pluginId) => pluginId !== currentPluginId))
|
|
3361
|
+
return 'absent';
|
|
3362
|
+
const ownedSession = options.context.codexObservationSession ? undefined
|
|
3363
|
+
: await createCodexNativeObservationSession({
|
|
3364
|
+
exactCwd: target.identity.cwd,
|
|
3365
|
+
env: options.context.env,
|
|
3366
|
+
runner,
|
|
3367
|
+
manager: currentCodexManager() ?? SYSTEM_CODEX_MANAGER,
|
|
3368
|
+
});
|
|
3369
|
+
const session = options.context.codexObservationSession ?? ownedSession;
|
|
3370
|
+
try {
|
|
3371
|
+
const observationScope = observationScopeFor(target);
|
|
3372
|
+
const snapshot = await session.snapshot(component.locator.marketplaceName, observationScope);
|
|
3373
|
+
if (!snapshot.neutral || (observationScope === 'both' && !snapshot.exact))
|
|
3374
|
+
return 'absent';
|
|
3375
|
+
const officialPluginIds = new Set([
|
|
3376
|
+
...(snapshot.exact?.keys() ?? []),
|
|
3377
|
+
...snapshot.neutral.keys(),
|
|
3378
|
+
]);
|
|
3379
|
+
return [...officialPluginIds].some((pluginId) => pluginId !== currentPluginId)
|
|
3380
|
+
? 'absent'
|
|
3381
|
+
: 'present';
|
|
3382
|
+
}
|
|
3383
|
+
finally {
|
|
3384
|
+
await ownedSession?.dispose();
|
|
3385
|
+
}
|
|
3386
|
+
};
|
|
3387
|
+
const inspectClaudeMarketplace = async (component) => {
|
|
3388
|
+
if (component.locator.kind !== 'claude-marketplace')
|
|
3389
|
+
throw new Error('Invalid Claude marketplace locator.');
|
|
3390
|
+
const target = findTarget(component);
|
|
3391
|
+
const state = await readClaudeNativeState({
|
|
3392
|
+
pluginName: component.identity.plugin,
|
|
3393
|
+
scope: component.identity.scope,
|
|
2449
3394
|
cwd: target.identity.cwd,
|
|
2450
3395
|
env: options.context.env,
|
|
3396
|
+
marketplaceName: component.locator.marketplaceName,
|
|
3397
|
+
marketplaceRoot: component.locator.marketplaceRoot,
|
|
2451
3398
|
});
|
|
3399
|
+
if (state.pluginState === 'foreign')
|
|
3400
|
+
return 'foreign';
|
|
3401
|
+
if (component.locator.desired) {
|
|
3402
|
+
return state.marketplaceDeclaredState === 'canonical'
|
|
3403
|
+
&& state.marketplaceMaterializedState === 'canonical'
|
|
3404
|
+
&& state.pluginState === 'present'
|
|
3405
|
+
&& state.pluginEnabled
|
|
3406
|
+
? 'present'
|
|
3407
|
+
: 'absent';
|
|
3408
|
+
}
|
|
3409
|
+
if (state.marketplaceState !== 'canonical')
|
|
3410
|
+
return state.marketplaceState;
|
|
3411
|
+
const projectPath = component.identity.scope === 'project'
|
|
3412
|
+
? path.normalize(await resolveRepoRoot(target.identity.cwd))
|
|
3413
|
+
: undefined;
|
|
3414
|
+
return state.marketplaceConsumers.some((consumer) => !(consumer.name === component.identity.plugin
|
|
3415
|
+
&& consumer.scope === component.identity.scope
|
|
3416
|
+
&& (component.identity.scope === 'user'
|
|
3417
|
+
|| path.normalize(consumer.projectPath ?? '') === projectPath))) ? 'absent' : 'present';
|
|
2452
3418
|
};
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
3419
|
+
const ownership = {
|
|
3420
|
+
inspectShared: async (component) => {
|
|
3421
|
+
const locator = component.locator;
|
|
3422
|
+
switch (locator.kind) {
|
|
3423
|
+
case 'claude-permissions':
|
|
3424
|
+
return (await readClaudeSettings(locator.settingsPath)).awGenerated
|
|
3425
|
+
?.permissions?.[locator.ownerKey] ? 'present' : 'absent';
|
|
3426
|
+
case 'codex-instruction':
|
|
3427
|
+
return (await readCodexConfigFile(locator.configPath)).snapshot
|
|
3428
|
+
.developerInstructions.managedBlocks[locator.ownerKey] ? 'present' : 'absent';
|
|
3429
|
+
case 'codex-activation': {
|
|
3430
|
+
const actual = (await readCodexConfigFile(locator.configPath))
|
|
3431
|
+
.snapshot.plugins[locator.codexPluginId]?.enabled;
|
|
3432
|
+
if (locator.disposition === 'remove')
|
|
3433
|
+
return actual === undefined ? 'absent' : 'present';
|
|
3434
|
+
if (locator.disposition === 'disable')
|
|
3435
|
+
return actual === true ? 'present' : 'absent';
|
|
3436
|
+
return actual === locator.desired ? 'present' : 'absent';
|
|
3437
|
+
}
|
|
3438
|
+
case 'legacy-codex-mcp': {
|
|
3439
|
+
const actual = (await readCodexConfigFile(locator.configPath))
|
|
3440
|
+
.snapshot.mcpServers[locator.serverKey];
|
|
3441
|
+
if (!actual)
|
|
3442
|
+
return 'absent';
|
|
3443
|
+
return legacyCodexMcpOwner(actual) === locator.ownerKey ? 'present' : 'foreign';
|
|
3444
|
+
}
|
|
3445
|
+
case 'legacy-codex-hook':
|
|
3446
|
+
return (await readLegacyCodexHooks(locator.hooksPath)).managedIdentities.some((entry) => serializeTrackedHookGroupIdentity(entry) === locator.serializedIdentity)
|
|
3447
|
+
? 'present'
|
|
3448
|
+
: 'absent';
|
|
3449
|
+
case 'codex-role-registration': {
|
|
3450
|
+
const registration = (await readCodexConfigFile(locator.configPath))
|
|
3451
|
+
.snapshot.agents[locator.roleKey];
|
|
3452
|
+
if (!registration)
|
|
3453
|
+
return 'absent';
|
|
3454
|
+
return await codexRoleRegistrationOwned({
|
|
3455
|
+
fileIdentity: locator.fileIdentity,
|
|
3456
|
+
configPath: locator.configPath,
|
|
3457
|
+
roleKey: locator.roleKey,
|
|
3458
|
+
rolePath: locator.rolePath,
|
|
3459
|
+
registration,
|
|
3460
|
+
}) ? 'present' : 'foreign';
|
|
3461
|
+
}
|
|
3462
|
+
case 'codex-marketplace':
|
|
3463
|
+
return inspectCodexMarketplace(component);
|
|
3464
|
+
case 'claude-marketplace':
|
|
3465
|
+
return inspectClaudeMarketplace(component);
|
|
3466
|
+
case 'corrupted':
|
|
3467
|
+
throw new Error(locator.message);
|
|
3468
|
+
}
|
|
3469
|
+
},
|
|
3470
|
+
removeShared: async (component) => {
|
|
3471
|
+
const locator = component.locator;
|
|
3472
|
+
switch (locator.kind) {
|
|
3473
|
+
case 'claude-permissions': {
|
|
3474
|
+
const settings = await readClaudeSettings(locator.settingsPath);
|
|
3475
|
+
rebuildClaudePermissions(settings, locator.ownerKey);
|
|
3476
|
+
await writeClaudeSettings(locator.settingsPath, settings);
|
|
3477
|
+
return;
|
|
3478
|
+
}
|
|
3479
|
+
case 'codex-instruction':
|
|
3480
|
+
await applyCodexConfigTransactionFile(locator.configPath, {
|
|
3481
|
+
removeManagedInstructionPluginName: locator.ownerKey,
|
|
3482
|
+
});
|
|
3483
|
+
return;
|
|
3484
|
+
case 'codex-activation':
|
|
3485
|
+
await applyConditionalCodexConfigTransactionFile(locator.configPath, (snapshot) => {
|
|
3486
|
+
const actual = snapshot.plugins[locator.codexPluginId]?.enabled;
|
|
3487
|
+
if (locator.disposition === 'remove') {
|
|
3488
|
+
return actual === undefined ? null : { removePluginActivations: [locator.codexPluginId] };
|
|
2475
3489
|
}
|
|
2476
|
-
|
|
2477
|
-
|
|
3490
|
+
if (locator.disposition === 'disable') {
|
|
3491
|
+
return actual === true
|
|
3492
|
+
? { upsertPluginActivations: { [locator.codexPluginId]: false } }
|
|
3493
|
+
: null;
|
|
3494
|
+
}
|
|
3495
|
+
return { removePluginActivations: [locator.codexPluginId] };
|
|
3496
|
+
});
|
|
3497
|
+
return;
|
|
3498
|
+
case 'legacy-codex-mcp':
|
|
3499
|
+
await applyConditionalCodexConfigTransactionFile(locator.configPath, (snapshot) => legacyCodexMcpOwner(snapshot.mcpServers[locator.serverKey]) === locator.ownerKey
|
|
3500
|
+
? { removeMcpServers: [locator.serverKey] }
|
|
3501
|
+
: null);
|
|
3502
|
+
return;
|
|
3503
|
+
case 'legacy-codex-hook':
|
|
3504
|
+
await applyLegacyCodexHookCleanupTransactionFile(locator.hooksPath, (parsed) => {
|
|
3505
|
+
let removed = false;
|
|
3506
|
+
const remaining = parsed.groups.flatMap((entry) => {
|
|
3507
|
+
if (!removed && entry.managedIdentity
|
|
3508
|
+
&& serializeTrackedHookGroupIdentity(entry.managedIdentity) === locator.serializedIdentity) {
|
|
3509
|
+
removed = true;
|
|
3510
|
+
return [];
|
|
3511
|
+
}
|
|
3512
|
+
return [entry.rawGroup];
|
|
3513
|
+
});
|
|
3514
|
+
return removed ? remaining : null;
|
|
3515
|
+
});
|
|
3516
|
+
return;
|
|
3517
|
+
case 'codex-role-registration':
|
|
3518
|
+
await applyConditionalCodexConfigTransactionFile(locator.configPath, async (snapshot) => await codexRoleRegistrationOwned({
|
|
3519
|
+
fileIdentity: locator.fileIdentity,
|
|
3520
|
+
configPath: locator.configPath,
|
|
3521
|
+
roleKey: locator.roleKey,
|
|
3522
|
+
rolePath: locator.rolePath,
|
|
3523
|
+
registration: snapshot.agents[locator.roleKey],
|
|
3524
|
+
}) ? { removeAgentRegistrations: [locator.roleKey] } : null);
|
|
3525
|
+
return;
|
|
3526
|
+
case 'codex-marketplace': {
|
|
3527
|
+
if (locator.desired)
|
|
3528
|
+
return;
|
|
3529
|
+
const target = findTarget(component);
|
|
3530
|
+
await prepareNativeGroup(target);
|
|
3531
|
+
codexMutationMarketplaces.add(locator.marketplaceName);
|
|
3532
|
+
await runObservedCodexMutation({
|
|
3533
|
+
marketplaceName: locator.marketplaceName,
|
|
3534
|
+
mutation: () => runNativeCliChecked(buildCodexMarketplaceRemoveInvocation(currentCodexManager() ?? SYSTEM_CODEX_MANAGER, locator.marketplaceName, target.identity.cwd, options.context.env), runner),
|
|
3535
|
+
});
|
|
3536
|
+
return;
|
|
2478
3537
|
}
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
options.context.runtimeLeaseHeld = previousRuntimeHeld;
|
|
3538
|
+
case 'claude-marketplace': {
|
|
3539
|
+
return;
|
|
2482
3540
|
}
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
3541
|
+
case 'corrupted':
|
|
3542
|
+
throw new Error(locator.message);
|
|
3543
|
+
}
|
|
3544
|
+
},
|
|
3545
|
+
validateSkillDirectory: async (component) => {
|
|
3546
|
+
await validateSkillDirectory(component.path, component.identity.platform === 'codex' ? 'codex.skill' : 'claude.skill', { ownerMarkerPolicy: 'ignore', requirementsReceiptPolicy: 'allow' });
|
|
3547
|
+
},
|
|
3548
|
+
skillDirectoryMatchesPristineTree: async ({ actualPath, files }) => {
|
|
3549
|
+
const expectedFiles = new Map(files.map((entry) => [entry.relativePath, entry.contentDigest]));
|
|
3550
|
+
const expectedDirectories = new Set(['']);
|
|
3551
|
+
for (const relativePath of expectedFiles.keys()) {
|
|
3552
|
+
const segments = relativePath.split('/');
|
|
3553
|
+
for (let index = 1; index < segments.length; index += 1) {
|
|
3554
|
+
expectedDirectories.add(segments.slice(0, index).join('/'));
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
const seen = new Set();
|
|
3558
|
+
const pending = [{ absolutePath: actualPath, relativePath: '' }];
|
|
3559
|
+
while (pending.length > 0) {
|
|
3560
|
+
const current = pending.pop();
|
|
3561
|
+
for (const entry of await fs.readdir(current.absolutePath, { withFileTypes: true })) {
|
|
3562
|
+
const relativePath = current.relativePath ? `${current.relativePath}/${entry.name}` : entry.name;
|
|
3563
|
+
if (entry.isSymbolicLink())
|
|
3564
|
+
return false;
|
|
3565
|
+
const absolutePath = path.join(current.absolutePath, entry.name);
|
|
3566
|
+
if (entry.isDirectory()) {
|
|
3567
|
+
if (!expectedDirectories.has(relativePath))
|
|
3568
|
+
return false;
|
|
3569
|
+
pending.push({ absolutePath, relativePath });
|
|
3570
|
+
continue;
|
|
3571
|
+
}
|
|
3572
|
+
if (!entry.isFile())
|
|
3573
|
+
return false;
|
|
3574
|
+
const digest = `sha256:${crypto.createHash('sha256').update(await fs.readFile(absolutePath)).digest('hex')}`;
|
|
3575
|
+
if (expectedFiles.get(relativePath) !== digest)
|
|
3576
|
+
return false;
|
|
3577
|
+
seen.add(relativePath);
|
|
2494
3578
|
}
|
|
2495
|
-
|
|
2496
|
-
|
|
3579
|
+
}
|
|
3580
|
+
return seen.size === expectedFiles.size;
|
|
3581
|
+
},
|
|
3582
|
+
};
|
|
3583
|
+
return {
|
|
3584
|
+
ownership,
|
|
3585
|
+
setNativeMarketplaceEligibleTargets: (eligibleTargets) => {
|
|
3586
|
+
nativeMarketplaceEligibleTargets = new Set(eligibleTargets);
|
|
3587
|
+
},
|
|
3588
|
+
prepareNativeMarketplaceGroup: prepareNativeGroup,
|
|
3589
|
+
selectRecoveryCleanupComponents: (target, components) => {
|
|
3590
|
+
if (!committedNativeGroups.has(nativeGroupKey(target)))
|
|
3591
|
+
return components;
|
|
3592
|
+
const marketplaceRoot = path.resolve(target.identity.marketplaceRoot ?? resolveAwHome(options.context.env));
|
|
3593
|
+
return components.filter((component) => {
|
|
3594
|
+
if (!('path' in component))
|
|
3595
|
+
return true;
|
|
3596
|
+
const relative = path.relative(marketplaceRoot, path.resolve(component.path));
|
|
3597
|
+
return relative === '..'
|
|
3598
|
+
|| relative.startsWith(`..${path.sep}`)
|
|
3599
|
+
|| path.isAbsolute(relative);
|
|
3600
|
+
});
|
|
3601
|
+
},
|
|
3602
|
+
...(options.operationScopedNativePhases ? {
|
|
3603
|
+
deferPostcheck: (target, postcheck) => {
|
|
3604
|
+
if (target.identity.platform !== 'codex')
|
|
3605
|
+
return false;
|
|
3606
|
+
deferredPostchecks.push({
|
|
3607
|
+
target,
|
|
3608
|
+
postcheck: async () => {
|
|
3609
|
+
if (target.requiresNative) {
|
|
3610
|
+
const marketplaceName = target.identity.marketplaceName ?? MARKETPLACE_NAME;
|
|
3611
|
+
const pluginId = `${target.identity.pluginName}@${marketplaceName}`;
|
|
3612
|
+
const registration = await inspectCodexPluginRegistrationLive({
|
|
3613
|
+
pluginId,
|
|
3614
|
+
cwd: target.identity.cwd,
|
|
3615
|
+
env: options.context.env,
|
|
3616
|
+
nativeCliRunner: runner,
|
|
3617
|
+
manager: currentCodexManager(),
|
|
3618
|
+
scope: observationScopeFor(target),
|
|
3619
|
+
observationSession: options.context.codexObservationSession,
|
|
3620
|
+
});
|
|
3621
|
+
const state = requireObservedCodexNativeState(registration.observation, 'native install postcheck');
|
|
3622
|
+
requireCodexObservationCoverage(registration, observationScopeFor(target), 'native install postcheck');
|
|
3623
|
+
if (state !== 'installed') {
|
|
3624
|
+
throw new Error(`codexNativeInstallIncomplete: ${pluginId} is absent from Codex plugin list.`);
|
|
3625
|
+
}
|
|
3626
|
+
const installedEntry = registration.observation.state === 'observed'
|
|
3627
|
+
&& registration.observation.value === 'installed'
|
|
3628
|
+
? registration.observation.entry
|
|
3629
|
+
: undefined;
|
|
3630
|
+
if (installedEntry?.version !== undefined
|
|
3631
|
+
&& installedEntry.version !== target.identity.pluginVersion) {
|
|
3632
|
+
throw new Error(`codexNativeInstallIncomplete: ${pluginId} version is ${installedEntry.version}; expected ${target.identity.pluginVersion}.`);
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
await postcheck();
|
|
3636
|
+
},
|
|
3637
|
+
});
|
|
3638
|
+
return true;
|
|
3639
|
+
},
|
|
3640
|
+
completePostchecks: async () => {
|
|
3641
|
+
const pending = deferredPostchecks;
|
|
3642
|
+
deferredPostchecks = [];
|
|
3643
|
+
const failures = [];
|
|
3644
|
+
for (const entry of pending) {
|
|
3645
|
+
try {
|
|
3646
|
+
await entry.postcheck();
|
|
3647
|
+
}
|
|
3648
|
+
catch (error) {
|
|
3649
|
+
failures.push({ target: entry.target, error });
|
|
3650
|
+
}
|
|
2497
3651
|
}
|
|
3652
|
+
return failures;
|
|
3653
|
+
},
|
|
3654
|
+
} : {}),
|
|
3655
|
+
captureCodexPostMutationSnapshots: async (marketplaceNames = []) => {
|
|
3656
|
+
for (const marketplaceName of marketplaceNames) {
|
|
3657
|
+
codexMutationMarketplaces.add(marketplaceName);
|
|
3658
|
+
}
|
|
3659
|
+
if (codexMutationMarketplaces.size === 0)
|
|
3660
|
+
return;
|
|
3661
|
+
const session = options.context.codexObservationSession;
|
|
3662
|
+
if (!session) {
|
|
3663
|
+
throw new Error('codexNativeObservationUnavailable: grouped postcheck requires an observation session.');
|
|
3664
|
+
}
|
|
3665
|
+
const marketplaces = [...codexMutationMarketplaces].sort();
|
|
3666
|
+
codexMutationMarketplaces.clear();
|
|
3667
|
+
for (const marketplaceName of marketplaces)
|
|
3668
|
+
session.invalidate(marketplaceName);
|
|
3669
|
+
await Promise.all(marketplaces.map((marketplaceName) => session.snapshot(marketplaceName, options.context.codexObservationScopeByMarketplace?.get(marketplaceName)
|
|
3670
|
+
?? (options.targets.some((target) => (nativeMarketplaceEligibleTargets?.has(target) ?? true)
|
|
3671
|
+
&& target.identity.platform === 'codex'
|
|
3672
|
+
&& (target.identity.marketplaceName ?? MARKETPLACE_NAME) === marketplaceName
|
|
3673
|
+
&& target.identity.scope === 'project')
|
|
3674
|
+
? 'both'
|
|
3675
|
+
: 'user'))));
|
|
3676
|
+
},
|
|
3677
|
+
preflightMutation: async (target) => {
|
|
3678
|
+
if (!target.codexNativeObservationExpectation
|
|
3679
|
+
&& !target.codexNativeSensitivePreflightRequired)
|
|
3680
|
+
return;
|
|
3681
|
+
const sensitiveValues = pluginHasCodexNativeSkillSurface(target.plugin)
|
|
3682
|
+
? await resolveSensitiveUserConfigValues(target.plugin, options.context.env)
|
|
3683
|
+
: [];
|
|
3684
|
+
if (sensitiveValues.length > 0) {
|
|
3685
|
+
await assertNoSensitiveValuesInCanonicalCodexNativePayload({
|
|
3686
|
+
plugin: target.plugin,
|
|
3687
|
+
marketplaceRoot: target.identity.marketplaceRoot ?? resolveAwHome(options.context.env),
|
|
3688
|
+
marketplaceName: target.identity.marketplaceName ?? MARKETPLACE_NAME,
|
|
3689
|
+
env: options.context.env,
|
|
3690
|
+
sensitiveValues,
|
|
3691
|
+
});
|
|
3692
|
+
}
|
|
3693
|
+
if (!target.codexNativeObservationExpectation)
|
|
3694
|
+
return;
|
|
3695
|
+
const pluginId = `${target.identity.pluginName}@${target.identity.marketplaceName ?? MARKETPLACE_NAME}`;
|
|
3696
|
+
const registration = await inspectCodexPluginRegistrationLive({
|
|
3697
|
+
pluginId,
|
|
3698
|
+
cwd: target.identity.cwd,
|
|
3699
|
+
env: options.context.env,
|
|
3700
|
+
nativeCliRunner: runner,
|
|
3701
|
+
manager: currentCodexManager(),
|
|
3702
|
+
scope: observationScopeFor(target),
|
|
3703
|
+
observationSession: options.context.codexObservationSession,
|
|
2498
3704
|
});
|
|
3705
|
+
const observed = requireObservedCodexNativeState(registration.observation, 'artifact write precheck');
|
|
3706
|
+
if (target.codexNativeObservationExpectation === 'installed' && observed !== 'installed') {
|
|
3707
|
+
throw new Error('codexNativeExpectedInstalled: artifact write precheck requires the reused Codex plugin to remain installed.');
|
|
3708
|
+
}
|
|
2499
3709
|
},
|
|
2500
3710
|
preflightMaterialize: async (target) => {
|
|
3711
|
+
if (target.identity.sourceId && options.context.expectedCatalogConfigDigest) {
|
|
3712
|
+
const current = await readCatalogSourcesConfigSnapshot(options.context.env);
|
|
3713
|
+
if (current.digest !== options.context.expectedCatalogConfigDigest) {
|
|
3714
|
+
throw new Error('catalogSourceConflict: catalog source config changed before owner publication.');
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
2501
3717
|
if (target.identity.platform === 'claude') {
|
|
2502
3718
|
await assertNoClaudeUnboundNativeNameCollision({
|
|
2503
3719
|
pluginName: target.identity.pluginName,
|
|
@@ -2518,27 +3734,17 @@ function createNativeDriver(options) {
|
|
|
2518
3734
|
|| (Object.hasOwn(config.snapshot.plugins, expectedPluginId) && !canonical)) {
|
|
2519
3735
|
throw new Error(`externalOwnershipPresent: Codex marketplace ${marketplaceName} has a foreign source instead of its canonical namespaced root.`);
|
|
2520
3736
|
}
|
|
3737
|
+
if (target.nativeOverlayDesired) {
|
|
3738
|
+
await prepareCodexNativeRenderCached({
|
|
3739
|
+
context: options.context,
|
|
3740
|
+
plugin: target.plugin,
|
|
3741
|
+
scriptTimeoutMs: options.scriptTimeoutMs,
|
|
3742
|
+
});
|
|
3743
|
+
}
|
|
2521
3744
|
},
|
|
2522
|
-
|
|
2523
|
-
const transaction = mirrorTransactions.get(target);
|
|
2524
|
-
if (!transaction)
|
|
2525
|
-
return;
|
|
2526
|
-
await transaction.commit();
|
|
2527
|
-
mirrorTransactions.delete(target);
|
|
2528
|
-
},
|
|
2529
|
-
rollbackMaterialize: async (target) => {
|
|
2530
|
-
const transaction = mirrorTransactions.get(target);
|
|
2531
|
-
if (!transaction)
|
|
2532
|
-
return;
|
|
2533
|
-
await transaction.rollback();
|
|
2534
|
-
mirrorTransactions.delete(target);
|
|
2535
|
-
},
|
|
2536
|
-
finalizeCleanup: finalizeNamespacedCleanup,
|
|
3745
|
+
finalizeCleanup: async () => undefined,
|
|
2537
3746
|
probeNative: async (component) => {
|
|
2538
|
-
const target =
|
|
2539
|
-
&& entry.target.identity.sourceId === component.identity.sourceId
|
|
2540
|
-
&& entry.target.identity.platform === component.identity.platform
|
|
2541
|
-
&& entry.target.identity.scope === component.identity.scope)?.target;
|
|
3747
|
+
const target = findTarget(component);
|
|
2542
3748
|
if (!target)
|
|
2543
3749
|
throw new Error('Native ownership probe cannot resolve its desired target.');
|
|
2544
3750
|
if (target.identity.platform === 'claude') {
|
|
@@ -2552,23 +3758,25 @@ function createNativeDriver(options) {
|
|
|
2552
3758
|
});
|
|
2553
3759
|
return state.pluginState;
|
|
2554
3760
|
}
|
|
2555
|
-
if (target.identity.platform === 'codex'
|
|
2556
|
-
const
|
|
2557
|
-
|
|
3761
|
+
if (target.identity.platform === 'codex') {
|
|
3762
|
+
const pending = pendingCodexNativeStates.get(codexStateKey(target, component.pluginId));
|
|
3763
|
+
if (pending)
|
|
3764
|
+
return pending;
|
|
3765
|
+
const registration = await inspectCodexPluginRegistrationLive({
|
|
3766
|
+
pluginId: component.pluginId,
|
|
2558
3767
|
cwd: target.identity.cwd,
|
|
2559
3768
|
env: options.context.env,
|
|
2560
|
-
|
|
2561
|
-
|
|
3769
|
+
nativeCliRunner: runner,
|
|
3770
|
+
manager: currentCodexManager(),
|
|
3771
|
+
scope: observationScopeFor(target),
|
|
3772
|
+
observationSession: options.context.codexObservationSession,
|
|
2562
3773
|
});
|
|
2563
|
-
return
|
|
3774
|
+
return observedCodexNativeState(registration.observation, 'native probe');
|
|
2564
3775
|
}
|
|
2565
3776
|
return 'absent';
|
|
2566
3777
|
},
|
|
2567
3778
|
probeNativeHealth: async (component) => {
|
|
2568
|
-
const target =
|
|
2569
|
-
&& entry.target.identity.sourceId === component.identity.sourceId
|
|
2570
|
-
&& entry.target.identity.platform === component.identity.platform
|
|
2571
|
-
&& entry.target.identity.scope === component.identity.scope)?.target;
|
|
3779
|
+
const target = findTarget(component);
|
|
2572
3780
|
if (!target)
|
|
2573
3781
|
throw new Error('Native health probe cannot resolve its desired target.');
|
|
2574
3782
|
if (target.identity.platform === 'claude') {
|
|
@@ -2584,50 +3792,80 @@ function createNativeDriver(options) {
|
|
|
2584
3792
|
return state.pluginState;
|
|
2585
3793
|
return state.pluginEnabled ? 'present' : 'partial';
|
|
2586
3794
|
}
|
|
2587
|
-
if (target.identity.platform === 'codex'
|
|
2588
|
-
const
|
|
2589
|
-
|
|
3795
|
+
if (target.identity.platform === 'codex') {
|
|
3796
|
+
const pluginId = `${target.identity.pluginName}@${target.identity.marketplaceName ?? MARKETPLACE_NAME}`;
|
|
3797
|
+
const registration = await inspectCodexPluginRegistrationLive({
|
|
3798
|
+
pluginId,
|
|
2590
3799
|
cwd: target.identity.cwd,
|
|
2591
3800
|
env: options.context.env,
|
|
2592
|
-
|
|
2593
|
-
|
|
3801
|
+
nativeCliRunner: runner,
|
|
3802
|
+
manager: currentCodexManager(),
|
|
3803
|
+
scope: observationScopeFor(target),
|
|
3804
|
+
observationSession: options.context.codexObservationSession,
|
|
2594
3805
|
});
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
3806
|
+
const nativeState = observedCodexNativeState(registration.observation, 'native health postcheck');
|
|
3807
|
+
if (nativeState !== 'present')
|
|
3808
|
+
return nativeState;
|
|
3809
|
+
const userConfig = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath);
|
|
3810
|
+
const userEnabled = userConfig.snapshot.plugins[pluginId]?.enabled === true;
|
|
3811
|
+
if (target.identity.scope === 'user')
|
|
3812
|
+
return userEnabled ? 'present' : 'partial';
|
|
3813
|
+
const projectConfig = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'project', options.context.env).configTomlPath);
|
|
3814
|
+
return userEnabled || projectConfig.snapshot.plugins[pluginId]?.enabled === true
|
|
3815
|
+
? 'present'
|
|
3816
|
+
: 'partial';
|
|
2598
3817
|
}
|
|
2599
3818
|
return 'absent';
|
|
2600
3819
|
},
|
|
2601
3820
|
removeNative: async (component) => {
|
|
2602
|
-
const target =
|
|
2603
|
-
&& entry.target.identity.sourceId === component.identity.sourceId
|
|
2604
|
-
&& entry.target.identity.platform === component.identity.platform
|
|
2605
|
-
&& entry.target.identity.scope === component.identity.scope)?.target;
|
|
3821
|
+
const target = findTarget(component);
|
|
2606
3822
|
if (!target)
|
|
2607
3823
|
throw new Error('Native ownership removal cannot resolve its desired target.');
|
|
3824
|
+
await prepareNativeGroup(target);
|
|
2608
3825
|
if (target.identity.platform === 'codex') {
|
|
2609
|
-
const
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
marketplaceRoot: target.identity.marketplaceRoot,
|
|
2615
|
-
});
|
|
2616
|
-
if (before.state === 'foreign') {
|
|
2617
|
-
throw new Error(`Codex native owner 为 foreign,拒绝调用 plugin remove:${before.labels.join(',')}`);
|
|
3826
|
+
const marketplaceName = component.pluginId.slice(component.pluginId.lastIndexOf('@') + 1);
|
|
3827
|
+
const stateKey = codexStateKey(target, component.pluginId);
|
|
3828
|
+
const before = pendingCodexNativeStates.get(stateKey) ?? codexNativeStates.get(stateKey);
|
|
3829
|
+
if (!before) {
|
|
3830
|
+
throw new Error('codexNativeObservationUnavailable: native group precheck is missing.');
|
|
2618
3831
|
}
|
|
2619
|
-
|
|
3832
|
+
const removalRequired = before === 'present';
|
|
3833
|
+
if (!removalRequired)
|
|
2620
3834
|
return;
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
3835
|
+
try {
|
|
3836
|
+
codexMutationMarketplaces.add(marketplaceName);
|
|
3837
|
+
await runObservedCodexMutation({
|
|
3838
|
+
marketplaceName,
|
|
3839
|
+
mutation: () => runNativeCliChecked(buildCodexPluginRemoveInvocation(requireCodexManager(currentCodexManager()), component.pluginId, target.identity.cwd, options.context.env), runner),
|
|
3840
|
+
});
|
|
3841
|
+
}
|
|
3842
|
+
catch {
|
|
3843
|
+
throw new Error(`codexNativeRemoveFailed: ${CODEX_REMOVE_RECOVERY}`);
|
|
3844
|
+
}
|
|
3845
|
+
pendingCodexNativeStates.set(stateKey, 'absent');
|
|
3846
|
+
const postcheck = async () => {
|
|
3847
|
+
const after = await inspectCodexPluginRegistrationLive({
|
|
3848
|
+
pluginId: component.pluginId,
|
|
3849
|
+
cwd: target.identity.cwd,
|
|
3850
|
+
env: options.context.env,
|
|
3851
|
+
nativeCliRunner: runner,
|
|
3852
|
+
manager: currentCodexManager(),
|
|
3853
|
+
scope: observationScopeFor(target),
|
|
3854
|
+
observationSession: options.context.codexObservationSession,
|
|
3855
|
+
});
|
|
3856
|
+
const expected = pendingCodexNativeStates.get(stateKey) ?? 'absent';
|
|
3857
|
+
const observed = requireObservedCodexNativeState(after.observation, 'native mutation postcheck');
|
|
3858
|
+
if ((observed === 'installed' ? 'present' : 'absent') !== expected) {
|
|
3859
|
+
throw new Error(expected === 'absent'
|
|
3860
|
+
? `codexNativeRemoveIncomplete: ${CODEX_REMOVE_RECOVERY}`
|
|
3861
|
+
: `codexNativeInstallIncomplete: ${component.pluginId} is absent from Codex plugin list.`);
|
|
3862
|
+
}
|
|
3863
|
+
};
|
|
3864
|
+
if (options.operationScopedNativePhases) {
|
|
3865
|
+
deferredPostchecks.push({ target, postcheck });
|
|
3866
|
+
}
|
|
3867
|
+
else {
|
|
3868
|
+
await postcheck();
|
|
2631
3869
|
}
|
|
2632
3870
|
}
|
|
2633
3871
|
else {
|
|
@@ -2657,162 +3895,176 @@ function createNativeDriver(options) {
|
|
|
2657
3895
|
}
|
|
2658
3896
|
},
|
|
2659
3897
|
writeNative: async (target) => {
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
throw new Error('Missing rebuild integration plan.');
|
|
3898
|
+
if (!target.catalog)
|
|
3899
|
+
throw new Error('Missing rebuild target catalog data.');
|
|
2663
3900
|
const marketplaceName = target.identity.marketplaceName ?? MARKETPLACE_NAME;
|
|
2664
3901
|
const expectedMarketplaceRoot = target.identity.marketplaceRoot ?? resolveAwHome(options.context.env);
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
3902
|
+
if (target.identity.platform === 'codex') {
|
|
3903
|
+
const config = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath);
|
|
3904
|
+
const marketplace = config.snapshot.marketplaces[marketplaceName];
|
|
3905
|
+
if (marketplace && (marketplace.sourceType !== 'local'
|
|
3906
|
+
|| marketplace.sourcePointsToManifest
|
|
3907
|
+
|| path.normalize(marketplace.source ?? '') !== path.normalize(expectedMarketplaceRoot))) {
|
|
3908
|
+
throw new Error(`externalOwnershipPresent: Codex marketplace ${marketplaceName} 已由 foreign source 占用,拒绝覆盖。`);
|
|
2668
3909
|
}
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
pluginName: target.identity.pluginName,
|
|
2682
|
-
scope: target.identity.scope,
|
|
2683
|
-
cwd: target.identity.cwd,
|
|
2684
|
-
env: options.context.env,
|
|
2685
|
-
marketplaceName,
|
|
2686
|
-
marketplaceRoot: expectedMarketplaceRoot,
|
|
2687
|
-
});
|
|
2688
|
-
if (state.marketplaceState === 'foreign') {
|
|
2689
|
-
throw new Error(`Claude marketplace ${marketplaceName} 已由 foreign source 占用,拒绝覆盖。`);
|
|
2690
|
-
}
|
|
2691
|
-
}
|
|
2692
|
-
const mirrored = await mirrorCatalog(plan.catalog, options.context.env, target.identity);
|
|
2693
|
-
mirrorTransactions.set(target, mirrored);
|
|
2694
|
-
const marketplaceRoot = mirrored.rootDir;
|
|
2695
|
-
const pluginId = `${target.identity.pluginName}@${marketplaceName}`;
|
|
2696
|
-
if (target.identity.platform === 'codex') {
|
|
2697
|
-
const requiredSkillWrites = plan.writes.filter((entry) => !entry.optional
|
|
2698
|
-
&& entry.component?.kind === 'skill-directory'
|
|
2699
|
-
&& entry.artifact.kind === 'skill'
|
|
2700
|
-
&& entry.artifact.state !== 'disabled'
|
|
2701
|
-
&& target.identity.scope === 'user');
|
|
2702
|
-
for (const write of requiredSkillWrites) {
|
|
2703
|
-
await write.write();
|
|
2704
|
-
}
|
|
2705
|
-
const skillsRelativeRoot = await ensureCodexActiveSkillsManifest({
|
|
2706
|
-
plugin: target.plugin,
|
|
2707
|
-
marketplaceRoot,
|
|
2708
|
-
hasRequiredSkills: requiredSkillWrites.length > 0,
|
|
2709
|
-
});
|
|
2710
|
-
const config = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath);
|
|
2711
|
-
const marketplace = config.snapshot.marketplaces[marketplaceName];
|
|
2712
|
-
if (path.normalize(marketplace?.source ?? '') !== path.normalize(marketplaceRoot)) {
|
|
2713
|
-
await runNativeCliChecked(buildCodexMarketplaceAddInvocation(marketplaceRoot, target.identity.cwd, options.context.env), runner);
|
|
2714
|
-
}
|
|
2715
|
-
await runNativeCliChecked(buildCodexMarketplaceListInvocation(target.identity.cwd, options.context.env), runner);
|
|
2716
|
-
await runNativeCliChecked(buildCodexPluginAddInvocation(pluginId, target.identity.cwd, options.context.env), runner);
|
|
2717
|
-
await runNativeCliChecked(buildCodexPluginListInvocation(target.identity.cwd, options.context.env), runner);
|
|
2718
|
-
await assertCodexNativeInstallState({
|
|
2719
|
-
pluginName: target.identity.pluginName,
|
|
2720
|
-
pluginVersion: target.identity.pluginVersion,
|
|
2721
|
-
cwd: target.identity.cwd,
|
|
2722
|
-
env: options.context.env,
|
|
2723
|
-
marketplaceRoot,
|
|
2724
|
-
marketplaceName,
|
|
2725
|
-
skillsRelativeRoot,
|
|
2726
|
-
requiredSkillKeys: requiredSkillWrites.flatMap((write) => write.artifact.artifactKey ? [write.artifact.artifactKey] : []),
|
|
2727
|
-
});
|
|
2728
|
-
}
|
|
2729
|
-
else {
|
|
2730
|
-
await runNativeCliChecked({
|
|
2731
|
-
command: 'claude',
|
|
2732
|
-
args: ['plugin', 'marketplace', 'add', marketplaceRoot, '--scope', target.identity.scope],
|
|
2733
|
-
cwd: target.identity.cwd,
|
|
2734
|
-
env: options.context.env,
|
|
2735
|
-
}, runner);
|
|
2736
|
-
await runNativeCliChecked({
|
|
2737
|
-
command: 'claude',
|
|
2738
|
-
args: ['plugin', 'install', pluginId, '--scope', target.identity.scope],
|
|
2739
|
-
cwd: target.identity.cwd,
|
|
2740
|
-
env: options.context.env,
|
|
2741
|
-
}, runner);
|
|
2742
|
-
await runNativeCliChecked({
|
|
2743
|
-
command: 'claude',
|
|
2744
|
-
args: ['plugin', 'list'],
|
|
2745
|
-
cwd: target.identity.cwd,
|
|
2746
|
-
env: options.context.env,
|
|
2747
|
-
}, runner);
|
|
2748
|
-
const after = await readClaudeNativeState({
|
|
2749
|
-
pluginName: target.identity.pluginName,
|
|
2750
|
-
scope: target.identity.scope,
|
|
2751
|
-
cwd: target.identity.cwd,
|
|
2752
|
-
env: options.context.env,
|
|
2753
|
-
marketplaceName,
|
|
2754
|
-
marketplaceRoot,
|
|
2755
|
-
});
|
|
2756
|
-
if (after.marketplaceDeclaredState !== 'canonical'
|
|
2757
|
-
|| after.marketplaceMaterializedState !== 'canonical'
|
|
2758
|
-
|| after.pluginState !== 'present'
|
|
2759
|
-
|| !after.pluginEnabled
|
|
2760
|
-
|| !after.pluginVersions.includes(target.identity.pluginVersion)) {
|
|
2761
|
-
throw new Error(`Claude native install postcheck 失败:declaredMarketplace=${after.marketplaceDeclaredState},materializedMarketplace=${after.marketplaceMaterializedState},plugin=${after.pluginState},enabled=${after.pluginEnabled},versions=${after.pluginVersions.join(',')}。`);
|
|
2762
|
-
}
|
|
2763
|
-
}
|
|
3910
|
+
}
|
|
3911
|
+
else {
|
|
3912
|
+
const state = await readClaudeNativeState({
|
|
3913
|
+
pluginName: target.identity.pluginName,
|
|
3914
|
+
scope: target.identity.scope,
|
|
3915
|
+
cwd: target.identity.cwd,
|
|
3916
|
+
env: options.context.env,
|
|
3917
|
+
marketplaceName,
|
|
3918
|
+
marketplaceRoot: expectedMarketplaceRoot,
|
|
3919
|
+
});
|
|
3920
|
+
if (state.marketplaceState === 'foreign') {
|
|
3921
|
+
throw new Error(`Claude marketplace ${marketplaceName} 已由 foreign source 占用,拒绝覆盖。`);
|
|
2764
3922
|
}
|
|
2765
|
-
}
|
|
2766
|
-
await (target
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
3923
|
+
}
|
|
3924
|
+
await prepareNativeGroup(target);
|
|
3925
|
+
const pluginId = `${target.identity.pluginName}@${marketplaceName}`;
|
|
3926
|
+
if (target.identity.platform === 'codex') {
|
|
3927
|
+
const stateKey = codexStateKey(target, pluginId);
|
|
3928
|
+
const codexRemovalRequired = pendingCodexNativeStates.get(stateKey)
|
|
3929
|
+
?? codexNativeStates.get(stateKey);
|
|
3930
|
+
if (!codexRemovalRequired) {
|
|
3931
|
+
throw new Error('codexNativeObservationUnavailable: native group precheck is missing.');
|
|
3932
|
+
}
|
|
3933
|
+
if (codexRemovalRequired === 'present') {
|
|
3934
|
+
codexMutationMarketplaces.add(marketplaceName);
|
|
3935
|
+
await runObservedCodexMutation({
|
|
3936
|
+
marketplaceName,
|
|
3937
|
+
mutation: () => runNativeCliChecked(buildCodexPluginRemoveInvocation(requireCodexManager(currentCodexManager()), pluginId, target.identity.cwd, options.context.env), runner),
|
|
3938
|
+
});
|
|
3939
|
+
pendingCodexNativeStates.set(stateKey, 'absent');
|
|
3940
|
+
}
|
|
3941
|
+
const config = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath);
|
|
3942
|
+
const marketplace = config.snapshot.marketplaces[marketplaceName];
|
|
3943
|
+
if (path.normalize(marketplace?.source ?? '') !== path.normalize(expectedMarketplaceRoot)) {
|
|
3944
|
+
codexMutationMarketplaces.add(marketplaceName);
|
|
3945
|
+
await runObservedCodexMutation({
|
|
3946
|
+
marketplaceName,
|
|
3947
|
+
mutation: () => runNativeCliChecked(buildCodexMarketplaceAddInvocation(requireCodexManager(currentCodexManager()), expectedMarketplaceRoot, target.identity.cwd, options.context.env), runner),
|
|
3948
|
+
});
|
|
3949
|
+
}
|
|
3950
|
+
codexMutationMarketplaces.add(marketplaceName);
|
|
3951
|
+
await runObservedCodexMutation({
|
|
3952
|
+
marketplaceName,
|
|
3953
|
+
mutation: () => runNativeCliChecked(buildCodexPluginAddInvocation(requireCodexManager(currentCodexManager()), pluginId, target.identity.cwd, options.context.env), runner),
|
|
3954
|
+
});
|
|
3955
|
+
pendingCodexNativeStates.set(stateKey, 'present');
|
|
3956
|
+
const installedConfig = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath);
|
|
3957
|
+
const installedMarketplace = installedConfig.snapshot.marketplaces[marketplaceName];
|
|
3958
|
+
if (installedMarketplace?.sourceType !== 'local'
|
|
3959
|
+
|| installedMarketplace.sourcePointsToManifest
|
|
3960
|
+
|| path.normalize(installedMarketplace.source ?? '') !== path.normalize(expectedMarketplaceRoot)
|
|
3961
|
+
|| installedConfig.snapshot.plugins[pluginId]?.enabled !== true) {
|
|
3962
|
+
throw new Error(`codexNativeInstallIncomplete: ${pluginId} public config is not canonical and enabled.`);
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
else {
|
|
3966
|
+
await runNativeCliChecked({
|
|
3967
|
+
command: 'claude',
|
|
3968
|
+
args: ['plugin', 'marketplace', 'add', expectedMarketplaceRoot, '--scope', target.identity.scope],
|
|
3969
|
+
cwd: target.identity.cwd,
|
|
3970
|
+
env: options.context.env,
|
|
3971
|
+
}, runner);
|
|
3972
|
+
await runNativeCliChecked({
|
|
3973
|
+
command: 'claude',
|
|
3974
|
+
args: ['plugin', 'install', pluginId, '--scope', target.identity.scope],
|
|
3975
|
+
cwd: target.identity.cwd,
|
|
3976
|
+
env: options.context.env,
|
|
3977
|
+
}, runner);
|
|
3978
|
+
await runNativeCliChecked({
|
|
3979
|
+
command: 'claude',
|
|
3980
|
+
args: ['plugin', 'list'],
|
|
3981
|
+
cwd: target.identity.cwd,
|
|
3982
|
+
env: options.context.env,
|
|
3983
|
+
}, runner);
|
|
3984
|
+
const after = await readClaudeNativeState({
|
|
3985
|
+
pluginName: target.identity.pluginName,
|
|
3986
|
+
scope: target.identity.scope,
|
|
3987
|
+
cwd: target.identity.cwd,
|
|
3988
|
+
env: options.context.env,
|
|
3989
|
+
marketplaceName,
|
|
3990
|
+
marketplaceRoot: expectedMarketplaceRoot,
|
|
3991
|
+
});
|
|
3992
|
+
if (after.marketplaceDeclaredState !== 'canonical'
|
|
3993
|
+
|| after.marketplaceMaterializedState !== 'canonical'
|
|
3994
|
+
|| after.pluginState !== 'present'
|
|
3995
|
+
|| !after.pluginEnabled
|
|
3996
|
+
|| !after.pluginVersions.includes(target.identity.pluginVersion)) {
|
|
3997
|
+
throw new Error(`Claude native install postcheck 失败:declaredMarketplace=${after.marketplaceDeclaredState},materializedMarketplace=${after.marketplaceMaterializedState},plugin=${after.pluginState},enabled=${after.pluginEnabled},versions=${after.pluginVersions.join(',')}。`);
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
2771
4000
|
},
|
|
2772
4001
|
writeArtifacts: async (target) => {
|
|
2773
|
-
const
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
return !options.context.runtimeLeaseHeld
|
|
2782
|
-
&& !materializeLeaseHeld.has(target)
|
|
2783
|
-
&& target.identity.platform === 'codex' && target.identity.scope === 'user'
|
|
2784
|
-
? withActiveMarketplaceRuntimeLease(options.context.env, writeAll)
|
|
2785
|
-
: writeAll();
|
|
4002
|
+
for (const operationAction of target.operationActions ?? []) {
|
|
4003
|
+
if (operationAction.kind === 'writeOwnedArtifact'
|
|
4004
|
+
|| operationAction.kind === 'updateSharedConfig'
|
|
4005
|
+
|| operationAction.kind === 'setActivation') {
|
|
4006
|
+
await applyPlannedOperationAction(operationAction);
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
4009
|
+
return target.artifacts;
|
|
2786
4010
|
},
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
pluginName: invocation.plugin.manifest.name,
|
|
4011
|
+
runPreApply: async (invocation) => {
|
|
4012
|
+
const run = () => runPreApplyScript({
|
|
4013
|
+
plugin: invocation.plugin,
|
|
2791
4014
|
platform: invocation.target.identity.platform,
|
|
2792
4015
|
scope: invocation.target.identity.scope,
|
|
2793
4016
|
cwd: invocation.target.identity.cwd,
|
|
2794
4017
|
context: options.context,
|
|
2795
|
-
currentPlugin: invocation.plugin,
|
|
2796
|
-
artifacts: [...invocation.artifacts],
|
|
2797
4018
|
scriptTimeoutMs: options.scriptTimeoutMs,
|
|
2798
4019
|
});
|
|
4020
|
+
const cache = options.context.preApplyExecutionCache;
|
|
4021
|
+
if (!cache) {
|
|
4022
|
+
await run();
|
|
4023
|
+
return;
|
|
4024
|
+
}
|
|
4025
|
+
const { identity } = invocation.target;
|
|
4026
|
+
const sourceIdentity = invocation.plugin.sourceIdentity ?? identity.sourceIdentity;
|
|
4027
|
+
const pluginId = invocation.plugin.pluginId ?? identity.pluginId;
|
|
4028
|
+
if (!sourceIdentity || !pluginId) {
|
|
4029
|
+
await run();
|
|
4030
|
+
return;
|
|
4031
|
+
}
|
|
4032
|
+
const projectKey = identity.scope === 'project'
|
|
4033
|
+
? identity.platform === 'codex'
|
|
4034
|
+
? await resolveCodexProjectKey(identity.cwd)
|
|
4035
|
+
: await resolveClaudeProjectKey(identity.cwd)
|
|
4036
|
+
: '';
|
|
4037
|
+
const key = [
|
|
4038
|
+
sourceIdentity,
|
|
4039
|
+
pluginId,
|
|
4040
|
+
identity.platform,
|
|
4041
|
+
identity.scope,
|
|
4042
|
+
projectKey,
|
|
4043
|
+
].join('\0');
|
|
4044
|
+
let execution = cache.get(key);
|
|
4045
|
+
if (!execution) {
|
|
4046
|
+
execution = run();
|
|
4047
|
+
cache.set(key, execution);
|
|
4048
|
+
}
|
|
4049
|
+
await execution;
|
|
2799
4050
|
},
|
|
2800
4051
|
};
|
|
2801
4052
|
}
|
|
2802
|
-
const planTargets = new WeakMap();
|
|
2803
|
-
function plansTargetEntries(plans) {
|
|
2804
|
-
return (planTargets.get(plans) ?? []).flatMap((target) => {
|
|
2805
|
-
const plan = plans.get(target);
|
|
2806
|
-
return plan ? [{ target, plan }] : [];
|
|
2807
|
-
});
|
|
2808
|
-
}
|
|
2809
4053
|
export function createRebuildIntegration(options) {
|
|
2810
|
-
const
|
|
2811
|
-
|
|
2812
|
-
const driver = createNativeDriver({ plans, ...options });
|
|
4054
|
+
const targets = [...(options.preparedTargets ?? [])];
|
|
4055
|
+
const driver = createNativeDriver({ targets, ...options });
|
|
2813
4056
|
return {
|
|
2814
4057
|
driver,
|
|
4058
|
+
setNativeMarketplaceEligibleTargets: driver.setNativeMarketplaceEligibleTargets,
|
|
4059
|
+
prepareNativeMarketplaceGroup: driver.prepareNativeMarketplaceGroup,
|
|
4060
|
+
captureCodexPostMutationSnapshots: driver.captureCodexPostMutationSnapshots,
|
|
2815
4061
|
buildTarget: async (input) => {
|
|
4062
|
+
input = {
|
|
4063
|
+
...input,
|
|
4064
|
+
sourceId: input.sourceId ?? input.plugin.sourceId,
|
|
4065
|
+
sourceIdentity: input.sourceIdentity ?? input.plugin.sourceIdentity,
|
|
4066
|
+
pluginId: input.pluginId ?? input.plugin.pluginId,
|
|
4067
|
+
};
|
|
2816
4068
|
const materializing = input.command !== 'cleanup'
|
|
2817
4069
|
&& input.command !== 'uninstall'
|
|
2818
4070
|
&& input.readOnlyInspectionPlan !== true;
|
|
@@ -2821,13 +4073,10 @@ export function createRebuildIntegration(options) {
|
|
|
2821
4073
|
cwd: input.cwd,
|
|
2822
4074
|
rootNames: input.platform === 'codex' ? ['.agents', '.codex'] : ['.claude'],
|
|
2823
4075
|
managedPaths: input.platform === 'codex'
|
|
2824
|
-
? ['.codex/config.toml'
|
|
4076
|
+
? ['.codex/config.toml']
|
|
2825
4077
|
: ['.claude/settings.json'],
|
|
2826
4078
|
});
|
|
2827
4079
|
input = { ...input, cwd: canonicalRepoRoot };
|
|
2828
|
-
if (materializing && input.platform === 'codex' && input.plugin.manifest.apps) {
|
|
2829
|
-
throw new Error(`Codex project scope 不支持 apps:${input.plugin.manifest.name}`);
|
|
2830
|
-
}
|
|
2831
4080
|
}
|
|
2832
4081
|
if (materializing && input.platform === 'codex') {
|
|
2833
4082
|
await preflightCodexWriteSafety({
|
|
@@ -2837,26 +4086,10 @@ export function createRebuildIntegration(options) {
|
|
|
2837
4086
|
env: options.context.env,
|
|
2838
4087
|
});
|
|
2839
4088
|
}
|
|
2840
|
-
const event =
|
|
2841
|
-
|
|
2842
|
-
if (input.includeHistoricalLifecycleArtifacts) {
|
|
2843
|
-
const loadPlugin = (bundleRoot) => readPluginManifest(bundleRoot, { mode: 'bundle' });
|
|
2844
|
-
const { readLifecycleActiveSnapshot } = await import('../utils/lifecycle-active-store.js');
|
|
2845
|
-
const active = await readLifecycleActiveSnapshot({
|
|
2846
|
-
...(input.sourceId ? {
|
|
2847
|
-
sourceId: input.sourceId,
|
|
2848
|
-
sourceIdentity: input.sourceIdentity,
|
|
2849
|
-
} : {}),
|
|
2850
|
-
pluginName: input.plugin.manifest.name,
|
|
2851
|
-
platform: input.platform,
|
|
2852
|
-
scope: input.scope,
|
|
2853
|
-
cwd: input.cwd,
|
|
2854
|
-
env: options.context.env,
|
|
2855
|
-
loadPlugin,
|
|
2856
|
-
});
|
|
2857
|
-
historicalArtifacts = active?.artifacts ?? [];
|
|
2858
|
-
}
|
|
4089
|
+
const event = 'install';
|
|
4090
|
+
const historicalArtifacts = [];
|
|
2859
4091
|
let writes = [];
|
|
4092
|
+
let currentRemovalComponents = [];
|
|
2860
4093
|
if (input.command !== 'cleanup' && input.command !== 'uninstall') {
|
|
2861
4094
|
if (input.readOnlyInspectionPlan) {
|
|
2862
4095
|
writes = await buildInspectionWrites({
|
|
@@ -2866,6 +4099,8 @@ export function createRebuildIntegration(options) {
|
|
|
2866
4099
|
cwd: input.cwd,
|
|
2867
4100
|
env: options.context.env,
|
|
2868
4101
|
marketplaceRoot: input.marketplaceRoot,
|
|
4102
|
+
marketplaceName: input.marketplaceName,
|
|
4103
|
+
userActivationDesired: options.context.codexUserActivationDesired,
|
|
2869
4104
|
});
|
|
2870
4105
|
}
|
|
2871
4106
|
else if (input.platform === 'codex') {
|
|
@@ -2875,6 +4110,8 @@ export function createRebuildIntegration(options) {
|
|
|
2875
4110
|
event,
|
|
2876
4111
|
historicalArtifacts,
|
|
2877
4112
|
scriptTimeoutMs: options.scriptTimeoutMs,
|
|
4113
|
+
marketplaceName: input.marketplaceName,
|
|
4114
|
+
userActivationDesired: options.context.codexUserActivationDesired,
|
|
2878
4115
|
});
|
|
2879
4116
|
}
|
|
2880
4117
|
else {
|
|
@@ -2887,6 +4124,22 @@ export function createRebuildIntegration(options) {
|
|
|
2887
4124
|
});
|
|
2888
4125
|
}
|
|
2889
4126
|
}
|
|
4127
|
+
else if (input.platform === 'codex' && input.scope === 'project') {
|
|
4128
|
+
const currentRemovalWrites = await buildInspectionWrites({
|
|
4129
|
+
plugin: input.plugin,
|
|
4130
|
+
platform: input.platform,
|
|
4131
|
+
scope: input.scope,
|
|
4132
|
+
cwd: input.cwd,
|
|
4133
|
+
env: options.context.env,
|
|
4134
|
+
marketplaceRoot: input.marketplaceRoot,
|
|
4135
|
+
marketplaceName: input.marketplaceName,
|
|
4136
|
+
userActivationDesired: options.context.codexUserActivationDesired,
|
|
4137
|
+
});
|
|
4138
|
+
currentRemovalComponents = currentRemovalWrites.flatMap((write) => [
|
|
4139
|
+
...(write.additionalComponents ?? []),
|
|
4140
|
+
...(write.component ? [write.component] : []),
|
|
4141
|
+
]);
|
|
4142
|
+
}
|
|
2890
4143
|
const desiredComponents = writes.filter((write) => !write.optional).flatMap((write) => [
|
|
2891
4144
|
...(write.additionalComponents ?? []),
|
|
2892
4145
|
...(write.component ? [write.component] : []),
|
|
@@ -2914,8 +4167,12 @@ export function createRebuildIntegration(options) {
|
|
|
2914
4167
|
})))).flat();
|
|
2915
4168
|
const skillRoots = input.platform === 'codex'
|
|
2916
4169
|
? input.scope === 'project'
|
|
2917
|
-
?
|
|
2918
|
-
|
|
4170
|
+
? materializing
|
|
4171
|
+
|| ((input.command === 'cleanup' || input.command === 'uninstall')
|
|
4172
|
+
&& input.readOnlyInspectionPlan !== true)
|
|
4173
|
+
? [path.join(input.cwd, '.agents', 'skills')]
|
|
4174
|
+
: []
|
|
4175
|
+
: [path.join(getCodexHome(options.context.env), 'skills')]
|
|
2919
4176
|
: [path.join(configDir, 'skills')];
|
|
2920
4177
|
const scanSkills = () => Promise.all(skillRoots.map((root) => scanOwnedSkills({
|
|
2921
4178
|
sourceId: input.sourceId,
|
|
@@ -2924,13 +4181,38 @@ export function createRebuildIntegration(options) {
|
|
|
2924
4181
|
pluginName: input.plugin.manifest.name,
|
|
2925
4182
|
platform: input.platform,
|
|
2926
4183
|
scope: input.scope,
|
|
4184
|
+
recursive: !(input.platform === 'codex' && input.scope === 'project'),
|
|
2927
4185
|
})));
|
|
2928
|
-
const scannedSkills = (await (
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
4186
|
+
const scannedSkills = (await scanSkills()).flat();
|
|
4187
|
+
const legacyCodexNativeSkills = input.platform === 'codex'
|
|
4188
|
+
? (await (async () => {
|
|
4189
|
+
const skillKeys = [...new Set(codexNativeRenderTargets(input.plugin)
|
|
4190
|
+
.map((target) => target.skillKey))];
|
|
4191
|
+
if (skillKeys.length === 0)
|
|
4192
|
+
return [];
|
|
4193
|
+
const projectRoot = await assertCanonicalProjectManagedRoots({
|
|
4194
|
+
cwd: input.cwd,
|
|
4195
|
+
rootNames: ['.agents'],
|
|
4196
|
+
});
|
|
4197
|
+
const roots = [
|
|
4198
|
+
{
|
|
4199
|
+
root: path.join(getCodexHome(options.context.env), 'skills'),
|
|
4200
|
+
scope: 'user',
|
|
4201
|
+
},
|
|
4202
|
+
{
|
|
4203
|
+
root: path.join(projectRoot, '.agents', 'skills'),
|
|
4204
|
+
scope: 'project',
|
|
4205
|
+
},
|
|
4206
|
+
];
|
|
4207
|
+
return roots.flatMap(({ root, scope }) => skillKeys.map((skillKey) => pathComponent({
|
|
4208
|
+
kind: 'skill-directory',
|
|
4209
|
+
path: path.join(root, skillKey),
|
|
4210
|
+
allowedRoot: root,
|
|
4211
|
+
identity: ownerIdentity(input.plugin.manifest.name, skillKey, 'codex', scope),
|
|
4212
|
+
})));
|
|
4213
|
+
})())
|
|
4214
|
+
: [];
|
|
4215
|
+
let historicalComponents = await buildHistoricalComponents({
|
|
2934
4216
|
sourceId: input.sourceId,
|
|
2935
4217
|
sourceIdentity: input.sourceIdentity,
|
|
2936
4218
|
pluginId: input.pluginId,
|
|
@@ -2940,7 +4222,23 @@ export function createRebuildIntegration(options) {
|
|
|
2940
4222
|
cwd: input.cwd,
|
|
2941
4223
|
env: options.context.env,
|
|
2942
4224
|
artifacts: historicalArtifacts,
|
|
4225
|
+
command: input.command,
|
|
2943
4226
|
});
|
|
4227
|
+
if (input.platform === 'codex') {
|
|
4228
|
+
const nativePluginRoot = await canonicalizePathThroughExistingAncestor(path.join(input.marketplaceRoot ?? resolveAwHome(options.context.env), 'plugins', input.plugin.manifest.name));
|
|
4229
|
+
const privateCacheRoot = path.resolve(getCodexHome(options.context.env), 'plugins', 'cache');
|
|
4230
|
+
historicalComponents = historicalComponents.filter((component) => {
|
|
4231
|
+
if (!('path' in component))
|
|
4232
|
+
return true;
|
|
4233
|
+
const componentPath = path.resolve(component.path);
|
|
4234
|
+
return [nativePluginRoot, privateCacheRoot].every((protectedRoot) => {
|
|
4235
|
+
const relative = path.relative(protectedRoot, componentPath);
|
|
4236
|
+
return relative === '..'
|
|
4237
|
+
|| relative.startsWith(`..${path.sep}`)
|
|
4238
|
+
|| path.isAbsolute(relative);
|
|
4239
|
+
});
|
|
4240
|
+
});
|
|
4241
|
+
}
|
|
2944
4242
|
const sharedComponents = input.platform === 'codex'
|
|
2945
4243
|
? await scanCodexSharedComponents({
|
|
2946
4244
|
sourceId: input.sourceId,
|
|
@@ -2950,7 +4248,6 @@ export function createRebuildIntegration(options) {
|
|
|
2950
4248
|
scope: input.scope,
|
|
2951
4249
|
paths: resolveCodexPaths(input.cwd, input.scope, options.context.env),
|
|
2952
4250
|
scanConfig: true,
|
|
2953
|
-
scanHooks: true,
|
|
2954
4251
|
ignoreReadErrors: true,
|
|
2955
4252
|
})
|
|
2956
4253
|
: await scanClaudeSharedComponents({
|
|
@@ -2962,9 +4259,36 @@ export function createRebuildIntegration(options) {
|
|
|
2962
4259
|
settingsPath: resolveClaudePaths(input.cwd, input.scope, options.context.env).settingsPath,
|
|
2963
4260
|
});
|
|
2964
4261
|
const nativeRequired = input.platform === 'claude'
|
|
2965
|
-
|| (input.
|
|
2966
|
-
const
|
|
2967
|
-
|
|
4262
|
+
|| analyzeCodexSurface(input.plugin.manifest).nativeKinds.length > 0;
|
|
4263
|
+
const nativeDesired = nativeRequired
|
|
4264
|
+
&& input.command !== 'cleanup'
|
|
4265
|
+
&& input.command !== 'uninstall';
|
|
4266
|
+
const codexRegistration = input.platform === 'codex'
|
|
4267
|
+
&& nativeDesired
|
|
4268
|
+
&& input.readOnlyInspectionPlan !== true
|
|
4269
|
+
? await inspectCodexPluginRegistrationLive({
|
|
4270
|
+
pluginId: `${input.plugin.manifest.name}@${input.marketplaceName ?? MARKETPLACE_NAME}`,
|
|
4271
|
+
cwd: input.cwd,
|
|
4272
|
+
env: options.context.env,
|
|
4273
|
+
nativeCliRunner: options.context.nativeCliRunner,
|
|
4274
|
+
manager: options.context.codexManager,
|
|
4275
|
+
scope: codexObservationScope(options.context, input.marketplaceName ?? MARKETPLACE_NAME, input.scope),
|
|
4276
|
+
observationSession: options.context.codexObservationSession,
|
|
4277
|
+
})
|
|
4278
|
+
: undefined;
|
|
4279
|
+
const codexNativeExact = codexRegistration?.observation.state === 'observed'
|
|
4280
|
+
&& codexRegistration.observation.value === 'installed'
|
|
4281
|
+
&& (codexRegistration.observation.entry.version === undefined
|
|
4282
|
+
|| codexRegistration.observation.entry.version === input.plugin.manifest.version);
|
|
4283
|
+
const materializeNativeRequired = nativeDesired && (input.platform === 'claude'
|
|
4284
|
+
|| input.command === 'reinstall'
|
|
4285
|
+
|| !codexNativeExact);
|
|
4286
|
+
const nativeCleanupCandidate = input.platform === 'claude'
|
|
4287
|
+
? input.includeNativeCleanupCandidate !== false
|
|
4288
|
+
: input.platform === 'codex'
|
|
4289
|
+
&& nativeRequired
|
|
4290
|
+
&& (input.command === 'cleanup' || input.command === 'uninstall');
|
|
4291
|
+
const nativePlugin = (nativeDesired || nativeCleanupCandidate)
|
|
2968
4292
|
? [{
|
|
2969
4293
|
kind: 'native-plugin',
|
|
2970
4294
|
identity: ownerIdentity(input.plugin.manifest.name, 'native', input.platform, input.scope),
|
|
@@ -2974,9 +4298,13 @@ export function createRebuildIntegration(options) {
|
|
|
2974
4298
|
const marketplaceDesired = nativeRequired
|
|
2975
4299
|
&& input.command !== 'cleanup'
|
|
2976
4300
|
&& input.command !== 'uninstall';
|
|
2977
|
-
const marketplace = input.platform === 'codex'
|
|
4301
|
+
const marketplace = input.platform === 'codex'
|
|
4302
|
+
&& input.scope === 'user'
|
|
4303
|
+
&& nativeRequired
|
|
4304
|
+
&& input.readOnlyShallowInspectionPlan !== true
|
|
2978
4305
|
? [createCodexMarketplaceComponent({
|
|
2979
4306
|
pluginName: input.plugin.manifest.name,
|
|
4307
|
+
codexManager: options.context.codexManager,
|
|
2980
4308
|
cwd: input.cwd,
|
|
2981
4309
|
env: options.context.env,
|
|
2982
4310
|
runner: options.context.nativeCliRunner,
|
|
@@ -2984,7 +4312,7 @@ export function createRebuildIntegration(options) {
|
|
|
2984
4312
|
marketplaceRoot: input.marketplaceRoot ?? resolveAwHome(options.context.env),
|
|
2985
4313
|
desired: marketplaceDesired,
|
|
2986
4314
|
})]
|
|
2987
|
-
: input.platform === 'claude'
|
|
4315
|
+
: input.platform === 'claude' && marketplaceDesired
|
|
2988
4316
|
? [createClaudeMarketplaceComponent({
|
|
2989
4317
|
pluginName: input.plugin.manifest.name,
|
|
2990
4318
|
scope: input.scope,
|
|
@@ -2996,38 +4324,83 @@ export function createRebuildIntegration(options) {
|
|
|
2996
4324
|
desired: marketplaceDesired,
|
|
2997
4325
|
})]
|
|
2998
4326
|
: [];
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
4327
|
+
if (nativeDesired)
|
|
4328
|
+
desiredComponents.push(...nativePlugin, ...marketplace);
|
|
4329
|
+
const activationCleanup = input.platform === 'codex'
|
|
4330
|
+
&& nativeRequired
|
|
4331
|
+
&& (input.command === 'cleanup' || input.command === 'uninstall')
|
|
4332
|
+
? (input.scope === 'project'
|
|
4333
|
+
? [
|
|
4334
|
+
createCodexActivationComponent({
|
|
4335
|
+
identity: ownerIdentity(input.plugin.manifest.name, internalArtifactKey('activation', 'project'), 'codex', input.scope),
|
|
4336
|
+
configPath: resolveCodexPaths(input.cwd, 'project', options.context.env).configTomlPath,
|
|
4337
|
+
codexPluginId: `${input.plugin.manifest.name}@${input.marketplaceName ?? MARKETPLACE_NAME}`,
|
|
4338
|
+
desired: true,
|
|
4339
|
+
}),
|
|
4340
|
+
]
|
|
4341
|
+
: [createCodexActivationComponent({
|
|
4342
|
+
identity: ownerIdentity(input.plugin.manifest.name, internalArtifactKey('activation', 'user'), 'codex', input.scope),
|
|
4343
|
+
configPath: resolveCodexPaths(input.cwd, 'user', options.context.env).configTomlPath,
|
|
4344
|
+
codexPluginId: `${input.plugin.manifest.name}@${input.marketplaceName ?? MARKETPLACE_NAME}`,
|
|
4345
|
+
desired: true,
|
|
4346
|
+
})])
|
|
4347
|
+
: [];
|
|
4348
|
+
const explicitActivationCleanup = input.platform === 'codex'
|
|
4349
|
+
? (input.codexActivationCleanup ?? []).map((activation) => createCodexActivationCleanupComponent({
|
|
4350
|
+
identity: ownerIdentity(input.plugin.manifest.name, internalArtifactKey('activation', activation.scope), 'codex', input.scope),
|
|
4351
|
+
configPath: resolveCodexPaths(input.cwd, activation.scope, options.context.env).configTomlPath,
|
|
4352
|
+
codexPluginId: activation.codexPluginId,
|
|
4353
|
+
disposition: activation.disposition,
|
|
4354
|
+
}))
|
|
3012
4355
|
: [];
|
|
3013
|
-
if (nativeRequired
|
|
3014
|
-
&& input.command !== 'cleanup'
|
|
3015
|
-
&& input.command !== 'uninstall')
|
|
3016
|
-
desiredComponents.push(...nativePlugin, ...marketplace, ...nativeVersion);
|
|
3017
4356
|
const allComponents = dedupeComponents([
|
|
3018
4357
|
...desiredComponents,
|
|
3019
4358
|
...optionalComponents,
|
|
3020
4359
|
...(input.platform === 'codex' ? sharedComponents : []),
|
|
3021
4360
|
...scannedText,
|
|
3022
4361
|
...scannedSkills,
|
|
4362
|
+
...legacyCodexNativeSkills,
|
|
4363
|
+
...currentRemovalComponents,
|
|
3023
4364
|
...historicalComponents,
|
|
3024
4365
|
...(input.platform === 'claude' ? sharedComponents : []),
|
|
3025
4366
|
...nativePlugin,
|
|
3026
4367
|
...marketplace,
|
|
3027
|
-
...
|
|
4368
|
+
...activationCleanup,
|
|
4369
|
+
...explicitActivationCleanup,
|
|
3028
4370
|
]);
|
|
3029
4371
|
bindNamespacedOwnerIdentity(allComponents, input);
|
|
3030
4372
|
const desiredKeys = new Set(desiredComponents.map(componentIdentityKey));
|
|
4373
|
+
const operationActions = [];
|
|
4374
|
+
const preApply = input.plugin.manifest.scripts?.preApply;
|
|
4375
|
+
if (preApply && input.command !== 'cleanup' && input.command !== 'uninstall') {
|
|
4376
|
+
const script = path.resolve(input.plugin.rootDir, preApply.path.replace(/^\.\//u, ''));
|
|
4377
|
+
const scriptDigest = `sha256:${crypto.createHash('sha256')
|
|
4378
|
+
.update(await fs.readFile(script)).digest('hex')}`;
|
|
4379
|
+
operationActions.push({
|
|
4380
|
+
kind: 'runPreApply',
|
|
4381
|
+
pluginId: input.pluginId ?? pluginActionId(input.plugin),
|
|
4382
|
+
platform: input.platform,
|
|
4383
|
+
scope: input.scope,
|
|
4384
|
+
script: preApply.path,
|
|
4385
|
+
scriptDigest,
|
|
4386
|
+
});
|
|
4387
|
+
}
|
|
4388
|
+
if (nativeDesired) {
|
|
4389
|
+
operationActions.push({
|
|
4390
|
+
kind: 'ensureNativeRegistration',
|
|
4391
|
+
pluginId: input.pluginId ?? pluginActionId(input.plugin),
|
|
4392
|
+
platform: input.platform,
|
|
4393
|
+
scope: input.scope,
|
|
4394
|
+
registration: `${input.plugin.manifest.name}@${input.marketplaceName ?? MARKETPLACE_NAME}`,
|
|
4395
|
+
marketplaceName: input.marketplaceName ?? MARKETPLACE_NAME,
|
|
4396
|
+
marketplaceRoot: input.marketplaceRoot ?? resolveAwHome(options.context.env),
|
|
4397
|
+
});
|
|
4398
|
+
}
|
|
4399
|
+
operationActions.push(...writes.flatMap((write) => [
|
|
4400
|
+
...(write.action ? [write.action] : []),
|
|
4401
|
+
...(write.additionalActions ?? []),
|
|
4402
|
+
]));
|
|
4403
|
+
finalizeOwnedArtifactActions(operationActions);
|
|
3031
4404
|
const target = {
|
|
3032
4405
|
plugin: input.plugin,
|
|
3033
4406
|
identity: {
|
|
@@ -3047,24 +4420,27 @@ export function createRebuildIntegration(options) {
|
|
|
3047
4420
|
ownedComponents: allComponents,
|
|
3048
4421
|
desiredOwnedComponents: allComponents.filter((component) => desiredKeys.has(componentIdentityKey(component))),
|
|
3049
4422
|
optionalOwnedComponents: allComponents.filter((component) => optionalComponentKeys.has(componentIdentityKey(component))),
|
|
3050
|
-
requiresNative:
|
|
3051
|
-
|
|
3052
|
-
|
|
4423
|
+
requiresNative: nativeDesired,
|
|
4424
|
+
writeNativeRequired: materializeNativeRequired,
|
|
4425
|
+
nativeOverlayDesired: nativeDesired,
|
|
3053
4426
|
artifacts: writes.map((write) => write.artifact),
|
|
3054
|
-
|
|
3055
|
-
};
|
|
3056
|
-
plans.set(target, {
|
|
4427
|
+
operationActions,
|
|
3057
4428
|
catalog: input.catalog,
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
planTargets.get(plans).push(target);
|
|
4429
|
+
preApplySource: input.plugin,
|
|
4430
|
+
};
|
|
4431
|
+
targets.push(target);
|
|
3062
4432
|
return target;
|
|
3063
4433
|
},
|
|
3064
4434
|
};
|
|
3065
4435
|
}
|
|
3066
4436
|
export async function inspectRebuildTarget(options) {
|
|
3067
|
-
const integration = createRebuildIntegration({
|
|
4437
|
+
const integration = createRebuildIntegration({
|
|
4438
|
+
context: {
|
|
4439
|
+
...options.context,
|
|
4440
|
+
codexUserActivationDesired: options.userActivationDesired
|
|
4441
|
+
?? options.context.codexUserActivationDesired,
|
|
4442
|
+
},
|
|
4443
|
+
});
|
|
3068
4444
|
const target = await integration.buildTarget({
|
|
3069
4445
|
...(options.sourceId ? {
|
|
3070
4446
|
sourceId: options.sourceId,
|
|
@@ -3079,15 +4455,53 @@ export async function inspectRebuildTarget(options) {
|
|
|
3079
4455
|
cwd: options.cwd,
|
|
3080
4456
|
command: 'install',
|
|
3081
4457
|
catalog: options.catalog,
|
|
3082
|
-
includeHistoricalLifecycleArtifacts: false,
|
|
3083
4458
|
readOnlyInspectionPlan: true,
|
|
4459
|
+
readOnlyShallowInspectionPlan: options.shallow,
|
|
3084
4460
|
});
|
|
3085
4461
|
const inspection = await inspectOwnedComponents({
|
|
3086
4462
|
components: target.ownedComponents,
|
|
3087
|
-
|
|
4463
|
+
executor: integration.driver.ownership,
|
|
4464
|
+
nativeProbe: options.nativeProbe
|
|
4465
|
+
?? integration.driver.probeNativeHealth
|
|
4466
|
+
?? integration.driver.probeNative,
|
|
3088
4467
|
verifyHealth: true,
|
|
3089
4468
|
});
|
|
3090
|
-
|
|
4469
|
+
if (!target.requiresNative)
|
|
4470
|
+
return { target, inspection };
|
|
4471
|
+
if (target.identity.platform === 'codex') {
|
|
4472
|
+
const nativeInspection = inspection.components.find((component) => component.component.kind === 'native-plugin');
|
|
4473
|
+
if (nativeInspection?.state !== 'present')
|
|
4474
|
+
return { target, inspection };
|
|
4475
|
+
const registration = await inspectCodexPluginRegistrationLive({
|
|
4476
|
+
pluginId: `${target.identity.pluginName}@${target.identity.marketplaceName ?? MARKETPLACE_NAME}`,
|
|
4477
|
+
cwd: target.identity.cwd,
|
|
4478
|
+
env: options.context.env,
|
|
4479
|
+
nativeCliRunner: options.context.nativeCliRunner,
|
|
4480
|
+
manager: options.context.codexManager,
|
|
4481
|
+
scope: target.identity.scope,
|
|
4482
|
+
observationSession: options.context.codexObservationSession,
|
|
4483
|
+
});
|
|
4484
|
+
return registration.observation.state === 'observed'
|
|
4485
|
+
&& registration.observation.value === 'installed'
|
|
4486
|
+
&& registration.observation.entry.version !== undefined
|
|
4487
|
+
? { target, inspection, installedVersion: registration.observation.entry.version }
|
|
4488
|
+
: { target, inspection };
|
|
4489
|
+
}
|
|
4490
|
+
const native = await readClaudeNativeState({
|
|
4491
|
+
pluginName: target.identity.pluginName,
|
|
4492
|
+
scope: target.identity.scope,
|
|
4493
|
+
cwd: target.identity.cwd,
|
|
4494
|
+
env: options.context.env,
|
|
4495
|
+
marketplaceName: target.identity.marketplaceName,
|
|
4496
|
+
marketplaceRoot: target.identity.marketplaceRoot,
|
|
4497
|
+
});
|
|
4498
|
+
const versions = [...new Set(native.pluginVersions)];
|
|
4499
|
+
return native.marketplaceState === 'canonical'
|
|
4500
|
+
&& native.pluginState === 'present'
|
|
4501
|
+
&& native.pluginEnabled
|
|
4502
|
+
&& versions.length === 1
|
|
4503
|
+
? { target, inspection, installedVersion: versions[0] }
|
|
4504
|
+
: { target, inspection };
|
|
3091
4505
|
}
|
|
3092
4506
|
async function discoverOwnerNamesInTextRoot(root, platform, scope) {
|
|
3093
4507
|
if (!await fs.pathExists(root))
|
|
@@ -3146,7 +4560,7 @@ export async function discoverRebuildTargetNames(options) {
|
|
|
3146
4560
|
cwd: options.cwd,
|
|
3147
4561
|
rootNames: options.platform === 'codex' ? ['.agents', '.codex'] : ['.claude'],
|
|
3148
4562
|
managedPaths: options.platform === 'codex'
|
|
3149
|
-
? ['.codex/config.toml'
|
|
4563
|
+
? ['.codex/config.toml']
|
|
3150
4564
|
: ['.claude/settings.json'],
|
|
3151
4565
|
});
|
|
3152
4566
|
options = { ...options, cwd: canonicalRepoRoot };
|
|
@@ -3160,7 +4574,7 @@ export async function discoverRebuildTargetNames(options) {
|
|
|
3160
4574
|
names.add(name);
|
|
3161
4575
|
const skillRoots = options.platform === 'codex'
|
|
3162
4576
|
? options.scope === 'project'
|
|
3163
|
-
? [
|
|
4577
|
+
? []
|
|
3164
4578
|
: [path.join(resolveAwHome(options.env), 'plugins')]
|
|
3165
4579
|
: [path.join(paths.configDir, 'skills')];
|
|
3166
4580
|
if (options.platform === 'codex' && options.scope === 'user') {
|
|
@@ -3195,34 +4609,59 @@ export async function discoverRebuildTargetNames(options) {
|
|
|
3195
4609
|
for (const name of Object.keys(config.snapshot.developerInstructions.managedBlocks)) {
|
|
3196
4610
|
names.add(sharedOwnerPluginName(name));
|
|
3197
4611
|
}
|
|
3198
|
-
for (const server of Object.values(config.snapshot.mcpServers)) {
|
|
3199
|
-
const envValue = server.value?.env;
|
|
3200
|
-
const owner = envValue
|
|
3201
|
-
&& typeof envValue === 'object'
|
|
3202
|
-
&& !Array.isArray(envValue)
|
|
3203
|
-
&& typeof envValue[MCP_OWNER_ENV] === 'string'
|
|
3204
|
-
? envValue[MCP_OWNER_ENV]
|
|
3205
|
-
: null;
|
|
3206
|
-
if (owner)
|
|
3207
|
-
names.add(sharedOwnerPluginName(owner));
|
|
3208
|
-
}
|
|
3209
4612
|
if (options.scope === 'project') {
|
|
3210
|
-
const
|
|
3211
|
-
const
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
4613
|
+
const suffix = `@${options.marketplaceName ?? MARKETPLACE_NAME}`;
|
|
4614
|
+
for (const pluginId of Object.keys(config.snapshot.plugins)) {
|
|
4615
|
+
if (!pluginId.endsWith(suffix))
|
|
4616
|
+
continue;
|
|
4617
|
+
const name = pluginId.slice(0, -suffix.length);
|
|
4618
|
+
if (/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name))
|
|
4619
|
+
names.add(name);
|
|
3216
4620
|
}
|
|
3217
4621
|
}
|
|
3218
4622
|
else {
|
|
3219
|
-
|
|
3220
|
-
|
|
4623
|
+
const marketplaceName = options.marketplaceName ?? MARKETPLACE_NAME;
|
|
4624
|
+
const marketplaceRoot = options.marketplaceRoot ?? resolveAwHome(options.env);
|
|
4625
|
+
const marketplace = config.snapshot.marketplaces[marketplaceName];
|
|
4626
|
+
const canonicalMarketplace = marketplace?.sourceType === 'local'
|
|
4627
|
+
&& !marketplace.sourcePointsToManifest
|
|
4628
|
+
&& path.normalize(marketplace.source ?? '') === path.normalize(marketplaceRoot);
|
|
4629
|
+
const suffix = `@${marketplaceName}`;
|
|
4630
|
+
const configuredPluginIds = Object.keys(config.snapshot.plugins)
|
|
4631
|
+
.filter((pluginId) => pluginId.endsWith(suffix));
|
|
4632
|
+
if (configuredPluginIds.length > 0 && !canonicalMarketplace) {
|
|
4633
|
+
throw new Error(`externalOwnershipPresent: Codex marketplace ${marketplaceName} has a foreign public config source.`);
|
|
4634
|
+
}
|
|
4635
|
+
for (const pluginId of canonicalMarketplace ? configuredPluginIds : []) {
|
|
4636
|
+
const name = pluginId.slice(0, -suffix.length);
|
|
4637
|
+
if (/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name))
|
|
4638
|
+
names.add(name);
|
|
4639
|
+
}
|
|
4640
|
+
const session = await createCodexNativeObservationSession({
|
|
4641
|
+
exactCwd: options.cwd,
|
|
3221
4642
|
env: options.env,
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
4643
|
+
runner: options.nativeCliRunner,
|
|
4644
|
+
});
|
|
4645
|
+
try {
|
|
4646
|
+
const snapshot = await session.snapshot(marketplaceName, 'user');
|
|
4647
|
+
if (!snapshot.neutral) {
|
|
4648
|
+
if (options.nativeFailureMode !== 'ignore') {
|
|
4649
|
+
throw new Error('codexNativeObservationFailed: cannot discover official Codex plugins.');
|
|
4650
|
+
}
|
|
4651
|
+
}
|
|
4652
|
+
else {
|
|
4653
|
+
for (const pluginId of snapshot.neutral.keys()) {
|
|
4654
|
+
if (!pluginId.endsWith(suffix))
|
|
4655
|
+
continue;
|
|
4656
|
+
const name = pluginId.slice(0, -suffix.length);
|
|
4657
|
+
if (/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name))
|
|
4658
|
+
names.add(name);
|
|
4659
|
+
}
|
|
4660
|
+
}
|
|
4661
|
+
}
|
|
4662
|
+
finally {
|
|
4663
|
+
await session.dispose();
|
|
4664
|
+
}
|
|
3226
4665
|
}
|
|
3227
4666
|
}
|
|
3228
4667
|
else {
|