@lesliechan721/aw 0.1.0-beta.64 → 0.1.0-beta.65

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.
Files changed (302) hide show
  1. package/dist/authoring/assets/aw-runtime-config.mjs +3 -3
  2. package/dist/authoring/assets/aw-runtime-config.mjs.map +1 -1
  3. package/dist/authoring/scripts/add-skill.mjs +3 -17
  4. package/dist/authoring/scripts/add-skill.mjs.map +1 -1
  5. package/dist/authoring/scripts/init-plugin.mjs +1 -0
  6. package/dist/authoring/scripts/init-plugin.mjs.map +1 -1
  7. package/dist/authoring/scripts/lib/plugin-version-check.mjs +1 -1
  8. package/dist/authoring/scripts/lib/skill-scaffold.d.mts +2 -0
  9. package/dist/authoring/scripts/lib/skill-scaffold.mjs +2 -0
  10. package/dist/authoring/scripts/lib/skill-scaffold.mjs.map +1 -1
  11. package/dist/authoring/scripts/validate-plugin.mjs +95 -698
  12. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
  13. package/dist/scripts/aw-skill-eval-materializer.d.ts +2 -0
  14. package/dist/scripts/aw-skill-eval-materializer.js +74 -44
  15. package/dist/scripts/aw-skill-eval-materializer.js.map +1 -1
  16. package/dist/scripts/bundle-plugins.js +10 -2
  17. package/dist/scripts/bundle-plugins.js.map +1 -1
  18. package/dist/scripts/plugin-staging.d.ts +1 -1
  19. package/dist/scripts/plugin-staging.js +1 -1
  20. package/dist/scripts/plugin-staging.js.map +1 -1
  21. package/dist/scripts/test-plugin-scripts.js +31 -31
  22. package/dist/scripts/test-plugin-scripts.js.map +1 -1
  23. package/dist/src/commands/aw-capabilities.d.ts +1 -1
  24. package/dist/src/commands/aw-capabilities.js +2 -0
  25. package/dist/src/commands/aw-capabilities.js.map +1 -1
  26. package/dist/src/commands/console.js +4 -3
  27. package/dist/src/commands/console.js.map +1 -1
  28. package/dist/src/commands/intent-command.d.ts +30 -0
  29. package/dist/src/commands/intent-command.js +164 -0
  30. package/dist/src/commands/intent-command.js.map +1 -0
  31. package/dist/src/commands/operation-plan.d.ts +2 -0
  32. package/dist/src/commands/operation-plan.js +2 -1
  33. package/dist/src/commands/operation-plan.js.map +1 -1
  34. package/dist/src/commands/options.d.ts +3 -11
  35. package/dist/src/commands/options.js +11 -17
  36. package/dist/src/commands/options.js.map +1 -1
  37. package/dist/src/commands/plugin-capabilities.js +27 -33
  38. package/dist/src/commands/plugin-capabilities.js.map +1 -1
  39. package/dist/src/commands/rebuild-command.d.ts +108 -6
  40. package/dist/src/commands/rebuild-command.js +617 -1549
  41. package/dist/src/commands/rebuild-command.js.map +1 -1
  42. package/dist/src/commands/rebuild-integration.d.ts +19 -3
  43. package/dist/src/commands/rebuild-integration.js +500 -459
  44. package/dist/src/commands/rebuild-integration.js.map +1 -1
  45. package/dist/src/commands/rebuild-operations.js +1 -2
  46. package/dist/src/commands/rebuild-operations.js.map +1 -1
  47. package/dist/src/commands/review-inspection.d.ts +2 -2
  48. package/dist/src/commands/review-inspection.js +30 -86
  49. package/dist/src/commands/review-inspection.js.map +1 -1
  50. package/dist/src/commands/review-preparation.js +16 -7
  51. package/dist/src/commands/review-preparation.js.map +1 -1
  52. package/dist/src/commands/skill-eval.d.ts +2 -0
  53. package/dist/src/commands/skill-eval.js +15 -9
  54. package/dist/src/commands/skill-eval.js.map +1 -1
  55. package/dist/src/commands/status-v6.d.ts +38 -5
  56. package/dist/src/commands/status-v6.js +269 -102
  57. package/dist/src/commands/status-v6.js.map +1 -1
  58. package/dist/src/commands/targets-command.d.ts +27 -0
  59. package/dist/src/commands/targets-command.js +77 -0
  60. package/dist/src/commands/targets-command.js.map +1 -0
  61. package/dist/src/commands/write-commands.js +5 -5
  62. package/dist/src/commands/write-commands.js.map +1 -1
  63. package/dist/src/eval/manifest-v1.d.ts +2 -0
  64. package/dist/src/eval/manifest-v1.js +7 -2
  65. package/dist/src/eval/manifest-v1.js.map +1 -1
  66. package/dist/src/generators/local-plugin.d.ts +2 -1
  67. package/dist/src/generators/local-plugin.js +111 -108
  68. package/dist/src/generators/local-plugin.js.map +1 -1
  69. package/dist/src/index.d.ts +10 -17
  70. package/dist/src/index.js +585 -1349
  71. package/dist/src/index.js.map +1 -1
  72. package/dist/src/intent/deployment-preferences.d.ts +63 -0
  73. package/dist/src/intent/deployment-preferences.js +170 -0
  74. package/dist/src/intent/deployment-preferences.js.map +1 -0
  75. package/dist/src/intent/diagnostics.d.ts +11 -0
  76. package/dist/src/intent/diagnostics.js +14 -0
  77. package/dist/src/intent/diagnostics.js.map +1 -0
  78. package/dist/src/intent/intent-codec.d.ts +19 -0
  79. package/dist/src/intent/intent-codec.js +117 -0
  80. package/dist/src/intent/intent-codec.js.map +1 -0
  81. package/dist/src/intent/intent-store.d.ts +43 -0
  82. package/dist/src/intent/intent-store.js +118 -0
  83. package/dist/src/intent/intent-store.js.map +1 -0
  84. package/dist/src/intent/scope-context.d.ts +33 -0
  85. package/dist/src/intent/scope-context.js +145 -0
  86. package/dist/src/intent/scope-context.js.map +1 -0
  87. package/dist/src/intent/work-context.d.ts +13 -0
  88. package/dist/src/intent/work-context.js +22 -0
  89. package/dist/src/intent/work-context.js.map +1 -0
  90. package/dist/src/output/command-receipt.js +16 -9
  91. package/dist/src/output/command-receipt.js.map +1 -1
  92. package/dist/src/portable/commands.d.ts +18 -8
  93. package/dist/src/portable/commands.js +46 -562
  94. package/dist/src/portable/commands.js.map +1 -1
  95. package/dist/src/portable/historical-materialization.d.ts +22 -0
  96. package/dist/src/portable/historical-materialization.js +124 -0
  97. package/dist/src/portable/historical-materialization.js.map +1 -0
  98. package/dist/src/portable/materializer.d.ts +8 -0
  99. package/dist/src/portable/materializer.js +75 -44
  100. package/dist/src/portable/materializer.js.map +1 -1
  101. package/dist/src/portable/recovery-layout.d.ts +17 -0
  102. package/dist/src/portable/recovery-layout.js +55 -0
  103. package/dist/src/portable/recovery-layout.js.map +1 -0
  104. package/dist/src/portable/recovery.d.ts +143 -0
  105. package/dist/src/portable/recovery.js +339 -0
  106. package/dist/src/portable/recovery.js.map +1 -0
  107. package/dist/src/portable/writer.d.ts +64 -0
  108. package/dist/src/portable/writer.js +316 -0
  109. package/dist/src/portable/writer.js.map +1 -0
  110. package/dist/src/recovery/coordinator.d.ts +137 -0
  111. package/dist/src/recovery/coordinator.js +1198 -0
  112. package/dist/src/recovery/coordinator.js.map +1 -0
  113. package/dist/src/recovery/demand.d.ts +44 -0
  114. package/dist/src/recovery/demand.js +178 -0
  115. package/dist/src/recovery/demand.js.map +1 -0
  116. package/dist/src/recovery/joint-coordinator.d.ts +24 -0
  117. package/dist/src/recovery/joint-coordinator.js +770 -0
  118. package/dist/src/recovery/joint-coordinator.js.map +1 -0
  119. package/dist/src/recovery/joint-demand.d.ts +68 -0
  120. package/dist/src/recovery/joint-demand.js +166 -0
  121. package/dist/src/recovery/joint-demand.js.map +1 -0
  122. package/dist/src/recovery/receipt.d.ts +81 -0
  123. package/dist/src/recovery/receipt.js +2 -0
  124. package/dist/src/recovery/receipt.js.map +1 -0
  125. package/dist/src/recovery/retirement.d.ts +45 -0
  126. package/dist/src/recovery/retirement.js +366 -0
  127. package/dist/src/recovery/retirement.js.map +1 -0
  128. package/dist/src/recovery/status.d.ts +108 -0
  129. package/dist/src/recovery/status.js +417 -0
  130. package/dist/src/recovery/status.js.map +1 -0
  131. package/dist/src/recovery/types.d.ts +86 -0
  132. package/dist/src/recovery/types.js +2 -0
  133. package/dist/src/recovery/types.js.map +1 -0
  134. package/dist/src/review/review-profile.d.ts +2 -1
  135. package/dist/src/review/review-profile.js +15 -15
  136. package/dist/src/review/review-profile.js.map +1 -1
  137. package/dist/src/review/review-selection.d.ts +1 -2
  138. package/dist/src/review/review-selection.js +40 -114
  139. package/dist/src/review/review-selection.js.map +1 -1
  140. package/dist/src/review/review-types.d.ts +4 -4
  141. package/dist/src/review/review-types.js.map +1 -1
  142. package/dist/src/review/skill-materialization.js +15 -6
  143. package/dist/src/review/skill-materialization.js.map +1 -1
  144. package/dist/src/review/source-binding.d.ts +14 -4
  145. package/dist/src/review/source-binding.js +62 -117
  146. package/dist/src/review/source-binding.js.map +1 -1
  147. package/dist/src/tui/agent-management.d.ts +23 -0
  148. package/dist/src/tui/agent-management.js +217 -0
  149. package/dist/src/tui/agent-management.js.map +1 -0
  150. package/dist/src/tui/agent-settings.d.ts +44 -0
  151. package/dist/src/tui/agent-settings.js +98 -0
  152. package/dist/src/tui/agent-settings.js.map +1 -0
  153. package/dist/src/tui/components.d.ts +3 -0
  154. package/dist/src/tui/components.js +10 -1
  155. package/dist/src/tui/components.js.map +1 -1
  156. package/dist/src/tui/config-references.d.ts +6 -3
  157. package/dist/src/tui/config-references.js +17 -12
  158. package/dist/src/tui/config-references.js.map +1 -1
  159. package/dist/src/tui/console-app-v4.d.ts +1 -2
  160. package/dist/src/tui/console-app-v4.js +1533 -1047
  161. package/dist/src/tui/console-app-v4.js.map +1 -1
  162. package/dist/src/tui/controller-runtime.d.ts +2 -1
  163. package/dist/src/tui/controller-runtime.js +405 -1626
  164. package/dist/src/tui/controller-runtime.js.map +1 -1
  165. package/dist/src/tui/controller.d.ts +131 -81
  166. package/dist/src/tui/controller.js +307 -227
  167. package/dist/src/tui/controller.js.map +1 -1
  168. package/dist/src/tui/entity-cards.d.ts +2 -2
  169. package/dist/src/tui/entity-cards.js +98 -20
  170. package/dist/src/tui/entity-cards.js.map +1 -1
  171. package/dist/src/tui/glossary.d.ts +2 -0
  172. package/dist/src/tui/glossary.js +20 -0
  173. package/dist/src/tui/glossary.js.map +1 -1
  174. package/dist/src/tui/model.d.ts +28 -10
  175. package/dist/src/tui/model.js +41 -41
  176. package/dist/src/tui/model.js.map +1 -1
  177. package/dist/src/tui/operation-review-planner.d.ts +2 -0
  178. package/dist/src/tui/operation-review-planner.js +58 -122
  179. package/dist/src/tui/operation-review-planner.js.map +1 -1
  180. package/dist/src/tui/plugin-content.js +7 -4
  181. package/dist/src/tui/plugin-content.js.map +1 -1
  182. package/dist/src/tui/plugin-detail.d.ts +22 -3
  183. package/dist/src/tui/plugin-detail.js +113 -26
  184. package/dist/src/tui/plugin-detail.js.map +1 -1
  185. package/dist/src/tui/reader.d.ts +1 -0
  186. package/dist/src/tui/reader.js +3 -1
  187. package/dist/src/tui/reader.js.map +1 -1
  188. package/dist/src/tui/router.d.ts +23 -33
  189. package/dist/src/tui/router.js.map +1 -1
  190. package/dist/src/tui/scoped-flow.d.ts +45 -0
  191. package/dist/src/tui/scoped-flow.js +96 -0
  192. package/dist/src/tui/scoped-flow.js.map +1 -0
  193. package/dist/src/tui/scoped-inventory.d.ts +122 -0
  194. package/dist/src/tui/scoped-inventory.js +225 -0
  195. package/dist/src/tui/scoped-inventory.js.map +1 -0
  196. package/dist/src/tui/screen-registry.d.ts +25 -25
  197. package/dist/src/tui/screen-registry.js +3 -7
  198. package/dist/src/tui/screen-registry.js.map +1 -1
  199. package/dist/src/tui/session-console-app.js +1 -1
  200. package/dist/src/tui/session-console-app.js.map +1 -1
  201. package/dist/src/tui/session-host.js +12 -2
  202. package/dist/src/tui/session-host.js.map +1 -1
  203. package/dist/src/types.d.ts +15 -12
  204. package/dist/src/types.js.map +1 -1
  205. package/dist/src/utils/agent-version.js +2 -0
  206. package/dist/src/utils/agent-version.js.map +1 -1
  207. package/dist/src/utils/artifact-plan.js +13 -6
  208. package/dist/src/utils/artifact-plan.js.map +1 -1
  209. package/dist/src/utils/artifact-renderer.d.ts +6 -3
  210. package/dist/src/utils/artifact-renderer.js +27 -32
  211. package/dist/src/utils/artifact-renderer.js.map +1 -1
  212. package/dist/src/utils/artifact-request.d.ts +1 -0
  213. package/dist/src/utils/artifact-request.js +22 -12
  214. package/dist/src/utils/artifact-request.js.map +1 -1
  215. package/dist/src/utils/artifact-sources.d.ts +2 -2
  216. package/dist/src/utils/bundle-layout.js +18 -16
  217. package/dist/src/utils/bundle-layout.js.map +1 -1
  218. package/dist/src/utils/catalog-digests.js +2 -0
  219. package/dist/src/utils/catalog-digests.js.map +1 -1
  220. package/dist/src/utils/catalog-runtime-projection.d.ts +4 -3
  221. package/dist/src/utils/catalog-runtime-projection.js.map +1 -1
  222. package/dist/src/utils/catalog-source-references.d.ts +5 -0
  223. package/dist/src/utils/catalog-source-references.js +42 -57
  224. package/dist/src/utils/catalog-source-references.js.map +1 -1
  225. package/dist/src/utils/claude-native-render-receipt.d.ts +14 -0
  226. package/dist/src/utils/claude-native-render-receipt.js +26 -0
  227. package/dist/src/utils/claude-native-render-receipt.js.map +1 -0
  228. package/dist/src/utils/claude-skills-root.js +14 -3
  229. package/dist/src/utils/claude-skills-root.js.map +1 -1
  230. package/dist/src/utils/codex-manager.js +6 -12
  231. package/dist/src/utils/codex-manager.js.map +1 -1
  232. package/dist/src/utils/codex-native-observation.js +29 -13
  233. package/dist/src/utils/codex-native-observation.js.map +1 -1
  234. package/dist/src/utils/codex-reconciler.d.ts +2 -5
  235. package/dist/src/utils/codex-reconciler.js +25 -1
  236. package/dist/src/utils/codex-reconciler.js.map +1 -1
  237. package/dist/src/utils/codex-surface-analysis.js +5 -3
  238. package/dist/src/utils/codex-surface-analysis.js.map +1 -1
  239. package/dist/src/utils/local-marketplace.d.ts +14 -2
  240. package/dist/src/utils/local-marketplace.js +209 -42
  241. package/dist/src/utils/local-marketplace.js.map +1 -1
  242. package/dist/src/utils/managed-command-evidence.js +1 -1
  243. package/dist/src/utils/managed-command-evidence.js.map +1 -1
  244. package/dist/src/utils/managed-command-materialization.d.ts +4 -0
  245. package/dist/src/utils/managed-command-materialization.js +15 -6
  246. package/dist/src/utils/managed-command-materialization.js.map +1 -1
  247. package/dist/src/utils/managed-command-store.d.ts +15 -0
  248. package/dist/src/utils/managed-command-store.js +73 -8
  249. package/dist/src/utils/managed-command-store.js.map +1 -1
  250. package/dist/src/utils/native-cli.d.ts +2 -0
  251. package/dist/src/utils/native-cli.js +22 -0
  252. package/dist/src/utils/native-cli.js.map +1 -1
  253. package/dist/src/utils/native-demand.d.ts +10 -0
  254. package/dist/src/utils/native-demand.js +4 -0
  255. package/dist/src/utils/native-demand.js.map +1 -0
  256. package/dist/src/utils/ownership.js +2 -0
  257. package/dist/src/utils/ownership.js.map +1 -1
  258. package/dist/src/utils/paths.js +1 -1
  259. package/dist/src/utils/paths.js.map +1 -1
  260. package/dist/src/utils/platform-support.d.ts +1 -1
  261. package/dist/src/utils/platform-support.js +1 -1
  262. package/dist/src/utils/platform-support.js.map +1 -1
  263. package/dist/src/utils/plugin-config-impact.d.ts +3 -0
  264. package/dist/src/utils/plugin-config-impact.js +115 -273
  265. package/dist/src/utils/plugin-config-impact.js.map +1 -1
  266. package/dist/src/utils/plugin-config-references.d.ts +9 -2
  267. package/dist/src/utils/plugin-config-references.js +75 -85
  268. package/dist/src/utils/plugin-config-references.js.map +1 -1
  269. package/dist/src/utils/plugin-config-write.d.ts +58 -1
  270. package/dist/src/utils/plugin-config-write.js +756 -809
  271. package/dist/src/utils/plugin-config-write.js.map +1 -1
  272. package/dist/src/utils/plugin-config.d.ts +47 -46
  273. package/dist/src/utils/plugin-config.js +292 -833
  274. package/dist/src/utils/plugin-config.js.map +1 -1
  275. package/dist/src/utils/plugin-scripts.d.ts +11 -6
  276. package/dist/src/utils/plugin-scripts.js +52 -35
  277. package/dist/src/utils/plugin-scripts.js.map +1 -1
  278. package/dist/src/utils/runtime-config.js +5 -4
  279. package/dist/src/utils/runtime-config.js.map +1 -1
  280. package/dist/src/utils/runtime-skill-closure.d.ts +2 -2
  281. package/dist/src/utils/runtime-skill-closure.js.map +1 -1
  282. package/dist/src/utils/state-paths.d.ts +8 -3
  283. package/dist/src/utils/state-paths.js +53 -16
  284. package/dist/src/utils/state-paths.js.map +1 -1
  285. package/dist/src/utils/windows-npm-shim.d.ts +2 -0
  286. package/dist/src/utils/windows-npm-shim.js +5 -7
  287. package/dist/src/utils/windows-npm-shim.js.map +1 -1
  288. package/dist/vitest.config.js +1 -0
  289. package/dist/vitest.config.js.map +1 -1
  290. package/package.json +3 -3
  291. package/dist/src/commands/sync-command.d.ts +0 -40
  292. package/dist/src/commands/sync-command.js +0 -254
  293. package/dist/src/commands/sync-command.js.map +0 -1
  294. package/dist/src/portable/skills-cli.d.ts +0 -51
  295. package/dist/src/portable/skills-cli.js +0 -202
  296. package/dist/src/portable/skills-cli.js.map +0 -1
  297. package/dist/src/tui/project-inventory.d.ts +0 -68
  298. package/dist/src/tui/project-inventory.js +0 -203
  299. package/dist/src/tui/project-inventory.js.map +0 -1
  300. package/dist/src/utils/plugin-intent-state.d.ts +0 -49
  301. package/dist/src/utils/plugin-intent-state.js +0 -320
  302. package/dist/src/utils/plugin-intent-state.js.map +0 -1
@@ -16,10 +16,10 @@ import { supportsPlatform } from '../utils/platform-support.js';
16
16
  import { analyzeCodexSurface } from '../utils/codex-surface-analysis.js';
17
17
  import { buildGeneratedName } from '../generators/shared.js';
18
18
  import { createCodexReconcilePlan, } from '../utils/codex-reconciler.js';
19
- import { readCodexConfigFile } from '../utils/codex-config.js';
19
+ import { parseCodexConfig, readCodexConfigFile } from '../utils/codex-config.js';
20
20
  import { isCanonicalCodexMarketplaceRegistration } from '../utils/codex-shared-native.js';
21
21
  import { executeRebuildComponentCleanup, executeRebuildOperation, prepareRebuildOperation, } from './rebuild-operations.js';
22
- import { assertNoClaudeUnboundNativeNameCollision, CodexActivationPostcheckError, createRebuildIntegration, executeCodexReconcileActivations, inspectCodexPluginRegistrationLive, inspectCodexSharedNativeLive, hasPreparedStaticNativeCommandDrift, inspectNativeMarketplaceRegistrations, removeCodexPluginActivation, removeNamespacedMarketplacePluginProjection, postcheckCodexReconcileActivations, } from './rebuild-integration.js';
22
+ import { assertNoClaudeUnboundNativeNameCollision, CodexActivationPostcheckError, createRebuildIntegration, executeCodexReconcileActivations, inspectCodexPluginRegistrationLive, inspectCodexSharedNativeLive, hasPreparedStaticNativeCommandDrift, inspectNativeMarketplaceRegistrations, isNativeActivationOnlyTarget, postcheckCodexReconcileActivations, removeForceCleanupCodexRoleRegistration, } from './rebuild-integration.js';
23
23
  import { assertSafeNamespacedMarketplacePluginRoot, getNamespacedMarketplaceName, readStrictNamespacedMarketplaceRoot, } from '../utils/local-marketplace.js';
24
24
  import { formatPluginId, parsePluginId, planPluginSelectorSourceIds, preflightDesiredArtifactCollisions, withResolvedMultiSourceCatalog, } from '../utils/multi-source-catalog.js';
25
25
  import { calculateCodexNativeRenderPayloadDigest, calculateCodexSharedNativePayloadDigest, } from '../utils/catalog-digests.js';
@@ -27,17 +27,17 @@ import { resolveCatalogOperationContext, } from '../utils/catalog-operation-cont
27
27
  import { assertStrictSemVer } from '../utils/manifest.js';
28
28
  import { assertCatalogSourceInventoryComplete, canonicalMarketplaceRegistrationNames, inventoryCatalogSourceReferences, } from '../utils/catalog-source-references.js';
29
29
  import { sanitizeCatalogSourceErrorMessage } from '../utils/catalog-public-error.js';
30
- import { inspectPluginConfigResetTarget, readNamespacedPluginConfigStoreIdentity, resetPluginConfigStore, resolvePluginConfigStorePath, resolveEffectivePluginConfig, } from '../utils/plugin-config.js';
30
+ import { readSafeOperationError } from '../utils/safe-operation-diagnostic.js';
31
+ import { inspectPluginConfigResetTarget, readNamespacedPluginConfigStoreIdentity, resolvePluginConfigStorePath, resolveEffectivePluginConfig, } from '../utils/plugin-config.js';
31
32
  import { resolveArtifactRenderConfig } from '../utils/artifact-renderer.js';
32
33
  import { pluginConfigTargetRenderScopes } from '../utils/plugin-config-impact.js';
33
34
  import { inspectOwnedComponents, parseOwnedTextHeader, readSkillOwnerMarker, SKILL_OWNER_MARKER, } from '../utils/ownership.js';
34
35
  import { resolveActiveCatalogGeneration, resolveActiveCatalogPluginShallow } from '../utils/catalog-generation-resolver.js';
35
- import { canonicalizePathThroughExistingAncestorSync, codexProjectConfigAliasesUserHome, getClaudeHome, getCodexHome, resolveCanonicalRepoProjectKey, resolveRepoRootSync, } from '../utils/state-paths.js';
36
+ import { canonicalizePathThroughExistingAncestorSync, codexProjectConfigAliasesUserHome, getClaudeHome, getCodexHome, resolveRepoRootSync, } from '../utils/state-paths.js';
36
37
  import { resolvePluginMetaDependencyClosure } from '../utils/plugin-dependencies.js';
37
38
  import { readCatalogSourcesConfigSnapshot } from '../utils/catalog-sources.js';
38
39
  import { catalogGenerationErrorCode, isOfflineCleanupCatalogGenerationError, } from '../utils/catalog-generation-error.js';
39
- import { canonicalizePluginIntentState, calculatePluginIntentDigest, createEmptyPluginIntentState, mergePluginIntentRootPlatforms, readPluginIntentState, removePluginIntentRootPlatforms, writePluginIntentState, } from '../utils/plugin-intent-state.js';
40
- import { assertOperationPlanFingerprint, createBatchIntentCompatiblePlanFingerprint, createOperationPlanConfigDigest, createOperationPlan, } from './operation-plan.js';
40
+ import { assertOperationPlanFingerprint, createOperationPlanConfigDigest, createOperationPlan, } from './operation-plan.js';
41
41
  import { validateCatalogSnapshotComplete, validateCatalogSnapshotPluginEntriesComplete, } from '../utils/catalog-snapshot.js';
42
42
  import { AwCapabilityUnsupportedError, IMPLEMENTED_AW_CAPABILITIES, } from './aw-capabilities.js';
43
43
  import { checkAwCapabilitySupport } from '../utils/manifest.js';
@@ -100,7 +100,7 @@ function assertRebuildCatalogSourceInventoryComplete(inventory, targetScope) {
100
100
  || !error.kind.startsWith('config')),
101
101
  });
102
102
  }
103
- const MALFORMED_CONFIG_PRESERVED_WARNING = 'pluginConfigPreserved: 目标插件的无效配置文件已保留。可稍后运行 aw config reset 清理。';
103
+ const MALFORMED_CONFIG_PRESERVED_WARNING = 'pluginConfigPreserved: 目标插件的无效配置文件已保留。可稍后运行 aw plugin config reset <source/plugin> --context user 清理。';
104
104
  const OBSOLETE_CONTRACTS_CLEANUP_WARNING = 'obsoleteContractsCleanupFailed: Obsolete installation contracts could not be removed.';
105
105
  function omitSelectedPluginConfigErrors(inventory, targets) {
106
106
  let omitted = false;
@@ -154,7 +154,7 @@ async function selectedProjectCodexSkillReferences(options) {
154
154
  return references;
155
155
  }
156
156
  const cleanupProjectSkillsByInventory = new WeakMap();
157
- function selectCodexActivationPlans(plans, targetScopes, preservedActivations) {
157
+ function selectCodexActivationPlans(plans, targetScopes, preservedActivations, originalUserActivations) {
158
158
  return plans.flatMap((plan) => {
159
159
  const includesScope = (pluginId, scope) => targetScopes.has(`${pluginId}\0${scope}`);
160
160
  const pluginIds = new Set(plan.desiredTargets.flatMap((target) => ['user', 'project'].some((scope) => includesScope(target.pluginId, scope))
@@ -162,9 +162,21 @@ function selectCodexActivationPlans(plans, targetScopes, preservedActivations) {
162
162
  : []));
163
163
  if (pluginIds.size === 0)
164
164
  return [];
165
- const includesActivationAction = (action) => includesScope(action.pluginId, action.scope) || !action.desired;
165
+ const includesActivationAction = (action) => includesScope(action.pluginId, action.scope);
166
166
  const sharedNativeActions = plan.sharedNativeActions.filter((action) => pluginIds.has(action.pluginId));
167
167
  const userActions = plan.activationActions.user.filter((action) => pluginIds.has(action.pluginId) && includesActivationAction(action));
168
+ for (const action of plan.activationActions.user) {
169
+ if (!originalUserActivations || includesScope(action.pluginId, 'user')
170
+ || !includesScope(action.pluginId, 'project')
171
+ || !sharedNativeActions.some((shared) => shared.pluginId === action.pluginId
172
+ && (shared.action === 'install' || shared.action === 'replace' || shared.action === 'repair'))) {
173
+ continue;
174
+ }
175
+ const original = originalUserActivations.get(action.codexPluginId) === true;
176
+ if ((preservedActivations?.user.get(action.codexPluginId) === true) === original)
177
+ continue;
178
+ userActions.push({ ...action, action: original ? 'enable' : 'disable', desired: original });
179
+ }
168
180
  const projectActions = plan.activationActions.project.filter((action) => pluginIds.has(action.pluginId) && includesActivationAction(action));
169
181
  const desiredTargets = plan.desiredTargets.flatMap((target) => {
170
182
  const scopes = target.scopes.filter((scope) => includesScope(target.pluginId, scope));
@@ -188,6 +200,7 @@ function selectCodexActivationPlans(plans, targetScopes, preservedActivations) {
188
200
  return [{
189
201
  ...plan,
190
202
  desiredTargets,
203
+ sharedNativeActions,
191
204
  activationActions: { user: userActions, project: projectActions },
192
205
  extensionWrites: plan.extensionWrites.filter((action) => includesScope(action.pluginId, action.scope)),
193
206
  extensionRemovals: plan.extensionRemovals.filter((action) => includesScope(action.pluginId, action.scope)),
@@ -227,134 +240,20 @@ function failedCodexActivationTargetScopes(options) {
227
240
  }
228
241
  return failed.size > 0 ? failed : new Set(options.successfulTargetScopes);
229
242
  }
230
- function updateIntentOverrides(options) {
231
- const plannedKeys = new Set(options.plan.targets.map((target) => `${target.pluginId}\0${target.platform}`));
232
- return canonicalizePluginIntentState({
233
- ...options.state,
234
- dependencyScopeOverrides: [
235
- ...options.state.dependencyScopeOverrides.filter((override) => !plannedKeys.has(`${override.pluginId}\0${override.platform}`)),
236
- ...options.plan.scopeOverrides,
237
- ],
238
- });
239
- }
240
243
  function effectiveDependencyScopeOverrides(options) {
241
244
  const nodeKeys = new Set(options.nodes.map((node) => `${node.pluginId}\0${node.platform}`));
242
245
  const explicitMatches = (pluginId, platform) => options.explicit.some((override) => override.pluginId === pluginId
243
246
  && (override.platform === undefined || override.platform === platform));
244
247
  return [
245
- ...options.state.dependencyScopeOverrides.filter((override) => nodeKeys.has(`${override.pluginId}\0${override.platform}`)
248
+ ...(options.state.dependencyScopeOverrides ?? []).filter((override) => nodeKeys.has(`${override.pluginId}\0${override.platform}`)
246
249
  && !options.ignoredPersistedRootIds?.has(override.pluginId)
247
250
  && !explicitMatches(override.pluginId, override.platform)),
248
251
  ...options.explicit.filter((override) => options.nodes.some((node) => node.pluginId === override.pluginId
249
252
  && (override.platform === undefined || node.platform === override.platform))),
250
253
  ];
251
254
  }
252
- function pruneUnreachableDependencyScopeOverrides(state, catalogEntries) {
253
- if (!catalogEntries || state.dependencyScopeOverrides.length === 0)
254
- return state;
255
- const byId = new Map(catalogEntries.map((entry) => [entry.pluginId, entry]));
256
- const reachable = new Set();
257
- const visiting = new Set();
258
- const visited = new Set();
259
- const visit = (pluginId, platform, root) => {
260
- const entry = byId.get(pluginId);
261
- if (!entry || visiting.has(pluginId))
262
- return false;
263
- const visitKey = `${pluginId}\0${platform}`;
264
- if (visited.has(visitKey)) {
265
- if (!root)
266
- reachable.add(visitKey);
267
- return true;
268
- }
269
- visiting.add(pluginId);
270
- for (const dependencyPluginId of entry.dependencyPluginIds) {
271
- if (!visit(dependencyPluginId, platform, false))
272
- return false;
273
- }
274
- visiting.delete(pluginId);
275
- visited.add(visitKey);
276
- if (!root)
277
- reachable.add(visitKey);
278
- return true;
279
- };
280
- for (const root of state.roots) {
281
- for (const platform of root.platforms) {
282
- visiting.clear();
283
- if (!visit(root.pluginId, platform, true))
284
- return state;
285
- }
286
- }
287
- return canonicalizePluginIntentState({
288
- ...state,
289
- dependencyScopeOverrides: state.dependencyScopeOverrides.filter((override) => reachable.has(`${override.pluginId}\0${override.platform}`)),
290
- });
291
- }
292
- function deriveDesiredIntentState(options) {
293
- let next = options.state;
294
- if (options.command === 'install') {
295
- for (const root of options.selectedRoots)
296
- next = mergePluginIntentRootPlatforms(next, root);
297
- if (options.selectedRoots.length > 0) {
298
- next = updateIntentOverrides({ state: next, plan: options.plan });
299
- }
300
- }
301
- else if (options.command === 'uninstall' || options.command === 'cleanup') {
302
- for (const root of options.selectedRoots)
303
- next = removePluginIntentRootPlatforms(next, root);
304
- if (options.command === 'uninstall') {
305
- next = pruneUnreachableDependencyScopeOverrides(next, options.catalogEntries);
306
- }
307
- }
308
- return next;
309
- }
310
- function removeSelectedRootPlatformsFromOtherScope(options) {
311
- let next = options.state;
312
- for (const root of options.selectedRoots) {
313
- const platforms = root.platforms.filter((platform) => platform !== 'codex' || !options.preserveCodexRootIds.has(root.pluginId));
314
- if (platforms.length > 0) {
315
- next = removePluginIntentRootPlatforms(next, { ...root, platforms });
316
- }
317
- }
318
- return next;
319
- }
320
- async function writeDesiredIntentSuperset(options) {
321
- const location = { scope: options.scope, cwd: options.cwd, env: options.env };
322
- let next = deriveDesiredIntentState({
323
- command: options.command,
324
- state: await readPluginIntentState(location),
325
- selectedRoots: options.selectedRoots,
326
- plan: options.plan,
327
- catalogEntries: options.catalogEntries,
328
- });
329
- const superset = next;
330
- await writePluginIntentState(location, superset);
331
- const writes = [{ location, superset }];
332
- if (options.command === 'install' || options.codexPlan) {
333
- const otherScope = options.scope === 'user' ? 'project' : 'user';
334
- const otherLocation = { scope: otherScope, cwd: options.cwd, env: options.env };
335
- let other = await readPluginIntentState(otherLocation);
336
- if (options.command === 'install' && options.removeOtherScopeRoots !== false) {
337
- const protectedOtherRootPlatforms = new Set(options.plan.targets.flatMap((target) => target.relation === 'requested'
338
- && (target.requestedScope ?? target.resolvedScope) === otherScope
339
- ? [`${target.pluginId}\0${target.platform}`]
340
- : []));
341
- other = removeSelectedRootPlatformsFromOtherScope({
342
- state: other,
343
- selectedRoots: options.selectedRoots.flatMap((root) => {
344
- const platforms = root.platforms.filter((platform) => !protectedOtherRootPlatforms.has(`${root.pluginId}\0${platform}`));
345
- return platforms.length > 0 ? [{ ...root, platforms }] : [];
346
- }),
347
- preserveCodexRootIds: options.preserveOtherCodexScopeRootIds,
348
- });
349
- }
350
- const otherSuperset = other;
351
- await writePluginIntentState(otherLocation, otherSuperset);
352
- writes.push({
353
- location: otherLocation,
354
- superset: otherSuperset,
355
- });
356
- }
357
- return writes;
255
+ function nativeDemandDigest(demand) {
256
+ return `sha256:${crypto.createHash('sha256').update(JSON.stringify(demand)).digest('hex')}`;
358
257
  }
359
258
  function createSyntheticPlugin(pluginName, env) {
360
259
  const rootDir = path.join(resolveAwHome(env), 'plugins', pluginName);
@@ -416,18 +315,6 @@ function toTargetResult(result) {
416
315
  } : {}),
417
316
  };
418
317
  }
419
- function toRemovedCleanupTargetResult(result) {
420
- const target = toTargetResult(result);
421
- if (result.status === 'succeeded' || !result.target.pluginId)
422
- return target;
423
- return {
424
- ...target,
425
- recoveryActions: [
426
- `repair the reported ownership, native CLI, or config path evidence, then run aw cleanup ${result.target.pluginId} --platform both --scope user --yes`,
427
- 'use cleanup --force only for exclusively owned damaged output, then retry ordinary cleanup',
428
- ],
429
- };
430
- }
431
318
  async function assertClaudeVersion(options) {
432
319
  if (!options.platforms.includes('claude'))
433
320
  return;
@@ -454,6 +341,9 @@ async function resolveRequestedNames(options) {
454
341
  }
455
342
  async function runSingleSourceRebuildCommand(options) {
456
343
  const runtime = {
344
+ preparedArtifactRenderResults: options.runtime?.preparedArtifactRenderResults,
345
+ signal: options.runtime?.signal,
346
+ scriptTimeoutMs: options.commandOptions.scriptTimeoutMs,
457
347
  env: options.runtime?.env ?? process.env,
458
348
  nativeCliRunner: options.runtime?.nativeCliRunner ?? getDefaultNativeCliRunner(),
459
349
  reporter: options.runtime?.reporter ?? createNoopReporter(),
@@ -464,6 +354,8 @@ async function runSingleSourceRebuildCommand(options) {
464
354
  codexObservationSession: options.runtime?.codexObservationSession,
465
355
  preApplyExecutionCache: options.runtime?.preApplyExecutionCache,
466
356
  managedCommandProducerCache: options.runtime?.managedCommandProducerCache ?? new Map(),
357
+ preserveLegacyNativeSkills: options.runtime?.preserveLegacyNativeSkills,
358
+ nativeActivationOnlyTargets: options.runtime?.nativeActivationOnlyTargets,
467
359
  commandResults: options.runtime?.commandResults ?? [],
468
360
  };
469
361
  const env = runtime.env;
@@ -663,6 +555,35 @@ function toCatalogResolution(source) {
663
555
  channel: source.manifest.channel,
664
556
  };
665
557
  }
558
+ function preparedNativeFootprints(targets) {
559
+ return targets.flatMap((target) => (target.desiredOwnedComponents ?? target.ownedComponents).flatMap((component) => {
560
+ let outputPath;
561
+ if ('path' in component)
562
+ outputPath = component.path;
563
+ else if ('locator' in component) {
564
+ const locator = component.locator;
565
+ if ('configPath' in locator)
566
+ outputPath = locator.configPath;
567
+ else if ('settingsPath' in locator)
568
+ outputPath = locator.settingsPath;
569
+ else if ('hooksPath' in locator)
570
+ outputPath = locator.hooksPath;
571
+ else if ('marketplaceRoot' in locator)
572
+ outputPath = locator.marketplaceRoot;
573
+ }
574
+ return outputPath ? [{
575
+ path: path.resolve(outputPath),
576
+ pluginId: target.identity.pluginId ?? target.identity.pluginName,
577
+ platform: target.identity.platform,
578
+ scope: target.identity.scope,
579
+ shared: component.kind === 'shared-config' || component.kind === 'managed-block',
580
+ ...(component.kind === 'shared-config'
581
+ && (component.locator.kind === 'codex-marketplace'
582
+ || component.locator.kind === 'claude-marketplace')
583
+ ? { sharedMarketplace: true } : {}),
584
+ }] : [];
585
+ }));
586
+ }
666
587
  function mergeRebuildCommandResults(results) {
667
588
  const rebuildResults = results.flatMap((result) => result.rebuildResults);
668
589
  const platforms = [...new Set(results.flatMap((result) => result.platforms))];
@@ -674,9 +595,12 @@ function mergeRebuildCommandResults(results) {
674
595
  targetResults: results.flatMap((result) => result.targetResults),
675
596
  ...(results.some((result) => result.commandResults?.length) ? { commandResults: results.flatMap((result) => result.commandResults ?? []) } : {}),
676
597
  overallState: rebuildResults.every((result) => result.status === 'succeeded')
677
- ? 'succeeded'
598
+ ? results.some((result) => result.overallState === 'partial') ? 'partial' : 'succeeded'
678
599
  : rebuildResults.some((result) => result.status === 'succeeded') ? 'partial' : 'failed',
679
600
  receiptTargets: results.flatMap((result) => result.receiptTargets),
601
+ ...(results.some((result) => result.nativeFootprints !== undefined)
602
+ ? { nativeFootprints: results.flatMap((result) => result.nativeFootprints ?? []) }
603
+ : {}),
680
604
  ...(results.find((result) => result.dependencyPlan)?.dependencyPlan
681
605
  ? { dependencyPlan: results.find((result) => result.dependencyPlan).dependencyPlan }
682
606
  : {}),
@@ -689,9 +613,6 @@ function mergeRebuildCommandResults(results) {
689
613
  ...(results.some((result) => result.offlineCleanupPlans !== undefined)
690
614
  ? { offlineCleanupPlans: results.flatMap((result) => result.offlineCleanupPlans ?? []) }
691
615
  : {}),
692
- ...(results.some((result) => result.removedPluginCleanupIds !== undefined)
693
- ? { removedPluginCleanupIds: [...new Set(results.flatMap((result) => result.removedPluginCleanupIds ?? []))] }
694
- : {}),
695
616
  };
696
617
  }
697
618
  function reconcileOverallState(targetResults, plan, rebuildResults = []) {
@@ -716,30 +637,6 @@ function reconcileOverallState(targetResults, plan, rebuildResults = []) {
716
637
  return completedRequested || completedMutation || observedMutation ? 'partial' : 'failed';
717
638
  }
718
639
  function createRebuildOperationPlan(options) {
719
- const intentChanges = ['user', 'project'].flatMap((scope) => {
720
- const selectedRoots = [...new Map(options.dependencyPlan.targets.flatMap((target) => target.relation === 'requested'
721
- && (target.requestedScope ?? target.resolvedScope) === scope
722
- ? [[target.pluginId, {
723
- pluginId: target.pluginId,
724
- platforms: options.dependencyPlan.targets.flatMap((candidate) => candidate.relation === 'requested'
725
- && candidate.pluginId === target.pluginId
726
- && (candidate.requestedScope ?? candidate.resolvedScope) === scope
727
- ? [candidate.platform]
728
- : []),
729
- }]]
730
- : [])).values()];
731
- const intentBefore = options.intentBeforeByScope[scope];
732
- const intentAfter = deriveDesiredIntentState({
733
- command: options.command,
734
- state: intentBefore,
735
- selectedRoots,
736
- plan: options.dependencyPlan,
737
- catalogEntries: options.catalogEntries,
738
- });
739
- const beforeDigest = calculatePluginIntentDigest(intentBefore);
740
- const afterDigest = calculatePluginIntentDigest(intentAfter);
741
- return beforeDigest === afterDigest ? [] : [{ scope, beforeDigest, afterDigest }];
742
- });
743
640
  const actions = [
744
641
  ...(options.targetActions ?? []),
745
642
  ...options.codexPlans.flatMap((plan) => [
@@ -771,7 +668,7 @@ function createRebuildOperationPlan(options) {
771
668
  ]),
772
669
  ];
773
670
  return createOperationPlan({
774
- intentChanges,
671
+ intentChanges: [],
775
672
  actions,
776
673
  changes: options.dependencyPlan.targets.map((target) => [
777
674
  target.pluginId,
@@ -785,10 +682,7 @@ function createRebuildOperationPlan(options) {
785
682
  sourceId: source.sourceId,
786
683
  digest: source.manifestDigest,
787
684
  })),
788
- intentDigests: ['user', 'project'].map((scope) => ({
789
- scope,
790
- digest: calculatePluginIntentDigest(options.intentBeforeByScope[scope]),
791
- })),
685
+ intentDigests: [{ scope: options.scope, digest: nativeDemandDigest(options.requirements) }],
792
686
  configDigests: options.configDigests,
793
687
  targetOwnerFingerprints: options.dependencyPlan.targets.map((target) => ({
794
688
  pluginId: target.pluginId,
@@ -878,18 +772,6 @@ function cleanupIntentTargets(options) {
878
772
  }];
879
773
  }));
880
774
  }
881
- function cleanupSelectedRoots(targets) {
882
- const platformsByPluginId = new Map();
883
- for (const target of targets) {
884
- const platforms = platformsByPluginId.get(target.pluginId) ?? new Set();
885
- platforms.add(target.platform);
886
- platformsByPluginId.set(target.pluginId, platforms);
887
- }
888
- return [...platformsByPluginId].map(([pluginId, platforms]) => ({
889
- pluginId,
890
- platforms: [...platforms],
891
- }));
892
- }
893
775
  function cleanupIntentResult(target) {
894
776
  return {
895
777
  status: 'succeeded',
@@ -1113,6 +995,7 @@ async function cleanupOfflineOwnedComponents(options) {
1113
995
  ...(codexManager ? { codexManager } : {}),
1114
996
  reporter: createNoopReporter(),
1115
997
  debugLogger: createNoopDebugLogger(),
998
+ preserveLegacyNativeSkills: options.preserveLegacyNativeSkills,
1116
999
  };
1117
1000
  const integration = createRebuildIntegration({ context });
1118
1001
  for (const target of options.targets) {
@@ -1125,14 +1008,11 @@ async function cleanupOfflineOwnedComponents(options) {
1125
1008
  && candidate.sourceIdentity === target.sourceIdentity
1126
1009
  && candidate.platform === target.platform
1127
1010
  && candidate.scope === target.scope);
1128
- const activationAction = target.platform === 'codex' && !options.removedPluginWhole
1011
+ const activationAction = target.platform === 'codex'
1129
1012
  ? evidence?.plan.actions.find((action) => action.action === 'removeNativeRegistration'
1130
1013
  && action.activationScope === target.scope)
1131
1014
  : undefined;
1132
- let activationDisposition = 'remove';
1133
- if (activationAction && target.scope === 'user') {
1134
- activationDisposition = 'disable';
1135
- }
1015
+ const activationDisposition = 'disable';
1136
1016
  const plugin = createSyntheticPlugin(target.pluginName, options.env);
1137
1017
  plugin.sourceId = target.sourceId;
1138
1018
  plugin.sourceIdentity = target.sourceIdentity;
@@ -1156,8 +1036,7 @@ async function cleanupOfflineOwnedComponents(options) {
1156
1036
  catalogVersion: '0.0.0',
1157
1037
  channel: 'beta',
1158
1038
  },
1159
- includeNativeCleanupCandidate: target.nativeRegistration === true,
1160
- ...(options.removedPluginWhole ? { removedPluginWhole: true } : {}),
1039
+ includeNativeCleanupCandidate: false,
1161
1040
  ...(activationAction
1162
1041
  ? {
1163
1042
  codexActivationCleanup: [{
@@ -1168,6 +1047,15 @@ async function cleanupOfflineOwnedComponents(options) {
1168
1047
  }
1169
1048
  : {}),
1170
1049
  });
1050
+ const sharedNativeComponent = (component) => component.kind === 'native-plugin'
1051
+ || (component.kind === 'shared-config'
1052
+ && component.identity.artifactKey.startsWith('marketplace-'));
1053
+ desired.requiresNative = false;
1054
+ desired.writeNativeRequired = false;
1055
+ desired.ownedComponents = desired.ownedComponents.filter((component) => !sharedNativeComponent(component));
1056
+ desired.desiredOwnedComponents = desired.desiredOwnedComponents?.filter((component) => !sharedNativeComponent(component));
1057
+ desired.optionalOwnedComponents = desired.optionalOwnedComponents?.filter((component) => !sharedNativeComponent(component));
1058
+ desired.operationActions = desired.operationActions?.filter((action) => action.kind !== 'removeNativeRegistration');
1171
1059
  const unplannedCandidates = evidence
1172
1060
  ? desired.ownedComponents.filter((component) => !cleanupComponentAuthorized(component, evidence))
1173
1061
  : [];
@@ -1183,19 +1071,17 @@ async function cleanupOfflineOwnedComponents(options) {
1183
1071
  desired.desiredOwnedComponents = desired.desiredOwnedComponents?.filter(authorized);
1184
1072
  desired.optionalOwnedComponents = desired.optionalOwnedComponents?.filter(authorized);
1185
1073
  desired.artifacts = desired.artifacts.filter((artifact) => cleanupPathAuthorized(artifact.path, evidence));
1186
- const nativeAuthorized = evidence.plan.actions.some((action) => action.action === 'removeNativeRegistration');
1187
- desired.requiresNative &&= nativeAuthorized;
1188
- desired.writeNativeRequired &&= nativeAuthorized;
1074
+ desired.requiresNative = false;
1075
+ desired.writeNativeRequired = false;
1189
1076
  desired.operationActions = desired.operationActions?.filter((action) => {
1190
1077
  if (action.kind === 'removeOwnedArtifact') {
1191
1078
  return cleanupPathAuthorized(action.path, evidence);
1192
1079
  }
1193
- if (action.kind === 'removeNativeRegistration') {
1194
- return evidence.plan.actions.some((planned) => planned.action === 'removeNativeRegistration'
1195
- && planned.target === action.registration);
1080
+ if (action.kind === 'setActivation') {
1081
+ return Boolean(activationAction
1082
+ && action.scope === target.scope
1083
+ && action.registration === activationAction.target);
1196
1084
  }
1197
- if (action.kind === 'setActivation')
1198
- return nativeAuthorized;
1199
1085
  if (action.kind === 'retainManagedCommandPackage')
1200
1086
  return true;
1201
1087
  return false;
@@ -1296,45 +1182,42 @@ async function previewOfflineOwnedComponents(options) {
1296
1182
  await options.onPreviewStage?.('dependencyPlan');
1297
1183
  return preview;
1298
1184
  }
1299
- function createOfflineCleanupOperationPlan(evidence, intent, warnings = [], retainedPackages = []) {
1300
- const beforeDigest = intent ? calculatePluginIntentDigest(intent.before) : undefined;
1301
- const afterDigest = intent ? calculatePluginIntentDigest(intent.after) : undefined;
1302
- const removesIntent = (target) => intent !== undefined
1303
- && intent.before.roots.some((root) => root.pluginId === target.pluginId && root.platforms.includes(target.platform))
1304
- && !intent.after.roots.some((root) => root.pluginId === target.pluginId && root.platforms.includes(target.platform));
1185
+ function createOfflineCleanupOperationPlan(evidence, requirements, warnings = [], retainedPackages = []) {
1305
1186
  return createOperationPlan({
1306
- intentChanges: intent && beforeDigest !== afterDigest
1307
- ? [{ scope: intent.scope, beforeDigest: beforeDigest, afterDigest: afterDigest }]
1308
- : [],
1309
- actions: [...retainedPackages, ...evidence.flatMap((target) => target.plan.actions.map((action) => action.action === 'removeNativeRegistration'
1310
- ? {
1311
- kind: 'removeNativeRegistration',
1312
- pluginId: target.pluginId,
1313
- platform: target.platform,
1314
- scope: target.scope,
1315
- registration: action.target,
1316
- }
1317
- : {
1318
- kind: 'removeOwnedArtifact',
1319
- pluginId: target.pluginId,
1320
- platform: target.platform,
1321
- scope: target.scope,
1322
- path: action.target,
1323
- ownerFingerprint: target.targetFingerprint,
1324
- }))],
1325
- changes: [
1326
- ...evidence.flatMap((target) => removesIntent(target)
1327
- ? [`${target.pluginId}:${target.platform}:${target.scope}:removeIntent`]
1328
- : []),
1329
- ...evidence.flatMap((target) => target.plan.actions.map((action) => `${target.pluginId}:${target.platform}:${target.scope}:${action.action}:${action.target}`)),
1330
- ],
1187
+ intentChanges: [],
1188
+ actions: [...retainedPackages, ...evidence.flatMap((target) => target.plan.actions.flatMap((action) => action.action === 'removeOwnedOutput'
1189
+ ? [{
1190
+ kind: 'removeOwnedArtifact',
1191
+ pluginId: target.pluginId,
1192
+ platform: target.platform,
1193
+ scope: target.scope,
1194
+ path: action.kind === 'namespacedOwner' && path.basename(action.target) === SKILL_OWNER_MARKER
1195
+ ? path.dirname(action.target) : action.target,
1196
+ ownerFingerprint: target.targetFingerprint,
1197
+ }]
1198
+ : action.activationScope === target.scope
1199
+ ? [{
1200
+ kind: 'setActivation',
1201
+ pluginId: target.pluginId,
1202
+ platform: 'codex',
1203
+ scope: target.scope,
1204
+ enabled: false,
1205
+ }]
1206
+ : []))],
1207
+ changes: evidence.flatMap((target) => target.plan.actions.flatMap((action) => action.action === 'removeOwnedOutput'
1208
+ ? [`${target.pluginId}:${target.platform}:${target.scope}:${action.action}:${action.target}`]
1209
+ : action.activationScope === target.scope
1210
+ ? [`${target.pluginId}:${target.platform}:${target.scope}:setActivation:false:${action.target}`]
1211
+ : [])),
1331
1212
  warnings: [...warnings],
1332
1213
  evidence: {
1333
1214
  catalogDigests: evidence.map((target) => ({
1334
1215
  sourceId: target.sourceId,
1335
1216
  digest: target.sourceIdentity,
1336
1217
  })),
1337
- intentDigests: intent ? [{ scope: intent.scope, digest: beforeDigest }] : [],
1218
+ intentDigests: requirements
1219
+ ? [{ scope: requirements.scope, digest: nativeDemandDigest(requirements.view) }]
1220
+ : [],
1338
1221
  configDigests: [],
1339
1222
  targetOwnerFingerprints: evidence.map((target) => ({
1340
1223
  pluginId: target.pluginId,
@@ -1596,24 +1479,14 @@ async function removedProjectionTreeFingerprint(root) {
1596
1479
  };
1597
1480
  return removedCleanupDigest(await visit(root));
1598
1481
  }
1599
- function removeRemovedPluginIntent(state, pluginIds) {
1600
- return canonicalizePluginIntentState({
1601
- ...state,
1602
- roots: state.roots.filter((root) => !pluginIds.has(root.pluginId)),
1603
- dependencyScopeOverrides: state.dependencyScopeOverrides.filter((override) => !pluginIds.has(override.pluginId)),
1604
- });
1605
- }
1606
- async function inspectRemovedPluginConfig(options) {
1482
+ export async function inspectRemovedPluginConfig(options) {
1607
1483
  const inspection = await inspectPluginConfigResetTarget(options);
1608
1484
  if (inspection.kind !== 'regularFile')
1609
1485
  return inspection;
1610
1486
  try {
1611
1487
  const identity = await readNamespacedPluginConfigStoreIdentity(options);
1612
- if (identity && (options.scope === 'user'
1613
- ? identity.binding.kind !== 'source'
1614
- || identity.binding.sourceIdentity !== options.sourceIdentity
1615
- : identity.binding.kind !== 'catalog'
1616
- || identity.binding.logicalIdentity !== options.logicalIdentity)) {
1488
+ if (identity && (identity.binding.kind !== 'source'
1489
+ || identity.binding.sourceIdentity !== options.sourceIdentity)) {
1617
1490
  return { ...inspection, conflict: 'removedPluginConfigIdentityConflict: saved config binding differs from the Active source.' };
1618
1491
  }
1619
1492
  }
@@ -1638,7 +1511,7 @@ async function inspectRemovedPluginConfig(options) {
1638
1511
  }
1639
1512
  return inspection;
1640
1513
  }
1641
- async function inspectRemovedPluginCleanup(options) {
1514
+ export async function inspectRemovedPluginCleanup(options) {
1642
1515
  const parsed = parsePluginId(options.pluginId);
1643
1516
  if (!parsed.sourceId)
1644
1517
  return null;
@@ -1705,7 +1578,7 @@ async function inspectRemovedPluginCleanup(options) {
1705
1578
  const activationScopes = [];
1706
1579
  if (marketplaceName) {
1707
1580
  const inspectedPaths = new Set();
1708
- for (const scope of ['user', 'project']) {
1581
+ for (const scope of [options.scope]) {
1709
1582
  const configPath = resolveCodexPaths(options.cwd, scope, options.env).configTomlPath;
1710
1583
  if (inspectedPaths.has(path.resolve(configPath)))
1711
1584
  continue;
@@ -1737,7 +1610,7 @@ async function inspectRemovedPluginCleanup(options) {
1737
1610
  env: options.env,
1738
1611
  nativeCliRunner: options.nativeCliRunner,
1739
1612
  manager: options.codexManager,
1740
- scope: 'both',
1613
+ scope: options.scope,
1741
1614
  });
1742
1615
  nativeState = observed.observation.state === 'observed'
1743
1616
  ? observed.observation.value === 'installed' ? 'installed' : 'absent'
@@ -1758,7 +1631,7 @@ async function inspectRemovedPluginCleanup(options) {
1758
1631
  blockers.push('removedPluginNativeOwnershipUnavailable: native evidence remains without a valid AW marketplace root.');
1759
1632
  }
1760
1633
  const configTargets = [];
1761
- for (const scope of ['user', 'project']) {
1634
+ for (const scope of [options.scope]) {
1762
1635
  try {
1763
1636
  const inspected = await inspectRemovedPluginConfig({
1764
1637
  sourceId: parsed.sourceId,
@@ -1817,7 +1690,7 @@ async function inspectRemovedPluginCleanup(options) {
1817
1690
  });
1818
1691
  const evidence = [];
1819
1692
  for (const platform of ['codex', 'claude']) {
1820
- for (const scope of ['user', 'project']) {
1693
+ for (const scope of [options.scope]) {
1821
1694
  const base = offlineCleanupEvidenceFromInventory({
1822
1695
  reason: 'bundleUnavailable',
1823
1696
  pluginId,
@@ -1835,27 +1708,11 @@ async function inspectRemovedPluginCleanup(options) {
1835
1708
  platform,
1836
1709
  scope,
1837
1710
  });
1838
- const actions = base.plan.actions.flatMap((action) => {
1839
- if (platform === 'codex' && action.action === 'removeNativeRegistration')
1840
- return [];
1841
- if (platform === 'claude' && action.action === 'removeNativeRegistration') {
1842
- return root && projectionOwnershipVerified && !claudeNativeCleanupBlocked
1843
- && registration?.claude === 'canonical'
1844
- && marketplaceName && action.kind === 'namespacedMarketplace'
1845
- ? [{ ...action, target: `${parsed.pluginName}@${marketplaceName}` }]
1846
- : [];
1847
- }
1848
- return [action];
1849
- });
1850
- if (platform === 'codex' && scope === 'user' && nativeState === 'installed'
1851
- && marketplaceName && nativeOwnershipVerified && projectionPathSafe) {
1852
- actions.push({
1853
- action: 'removeNativeRegistration',
1854
- kind: 'namespacedMarketplace',
1855
- target: `${parsed.pluginName}@${marketplaceName}`,
1856
- marketplaceName,
1857
- });
1858
- }
1711
+ const actions = base.plan.actions.filter((action) => action.action !== 'removeNativeRegistration'
1712
+ || (platform === 'codex'
1713
+ && nativeOwnershipVerified
1714
+ && projectionPathSafe
1715
+ && action.activationScope === scope));
1859
1716
  const safeActions = ownershipConflict ? [] : actions;
1860
1717
  const enrichedActions = await Promise.all(safeActions.map(async (action) => action.action === 'removeOwnedOutput'
1861
1718
  ? { ...action, pathEvidence: await removedProjectionTreeFingerprint(action.target.split('#', 1)[0]) }
@@ -1887,441 +1744,131 @@ async function inspectRemovedPluginCleanup(options) {
1887
1744
  blockers,
1888
1745
  };
1889
1746
  }
1890
- function createRemovedPluginCleanupOperationPlan(options) {
1891
- const actions = [];
1892
- const changes = [];
1893
- for (const target of options.targets) {
1894
- for (const scope of ['user', 'project']) {
1895
- const before = options.intents[scope].before;
1896
- const after = options.intents[scope].after;
1897
- if (calculatePluginIntentDigest(before) !== calculatePluginIntentDigest(after)
1898
- && (before.roots.some((root) => root.pluginId === target.pluginId)
1899
- || before.dependencyScopeOverrides.some((override) => override.pluginId === target.pluginId))) {
1900
- changes.push(`${target.pluginId}:${scope}:removeIntentAndOverrides`);
1901
- }
1902
- }
1903
- for (const evidence of target.evidence) {
1904
- for (const action of evidence.plan.actions) {
1905
- actions.push(action.action === 'removeNativeRegistration'
1906
- ? {
1907
- kind: 'removeNativeRegistration',
1908
- pluginId: target.pluginId,
1909
- platform: evidence.platform,
1910
- scope: evidence.scope,
1911
- registration: action.target,
1912
- }
1913
- : {
1914
- kind: 'removeOwnedArtifact',
1915
- pluginId: target.pluginId,
1916
- platform: evidence.platform,
1917
- scope: evidence.scope,
1918
- path: action.target,
1919
- ownerFingerprint: evidence.targetFingerprint,
1920
- });
1921
- changes.push(`${target.pluginId}:${evidence.platform}:${evidence.scope}:${action.action}:${action.target}`);
1922
- }
1923
- }
1924
- for (const config of target.configTargets.filter((entry) => entry.kind !== 'absent')) {
1925
- if (config.kind === 'directory' || config.kind === 'special'
1926
- || target.blockers.some((blocker) => blocker.startsWith(`${config.scope}: removedPluginConfigIdentityConflict`))) {
1927
- continue;
1928
- }
1929
- actions.push({
1930
- kind: 'removePluginConfig',
1931
- pluginId: target.pluginId,
1932
- platform: 'codex',
1933
- scope: config.scope,
1934
- path: resolvePluginConfigStorePath({
1935
- sourceId: target.sourceId,
1936
- pluginName: target.pluginName,
1937
- scope: config.scope,
1938
- cwd: options.cwd,
1939
- env: options.env,
1940
- }),
1941
- fingerprint: config.fingerprint,
1942
- });
1943
- changes.push(`${target.pluginId}:${config.scope}:removeSavedConfig:${config.kind}:${config.fingerprint}`);
1944
- }
1945
- for (const activation of target.activationScopes) {
1946
- changes.push(`${target.pluginId}:codex:${activation.scope}:activationEvidence:${activation.enabled}`);
1947
- if (!target.nativeOwnershipVerified || target.nativeState === 'unknown')
1948
- continue;
1949
- actions.push({
1950
- kind: 'removeActivation',
1951
- pluginId: target.pluginId,
1952
- platform: 'codex',
1953
- scope: activation.scope,
1954
- registration: `${target.pluginName}@${target.marketplaceName}`,
1955
- });
1956
- changes.push(`${target.pluginId}:codex:${activation.scope}:removeActivation`);
1957
- }
1958
- changes.push(`${target.pluginId}:projectionEvidence:${target.projectionFingerprint}`);
1959
- if (target.projectionPresent && target.projectionOwnershipVerified
1960
- && target.nativeState !== 'unknown'
1961
- && (target.nativeOwnershipVerified || target.activationScopes.length === 0)) {
1962
- actions.push({
1963
- kind: 'removeMarketplaceProjection',
1964
- pluginId: target.pluginId,
1965
- platform: 'codex',
1966
- scope: 'user',
1967
- marketplaceRoot: target.marketplaceRoot,
1968
- });
1969
- changes.push(`${target.pluginId}:removeMarketplaceProjection`);
1970
- }
1971
- changes.push(`${target.pluginId}:nativeObservation:${target.nativeState}:${target.nativeObservationFingerprint}:${target.marketplaceEvidenceFingerprint}`);
1972
- }
1973
- const warnings = options.targets.flatMap((target) => [
1974
- `removedPluginCleanupScope: ${target.pluginId} 清理范围为 Codex 和 Claude 的用户及当前项目,包含两处保存设置。`,
1975
- `removedPluginCleanupSharedNativeImpact: ${target.pluginId} 的 Codex 共享原生包卸载后,其他项目可能无法继续使用;其他项目的本地记录不会修改。`,
1976
- ...target.blockers,
1977
- ]);
1978
- return createOperationPlan({
1979
- intentChanges: ['user', 'project'].flatMap((scope) => {
1980
- const beforeDigest = calculatePluginIntentDigest(options.intents[scope].before);
1981
- const afterDigest = calculatePluginIntentDigest(options.intents[scope].after);
1982
- return beforeDigest === afterDigest ? [] : [{ scope, beforeDigest, afterDigest }];
1983
- }),
1984
- actions,
1985
- changes,
1986
- warnings,
1987
- evidence: {
1988
- catalogDigests: options.targets.map((target) => ({
1989
- sourceId: target.sourceId,
1990
- digest: removedCleanupDigest({
1991
- sourceIdentity: target.sourceIdentity,
1992
- logicalIdentity: target.logicalIdentity,
1993
- snapshotDigest: target.snapshotDigest,
1994
- }),
1995
- })),
1996
- intentDigests: ['user', 'project'].map((scope) => ({
1997
- scope,
1998
- digest: calculatePluginIntentDigest(options.intents[scope].before),
1999
- })),
2000
- configDigests: options.targets.flatMap((target) => target.configTargets.map((config) => ({
2001
- pluginId: target.pluginId,
2002
- scope: config.scope,
2003
- digest: config.fingerprint,
2004
- }))),
2005
- targetOwnerFingerprints: options.targets.flatMap((target) => target.evidence.map((evidence) => ({
2006
- pluginId: target.pluginId,
2007
- platform: evidence.platform,
2008
- scope: evidence.scope,
2009
- fingerprint: evidence.targetFingerprint,
2010
- }))),
2011
- },
2012
- });
2013
- }
2014
- function combineCleanupOperationPlans(plans) {
2015
- return createOperationPlan({
2016
- intentChanges: plans.flatMap((plan) => plan.intentChanges),
2017
- actions: plans.flatMap((plan) => plan.actions),
2018
- changes: plans.flatMap((plan) => plan.changes),
2019
- warnings: plans.flatMap((plan) => plan.warnings),
2020
- evidence: {
2021
- catalogDigests: plans.flatMap((plan) => plan.evidence.catalogDigests),
2022
- intentDigests: plans.flatMap((plan) => plan.evidence.intentDigests),
2023
- configDigests: plans.flatMap((plan) => plan.evidence.configDigests),
2024
- targetOwnerFingerprints: plans.flatMap((plan) => plan.evidence.targetOwnerFingerprints),
2025
- },
1747
+ export async function executeRemovedPluginOutputCleanup(options) {
1748
+ const selected = options.selectedTargets.filter((target) => target.pluginId === options.target.pluginId && target.scope === options.context.scope);
1749
+ const retainedPackages = await discoverRetainedManagedCommandPackages({
1750
+ selectors: [{ sourceId: options.target.sourceId, pluginName: options.target.pluginName }],
1751
+ scope: options.context.scope,
1752
+ cwd: options.context.executionCwd,
1753
+ env: options.context.env,
2026
1754
  });
2027
- }
2028
- async function unavailableRemovedCleanupWarnings(options) {
2029
- const warnings = [];
2030
- for (const pluginId of options.pluginIds) {
2031
- const parsed = parsePluginId(pluginId);
2032
- if (!parsed.sourceId)
2033
- continue;
2034
- try {
2035
- await resolveActiveCatalogGeneration({ sourceId: parsed.sourceId, env: options.env });
2036
- continue;
2037
- }
2038
- catch {
2039
- // Only a healthy Active generation can prove that a plugin entry was deleted.
2040
- }
2041
- let hasConfig = false;
2042
- for (const scope of ['user', 'project']) {
2043
- try {
2044
- hasConfig ||= (await inspectPluginConfigResetTarget({
2045
- sourceId: parsed.sourceId,
2046
- pluginName: parsed.pluginName,
2047
- scope,
2048
- cwd: options.cwd,
2049
- env: options.env,
2050
- })).exists;
2051
- }
2052
- catch {
2053
- hasConfig = true;
1755
+ const retainedActions = retainedPackages.map((entry) => ({
1756
+ ...entry, kind: 'retainManagedCommandPackage', pluginId: options.target.pluginId,
1757
+ scope: entry.producer.scope,
1758
+ }));
1759
+ const withRetainedPackages = (result, operationPlan = createOfflineCleanupOperationPlan([], undefined, [], retainedActions)) => {
1760
+ result.operationPlan = operationPlan;
1761
+ const commandResults = [...new Map([
1762
+ ...retainedPackages.map(retainedManagedCommandPackageResult), ...(result.commandResults ?? []),
1763
+ ].map((entry) => [JSON.stringify(entry.producer), entry])).values()];
1764
+ if (commandResults.length > 0) {
1765
+ result.commandResults = commandResults;
1766
+ if (result.overallState === 'succeeded' && commandResults.some((entry) => entry.state !== 'completed')) {
1767
+ result.overallState = 'partial';
2054
1768
  }
2055
1769
  }
2056
- if (!hasConfig && !options.inventory.references.some((reference) => reference.sourceId === parsed.sourceId && reference.pluginName === parsed.pluginName)) {
2057
- continue;
2058
- }
2059
- warnings.push(`removedPluginCleanupNotPerformed: ${pluginId} has no healthy local Active source, so whole-plugin cleanup was not performed and saved settings may remain. Restore the source, run aw catalog source sync ${parsed.sourceId}, then retry ordinary cleanup.`);
2060
- }
2061
- return warnings;
2062
- }
2063
- async function executeRemovedPluginCleanup(options) {
2064
- const results = [];
2065
- const warnings = [...options.operationPlan.warnings];
2066
- const targetIdentity = (target, scope = 'user') => ({
2067
- pluginId: target.pluginId,
2068
- pluginName: target.pluginName,
2069
- sourceId: target.sourceId,
2070
- sourceIdentity: target.sourceIdentity,
2071
- pluginVersion: '0.0.0',
2072
- platform: 'codex',
2073
- scope,
2074
- cwd: options.cwd,
1770
+ return result;
1771
+ };
1772
+ if (selected.length === 0)
1773
+ return withRetainedPackages(mergeRebuildCommandResults([]));
1774
+ const inventoryReader = options.runtime?.inventoryCatalogSourceReferences
1775
+ ?? inventoryCatalogSourceReferences;
1776
+ const inventory = await inventoryReader({
1777
+ cwd: options.context.executionCwd,
1778
+ env: options.context.env,
1779
+ scope: options.context.scope,
1780
+ includeProjectAgentSkills: true,
2075
1781
  });
2076
- let intentChanged = false;
2077
- for (const scope of ['user', 'project']) {
2078
- if (calculatePluginIntentDigest(options.intents[scope].before)
2079
- === calculatePluginIntentDigest(options.intents[scope].after))
2080
- continue;
2081
- try {
2082
- await (options.runtime?.writePluginIntentState ?? writePluginIntentState)({
2083
- scope, cwd: options.cwd, env: options.env,
2084
- }, options.intents[scope].after);
2085
- intentChanged = true;
2086
- for (const target of options.targets) {
2087
- if (!options.intents[scope].before.roots.some((root) => root.pluginId === target.pluginId)
2088
- && !options.intents[scope].before.dependencyScopeOverrides.some((override) => override.pluginId === target.pluginId))
2089
- continue;
2090
- const result = cleanupIntentResult({
2091
- pluginId: target.pluginId,
2092
- pluginName: target.pluginName,
2093
- sourceId: target.sourceId,
2094
- sourceIdentity: target.sourceIdentity,
2095
- platform: 'codex',
2096
- scope,
2097
- });
2098
- result.removed.push(`pluginIntent:${scope}`);
2099
- results.push(result);
2100
- }
2101
- }
2102
- catch (error) {
2103
- const message = `removedPluginIntentWriteFailed: ${scope}: ${error instanceof Error ? error.message : String(error)}. Retry cleanup.`;
2104
- results.push(...options.targets.map((target) => failedResult('cleanup', targetIdentity(target, scope), message)));
2105
- return {
2106
- platforms: ['codex', 'claude'],
2107
- plugins: [],
2108
- warnings: [...warnings, message],
2109
- rebuildResults: results,
2110
- targetResults: results.map(toRemovedCleanupTargetResult),
2111
- overallState: intentChanged ? 'partial' : 'failed',
2112
- receiptTargets: results.map((result) => result.target),
2113
- removedPluginCleanupIds: options.targets.map((target) => target.pluginId),
2114
- operationPlan: options.operationPlan,
2115
- };
2116
- }
2117
- }
2118
- let codexManager = options.runtime?.codexManager;
2119
- if (!codexManager && options.targets.some((target) => target.nativeState === 'installed')) {
2120
- try {
2121
- if (options.runtime?.codexManagerResolver) {
2122
- codexManager = await options.runtime.codexManagerResolver({
2123
- cwd: options.cwd,
2124
- env: options.env,
2125
- runner: options.runtime.nativeCliRunner ?? getDefaultNativeCliRunner(),
2126
- authorizer: options.runtime.managedCodexAuthorizer,
2127
- operationRequirement: getCodexOperationRequirement('project'),
2128
- });
2129
- }
2130
- else {
2131
- const consumer = await (options.runtime?.codexOperationConsumerResolver
2132
- ?? resolveCodexOperationConsumer)({
2133
- cwd: options.cwd,
2134
- env: options.env,
2135
- runner: options.runtime?.nativeCliRunner ?? getDefaultNativeCliRunner(),
2136
- authorizer: options.runtime?.managedCodexAuthorizer,
2137
- operationRequirement: getCodexOperationRequirement('project'),
2138
- requiresNativeMaterialization: true,
2139
- });
2140
- if (consumer.support !== 'observed') {
2141
- throw new Error('Codex native removal requires an observed official CLI consumer.');
2142
- }
2143
- codexManager = consumer.manager;
2144
- }
2145
- }
2146
- catch (error) {
2147
- warnings.push(`removedPluginCodexManagerUnavailable: ${error instanceof Error ? error.message : String(error)}`);
2148
- }
1782
+ const current = await inspectRemovedPluginCleanup({
1783
+ pluginId: options.target.pluginId,
1784
+ scope: options.context.scope,
1785
+ inventory,
1786
+ cwd: options.context.executionCwd,
1787
+ env: options.context.env,
1788
+ nativeCliRunner: options.runtime?.nativeCliRunner ?? getDefaultNativeCliRunner(),
1789
+ codexManager: options.runtime?.codexManager,
1790
+ });
1791
+ if (!current || current.sourceIdentity !== options.target.sourceIdentity
1792
+ || current.projectionFingerprint !== options.target.projectionFingerprint
1793
+ || current.evidence.some((entry) => {
1794
+ const expected = options.target.evidence.find((candidate) => candidate.platform === entry.platform && candidate.scope === entry.scope);
1795
+ return !expected || !offlineCleanupEvidenceMatches(expected, entry);
1796
+ })) {
1797
+ throw new Error('operationReviewStale: removed plugin ownership or output changed before cleanup.');
2149
1798
  }
2150
- const ownedTargets = options.targets.flatMap((target) => target.evidence
2151
- .filter((entry) => entry.plan.actions.length > 0)
2152
- .map((entry) => ({
2153
- pluginId: target.pluginId,
2154
- pluginName: target.pluginName,
2155
- sourceId: target.sourceId,
2156
- sourceIdentity: target.sourceIdentity,
1799
+ const evidence = current.evidence.filter((entry) => selected.some((target) => target.platform === entry.platform && target.scope === entry.scope));
1800
+ const targets = evidence.map((entry) => ({
1801
+ pluginId: current.pluginId,
1802
+ pluginName: current.pluginName,
1803
+ sourceId: current.sourceId,
1804
+ sourceIdentity: current.sourceIdentity,
2157
1805
  platform: entry.platform,
2158
1806
  scope: entry.scope,
2159
- ...(target.marketplaceName ? { marketplaceName: target.marketplaceName } : {}),
2160
- nativeRegistration: entry.plan.actions.some((action) => action.action === 'removeNativeRegistration'),
2161
- })));
2162
- if (ownedTargets.length > 0) {
2163
- const owned = await cleanupOfflineOwnedComponents({
2164
- targets: ownedTargets,
2165
- evidence: options.targets.flatMap((target) => target.evidence),
2166
- inventoryErrors: options.inventory.errors.filter((error) => !error.kind.startsWith('config')),
2167
- cwd: options.cwd,
2168
- env: options.env,
1807
+ ...(current.marketplaceName ? { marketplaceName: current.marketplaceName } : {}),
1808
+ nativeRegistration: entry.platform === 'codex',
1809
+ }));
1810
+ const operationPlan = await withNativeToolRemovalImpact(createOfflineCleanupOperationPlan(evidence, undefined, [], retainedActions), options.context.env);
1811
+ if (options.runtime?.expectedPlanFingerprint) {
1812
+ assertOperationPlanFingerprint(options.runtime.expectedPlanFingerprint, operationPlan);
1813
+ }
1814
+ const result = options.runtime?.previewOperationPlan
1815
+ ? await previewOfflineOwnedComponents({
1816
+ targets,
1817
+ evidence,
1818
+ cwd: options.context.executionCwd,
1819
+ env: options.context.env,
1820
+ nativeCliRunner: options.runtime?.nativeCliRunner,
1821
+ codexManagerPreviewer: options.runtime?.codexManagerPreviewer,
1822
+ onPreviewStage: options.runtime?.onPreviewStage,
1823
+ })
1824
+ : await cleanupOfflineOwnedComponents({
1825
+ targets,
1826
+ evidence,
1827
+ inventoryErrors: inventory.errors.filter((error) => !error.kind.startsWith('config')),
1828
+ cwd: options.context.executionCwd,
1829
+ env: options.context.env,
2169
1830
  nativeCliRunner: options.runtime?.nativeCliRunner,
2170
- ...(codexManager ? { codexManager } : {}),
1831
+ codexManager: options.runtime?.codexManager,
2171
1832
  executeCleanupOperation: options.runtime?.executeCleanupOperation,
2172
- removeObsoleteInstallationContracts: async () => undefined,
2173
- removedPluginWhole: true,
2174
- });
2175
- results.push(...owned.rebuildResults);
2176
- warnings.push(...owned.warnings);
2177
- }
2178
- for (const target of options.targets) {
2179
- for (const config of target.configTargets.filter((entry) => entry.kind !== 'absent')) {
2180
- if (config.kind === 'directory' || config.kind === 'special'
2181
- || target.blockers.some((blocker) => blocker.startsWith(`${config.scope}: removedPluginConfigIdentityConflict`))) {
2182
- continue;
2183
- }
2184
- try {
2185
- const receipt = await resetPluginConfigStore({
2186
- sourceId: target.sourceId,
2187
- sourceIdentity: target.sourceIdentity,
2188
- pluginName: target.pluginName,
2189
- scope: config.scope,
2190
- cwd: options.cwd,
2191
- env: options.env,
2192
- });
2193
- const result = cleanupIntentResult({
2194
- pluginId: target.pluginId,
2195
- pluginName: target.pluginName,
2196
- sourceId: target.sourceId,
2197
- sourceIdentity: target.sourceIdentity,
2198
- platform: 'codex',
2199
- scope: config.scope,
2200
- });
2201
- if (receipt.deleted)
2202
- result.removed.push(`${config.scope}/plugin-config/${target.sourceId}/${target.pluginName}`);
2203
- results.push(result);
2204
- }
2205
- catch (error) {
2206
- results.push(failedResult('cleanup', targetIdentity(target, config.scope), `removedPluginConfigResetFailed: ${error instanceof Error ? error.message : String(error)}. Retry cleanup.`, 'cleanup.owned'));
2207
- }
2208
- }
2209
- let nativeAbsent = target.nativeState === 'absent';
2210
- if (target.marketplaceName && target.nativeState !== 'unbound') {
2211
- try {
2212
- const observed = await inspectCodexPluginRegistrationLive({
2213
- pluginId: `${target.pluginName}@${target.marketplaceName}`,
2214
- cwd: options.cwd,
2215
- env: options.env,
2216
- nativeCliRunner: options.runtime?.nativeCliRunner ?? getDefaultNativeCliRunner(),
2217
- ...(codexManager ? { manager: codexManager } : {}),
2218
- scope: 'both',
2219
- });
2220
- nativeAbsent = observed.observation.state === 'observed'
2221
- && observed.observation.value === 'absent';
2222
- }
2223
- catch {
2224
- nativeAbsent = false;
2225
- }
2226
- }
2227
- if (!nativeAbsent && (target.nativeState === 'installed' || target.nativeState === 'unknown')) {
2228
- results.push(failedResult('cleanup', targetIdentity(target), 'removedPluginNativeResidual: official Codex list did not confirm absent. Retry ordinary cleanup.', 'cleanup.owned'));
2229
- }
2230
- if (nativeAbsent && target.marketplaceName && target.nativeOwnershipVerified) {
2231
- for (const activation of target.activationScopes) {
2232
- try {
2233
- await removeCodexPluginActivation({
2234
- configPath: resolveCodexPaths(options.cwd, activation.scope, options.env).configTomlPath,
2235
- codexPluginId: `${target.pluginName}@${target.marketplaceName}`,
2236
- });
2237
- const result = cleanupIntentResult({
2238
- pluginId: target.pluginId,
2239
- pluginName: target.pluginName,
2240
- sourceId: target.sourceId,
2241
- sourceIdentity: target.sourceIdentity,
2242
- platform: 'codex',
2243
- scope: activation.scope,
2244
- });
2245
- result.removed.push(`codexActivation:${activation.scope}`);
2246
- results.push(result);
2247
- }
2248
- catch (error) {
2249
- results.push(failedResult('cleanup', targetIdentity(target, activation.scope), `removedPluginActivationFailed: ${error instanceof Error ? error.message : String(error)}`, 'cleanup.owned'));
2250
- }
2251
- }
2252
- }
2253
- const claudeNativeFailed = results.some((result) => result.target.pluginId === target.pluginId
2254
- && result.target.platform === 'claude' && result.status === 'failed');
2255
- if (target.projectionPresent && target.projectionOwnershipVerified
2256
- && nativeAbsent && !claudeNativeFailed && !target.claudeNativeCleanupBlocked
2257
- && (target.nativeOwnershipVerified || target.activationScopes.length === 0)) {
2258
- try {
2259
- await removeNamespacedMarketplacePluginProjection({
2260
- rootDir: target.marketplaceRoot,
2261
- sourceId: target.sourceId,
2262
- sourceIdentity: target.sourceIdentity,
2263
- pluginName: target.pluginName,
2264
- env: options.env,
2265
- });
2266
- const result = cleanupIntentResult({
2267
- pluginId: target.pluginId,
2268
- pluginName: target.pluginName,
2269
- sourceId: target.sourceId,
2270
- sourceIdentity: target.sourceIdentity,
2271
- platform: 'codex',
2272
- scope: 'user',
2273
- });
2274
- result.removed.push('marketplaceProjection');
2275
- results.push(result);
2276
- }
2277
- catch (error) {
2278
- results.push(failedResult('cleanup', targetIdentity(target), `removedPluginMarketplaceProjectionFailed: ${error instanceof Error ? error.message : String(error)}`, 'cleanup.owned'));
2279
- }
2280
- }
2281
- for (const blocker of target.blockers) {
2282
- results.push(failedResult('cleanup', targetIdentity(target), blocker));
2283
- }
2284
- }
2285
- const afterInventory = await options.readInventory({ cwd: options.cwd, env: options.env });
2286
- for (const target of options.targets) {
2287
- const after = await inspectRemovedPluginCleanup({
2288
- pluginId: target.pluginId,
2289
- inventory: afterInventory,
2290
- cwd: options.cwd,
2291
- env: options.env,
2292
- nativeCliRunner: options.runtime?.nativeCliRunner ?? getDefaultNativeCliRunner(),
2293
- ...(codexManager ? { codexManager } : {}),
1833
+ preserveLegacyNativeSkills: options.runtime?.preserveLegacyNativeSkills,
1834
+ removeObsoleteInstallationContracts: options.runtime?.removeObsoleteInstallationContracts,
2294
1835
  });
2295
- if (after && (after.configTargets.some((config) => config.kind !== 'absent')
2296
- || after.activationScopes.length > 0
2297
- || after.projectionPresent
2298
- || after.nativeState === 'installed'
2299
- || after.nativeState === 'unknown'
2300
- || after.evidence.some((entry) => entry.plan.actions.length > 0)
2301
- || after.blockers.length > 0)) {
2302
- results.push(failedResult('cleanup', targetIdentity(target), 'removedPluginCleanupResidual: some exact targets remain. Retry ordinary cleanup or repair the reported evidence.', 'cleanup.owned'));
2303
- }
1836
+ withRetainedPackages(result, operationPlan);
1837
+ const residual = [
1838
+ ...current.blockers.filter((blocker) => !blocker.includes('Config')),
1839
+ ...(current.nativeState === 'installed' || current.nativeState === 'unknown'
1840
+ ? ['sharedNativePreserved: Codex package remains available to other scopes.'] : []),
1841
+ ...(current.projectionPresent ? ['sharedMarketplaceProjectionPreserved'] : []),
1842
+ ];
1843
+ if (residual.length > 0) {
1844
+ result.warnings.push(...residual);
1845
+ if (result.overallState === 'succeeded')
1846
+ result.overallState = 'partial';
2304
1847
  }
2305
- const hasFailure = results.some((result) => result.status === 'failed');
2306
- const hasSuccess = intentChanged || results.some((result) => result.status === 'succeeded'
2307
- && result.removed.length > 0);
2308
- return {
2309
- platforms: ['codex', 'claude'],
2310
- plugins: hasFailure ? [] : options.targets.map((target) => target.pluginName),
2311
- warnings: [...new Set(warnings)],
2312
- rebuildResults: results,
2313
- targetResults: results.map(toRemovedCleanupTargetResult),
2314
- overallState: hasFailure ? hasSuccess ? 'partial' : 'failed' : 'succeeded',
2315
- receiptTargets: results.map((result) => result.target),
2316
- removedPluginCleanupIds: options.targets.map((target) => target.pluginId),
2317
- operationPlan: options.operationPlan,
2318
- };
1848
+ return result;
2319
1849
  }
2320
1850
  async function runRebuildCommandInSession(options) {
2321
- const runtimeEnv = options.runtime?.env ?? process.env;
1851
+ const executionInput = options.runtime?.executionInput;
1852
+ if (!executionInput || executionInput.context.scope !== options.commandOptions.scope
1853
+ || executionInput.preserveOtherObservedTargets !== true) {
1854
+ throw new Error('nativeExecutionInputRequired: Native execution requires an explicit matching scope context and complete demand.');
1855
+ }
1856
+ if (executionInput.selectedTargets.length === 0)
1857
+ return mergeRebuildCommandResults([]);
1858
+ if (executionInput.selectedTargets.some((target) => target.scope !== executionInput.context.scope)) {
1859
+ throw new Error('nativeExecutionTargetScopeInvalid: Selected targets must use the execution context scope.');
1860
+ }
1861
+ const requestedPlatforms = selectedPlatforms(options.commandOptions.platform);
1862
+ if (executionInput.selectedTargets.some((target) => !requestedPlatforms.includes(target.platform))) {
1863
+ throw new Error('nativeExecutionTargetPlatformInvalid: Selected targets must use a requested platform.');
1864
+ }
1865
+ const selectedExecutionTarget = (target) => executionInput.selectedTargets.some((selected) => selected.pluginId === target.pluginId && selected.platform === target.platform && selected.scope === target.scope);
1866
+ const runtimeEnv = executionInput.context.env;
2322
1867
  const retainSelectors = options.command === 'cleanup' ? options.pluginNames.map(parsePluginId) : [];
2323
1868
  const primarySourceId = retainSelectors.some((selector) => !selector.sourceId)
2324
- ? (await readCatalogSourcesConfigSnapshot(runtimeEnv)).config.sources.find((source) => source.primary)?.id
1869
+ ? await readCatalogSourcesConfigSnapshot(runtimeEnv)
1870
+ .then((snapshot) => snapshot.config.sources.find((source) => source.primary)?.id)
1871
+ .catch(() => undefined)
2325
1872
  : undefined;
2326
1873
  const retainedPackages = options.command === 'cleanup'
2327
1874
  ? await discoverRetainedManagedCommandPackages({ selectors: retainSelectors.map((selector) => ({ ...selector, sourceId: selector.sourceId ?? primarySourceId })), all: options.all, scope: options.commandOptions.scope, cwd: options.commandOptions.cwd, env: runtimeEnv })
@@ -2335,6 +1882,7 @@ async function runRebuildCommandInSession(options) {
2335
1882
  const inventory = await readInventory({
2336
1883
  cwd: options.commandOptions.cwd,
2337
1884
  env: runtimeEnv,
1885
+ scope: options.commandOptions.scope,
2338
1886
  });
2339
1887
  if (options.command !== 'cleanup'
2340
1888
  || options.commandOptions.scope !== 'project'
@@ -2369,13 +1917,7 @@ async function runRebuildCommandInSession(options) {
2369
1917
  let requestedPluginNames = options.pluginNames;
2370
1918
  const cleanupForce = options.command === 'cleanup'
2371
1919
  && options.commandOptions.force === true;
2372
- const cleanupIntentBefore = options.command === 'cleanup'
2373
- ? await readPluginIntentState({
2374
- scope: options.commandOptions.scope,
2375
- cwd: options.commandOptions.cwd,
2376
- env: runtimeEnv,
2377
- })
2378
- : undefined;
1920
+ const cleanupRequirements = options.command === 'cleanup' ? executionInput.requirements : undefined;
2379
1921
  const expectedOfflineCleanup = options.runtime?.expectedOfflineCleanupEvidence;
2380
1922
  let cleanupInventory;
2381
1923
  let cleanupLiveTargets = [];
@@ -2391,7 +1933,7 @@ async function runRebuildCommandInSession(options) {
2391
1933
  references: cleanupInventory.references,
2392
1934
  });
2393
1935
  requestedPluginNames = live.selectors;
2394
- cleanupLiveTargets = live.targets;
1936
+ cleanupLiveTargets = live.targets.filter(selectedExecutionTarget);
2395
1937
  if (expectedOfflineCleanup
2396
1938
  && expectedOfflineCleanup.forceExclusive !== true
2397
1939
  && expectedOfflineCleanup.plan.actions.length > 0) {
@@ -2456,7 +1998,7 @@ async function runRebuildCommandInSession(options) {
2456
1998
  liveTargets = projectedTargets;
2457
1999
  if (options.command === 'cleanup') {
2458
2000
  cleanupInventory = inventory;
2459
- cleanupLiveTargets = projectedTargets;
2001
+ cleanupLiveTargets = projectedTargets.filter(selectedExecutionTarget);
2460
2002
  }
2461
2003
  const identities = new Set();
2462
2004
  for (const reference of live) {
@@ -2464,14 +2006,13 @@ async function runRebuildCommandInSession(options) {
2464
2006
  identities.add(pluginId);
2465
2007
  }
2466
2008
  if (options.command === 'cleanup') {
2467
- for (const root of cleanupIntentBefore.roots)
2009
+ for (const root of cleanupRequirements.roots)
2468
2010
  identities.add(root.pluginId);
2469
2011
  }
2470
2012
  requestedPluginNames = [...identities];
2471
2013
  }
2472
2014
  let cleanupTargets = [];
2473
2015
  let cleanupUnavailableWarnings = [];
2474
- let cleanupIntentAfter;
2475
2016
  let cleanupSelectionErrors = [];
2476
2017
  if (options.command === 'cleanup') {
2477
2018
  const primarySourceId = cleanupForce
@@ -2480,7 +2021,7 @@ async function runRebuildCommandInSession(options) {
2480
2021
  .catch(() => undefined)
2481
2022
  : undefined;
2482
2023
  const intentTargets = cleanupIntentTargets({
2483
- state: cleanupIntentBefore,
2024
+ state: cleanupRequirements,
2484
2025
  selectors: options.pluginNames,
2485
2026
  platforms: selectedPlatforms(options.commandOptions.platform),
2486
2027
  scope: options.commandOptions.scope,
@@ -2507,17 +2048,11 @@ async function runRebuildCommandInSession(options) {
2507
2048
  cleanupTargets = [...new Map([...damagedTargets, ...intentTargets, ...cleanupLiveTargets].map((target) => [
2508
2049
  `${target.pluginId}\0${target.platform}\0${target.scope}`,
2509
2050
  target,
2510
- ])).values()];
2051
+ ])).values()].filter(selectedExecutionTarget);
2511
2052
  requestedPluginNames = [...new Set([
2512
2053
  ...requestedPluginNames,
2513
2054
  ...cleanupTargets.map((target) => target.pluginId),
2514
2055
  ])];
2515
- cleanupIntentAfter = deriveDesiredIntentState({
2516
- command: 'cleanup',
2517
- state: cleanupIntentBefore,
2518
- selectedRoots: cleanupSelectedRoots(cleanupTargets),
2519
- plan: { requestedPluginIds: [], resolvedPluginIds: [], dependencyEdges: [], targets: [], scopeOverrides: [] },
2520
- });
2521
2056
  const filtered = omitSelectedPluginConfigErrors(cleanupInventory, cleanupTargets);
2522
2057
  cleanupInventory = filtered.inventory;
2523
2058
  malformedSelectedConfigPreserved ||= filtered.omitted;
@@ -2532,171 +2067,18 @@ async function runRebuildCommandInSession(options) {
2532
2067
  const selectors = requestedPluginNames.map(parsePluginId);
2533
2068
  const sourceIds = planPluginSelectorSourceIds(selectors);
2534
2069
  if (options.all && selectors.length === 0 && options.command !== 'cleanup') {
2535
- if (legacyLiveTargets.length > 0) {
2536
- throw namespacedPreflightError(`legacyStateUnsupported: legacy live runtime state exists for ${legacyLiveTargets.map((target) => `${target.pluginName}:${target.platform}/${target.scope}`).join(', ')}.`, [], legacyLiveTargets);
2537
- }
2538
- return mergeRebuildCommandResults([]);
2539
- }
2540
- if (options.command === 'cleanup') {
2541
- if (!cleanupForce) {
2542
- const exactIds = [...new Set(requestedPluginNames.flatMap((selector) => {
2543
- const parsed = parsePluginId(selector);
2544
- return parsed.sourceId ? [formatPluginId(parsed)] : [];
2545
- }))];
2546
- const projectSkills = await selectedProjectCodexSkillReferences({
2547
- cwd: options.commandOptions.cwd,
2548
- });
2549
- const fullInventory = {
2550
- ...cleanupInventory,
2551
- references: [...new Map([...cleanupInventory.references, ...projectSkills].map((reference) => [[
2552
- reference.kind,
2553
- reference.location,
2554
- reference.sourceId ?? '',
2555
- reference.pluginName ?? '',
2556
- reference.platform ?? '',
2557
- reference.scope ?? '',
2558
- ].join('\0'), reference])).values()],
2559
- };
2560
- const removed = (await Promise.all(exactIds.map((pluginId) => inspectRemovedPluginCleanup({
2561
- pluginId,
2562
- inventory: fullInventory,
2563
- cwd: options.commandOptions.cwd,
2564
- env: runtimeEnv,
2565
- nativeCliRunner: options.runtime?.nativeCliRunner ?? getDefaultNativeCliRunner(),
2566
- codexManager: options.runtime?.codexManager,
2567
- })))).filter((target) => target !== null);
2568
- cleanupUnavailableWarnings = await unavailableRemovedCleanupWarnings({
2569
- pluginIds: exactIds.filter((pluginId) => !removed.some((target) => target.pluginId === pluginId)),
2570
- inventory: fullInventory,
2571
- cwd: options.commandOptions.cwd,
2572
- env: runtimeEnv,
2573
- });
2574
- if (removed.length > 0) {
2575
- const removedIds = new Set(removed.map((target) => target.pluginId));
2576
- const intents = {
2577
- user: {
2578
- before: await readPluginIntentState({
2579
- scope: 'user', cwd: options.commandOptions.cwd, env: runtimeEnv,
2580
- }),
2581
- after: createEmptyPluginIntentState(),
2582
- },
2583
- project: {
2584
- before: await readPluginIntentState({
2585
- scope: 'project', cwd: options.commandOptions.cwd, env: runtimeEnv,
2586
- }),
2587
- after: createEmptyPluginIntentState(),
2588
- },
2589
- };
2590
- intents.user.after = removeRemovedPluginIntent(intents.user.before, removedIds);
2591
- intents.project.after = removeRemovedPluginIntent(intents.project.before, removedIds);
2592
- const removedPlan = await withNativeToolRemovalImpact(createRemovedPluginCleanupOperationPlan({
2593
- targets: removed,
2594
- intents,
2595
- cwd: options.commandOptions.cwd,
2596
- env: runtimeEnv,
2597
- }), runtimeEnv);
2598
- const remaining = exactIds.filter((pluginId) => !removedIds.has(pluginId));
2599
- const normalOptions = {
2600
- ...options,
2601
- all: false,
2602
- pluginNames: remaining,
2603
- runtime: {
2604
- ...options.runtime,
2605
- expectedPlanFingerprint: undefined,
2606
- expectedOfflineCleanupEvidence: undefined,
2607
- },
2608
- };
2609
- const normalPreview = remaining.length > 0
2610
- ? await runRebuildCommandInSession({
2611
- ...normalOptions,
2612
- runtime: { ...normalOptions.runtime, previewOperationPlan: true },
2613
- })
2614
- : undefined;
2615
- const operationPlan = normalPreview?.operationPlan
2616
- ? combineCleanupOperationPlans([removedPlan, normalPreview.operationPlan])
2617
- : removedPlan;
2618
- if (options.runtime?.expectedPlanFingerprint) {
2619
- assertOperationPlanFingerprint(options.runtime.expectedPlanFingerprint, operationPlan);
2620
- }
2621
- if (options.runtime?.previewOperationPlan) {
2622
- const preview = normalPreview ?? mergeRebuildCommandResults([]);
2623
- preview.removedPluginCleanupIds = [...removedIds];
2624
- preview.operationPlan = operationPlan;
2625
- preview.warnings = [...new Set([...preview.warnings, ...removedPlan.warnings])];
2626
- return preview;
2627
- }
2628
- assertNativeToolChangeAuthorization(operationPlan, options);
2629
- if (options.commandOptions.yes !== true
2630
- && !(options.runtime?.preApplyConfirmed === true
2631
- && options.runtime.expectedPlanFingerprint !== undefined)) {
2632
- throw new Error('removedPluginCleanupAuthorizationRequired: deleted-plugin cleanup covers Codex and Claude in user and current-project scopes, removes saved settings and the shared Codex native package, and may affect other projects. Use --yes or accept the full Review.');
2633
- }
2634
- const removedResult = await executeRemovedPluginCleanup({
2635
- targets: removed,
2636
- intents,
2637
- inventory: fullInventory,
2638
- cwd: options.commandOptions.cwd,
2639
- env: runtimeEnv,
2640
- runtime: options.runtime,
2641
- readInventory,
2642
- operationPlan: removedPlan,
2643
- });
2644
- let normalResult;
2645
- if (remaining.length > 0) {
2646
- try {
2647
- for (const scope of ['user', 'project']) {
2648
- const actual = await readPluginIntentState({ scope, cwd: options.commandOptions.cwd, env: runtimeEnv });
2649
- if (calculatePluginIntentDigest(actual) !== calculatePluginIntentDigest(intents[scope].after)) {
2650
- throw new Error('operationPlanStale: installation intent changed beyond the reviewed deleted-plugin cleanup.');
2651
- }
2652
- }
2653
- const currentNormalPreview = await runRebuildCommandInSession({
2654
- ...normalOptions,
2655
- runtime: { ...normalOptions.runtime, previewOperationPlan: true },
2656
- });
2657
- if (!normalPreview?.operationPlan || !currentNormalPreview.operationPlan
2658
- || createBatchIntentCompatiblePlanFingerprint(normalPreview.operationPlan)
2659
- !== createBatchIntentCompatiblePlanFingerprint(currentNormalPreview.operationPlan)) {
2660
- throw new Error('operationPlanStale: the remaining cleanup actions changed after the reviewed deleted-plugin cleanup.');
2661
- }
2662
- normalResult = await runRebuildCommandInSession({
2663
- ...normalOptions,
2664
- runtime: { ...normalOptions.runtime, expectedPlanFingerprint: currentNormalPreview.operationPlan.planFingerprint },
2665
- });
2666
- }
2667
- catch (error) {
2668
- const message = error instanceof Error ? error.message : String(error);
2669
- const failures = (normalPreview?.receiptTargets ?? []).map((target) => failedResult('cleanup', {
2670
- ...target, cwd: options.commandOptions.cwd, pluginVersion: 'unavailable',
2671
- }, message));
2672
- normalResult = { ...mergeRebuildCommandResults([]), overallState: 'failed', warnings: [message],
2673
- platforms: normalPreview?.platforms ?? [], rebuildResults: failures,
2674
- targetResults: failures.map(toTargetResult), receiptTargets: failures.map((failure) => failure.target) };
2675
- }
2676
- }
2677
- const combined = normalResult
2678
- ? mergeRebuildCommandResults([removedResult, normalResult])
2679
- : removedResult;
2680
- combined.operationPlan = operationPlan;
2681
- combined.removedPluginCleanupIds = [...removedIds];
2682
- combined.warnings = [...new Set([...combined.warnings, ...operationPlan.warnings])];
2683
- combined.overallState = [removedResult, normalResult].filter(Boolean).every((result) => result?.overallState === 'succeeded')
2684
- ? 'succeeded'
2685
- : [removedResult, normalResult].filter(Boolean).some((result) => result?.overallState === 'succeeded' || result?.overallState === 'partial')
2686
- ? 'partial' : 'failed';
2687
- return combined;
2688
- }
2070
+ if (legacyLiveTargets.length > 0) {
2071
+ throw namespacedPreflightError(`legacyStateUnsupported: legacy live runtime state exists for ${legacyLiveTargets.map((target) => `${target.pluginName}:${target.platform}/${target.scope}`).join(', ')}.`, [], legacyLiveTargets);
2689
2072
  }
2073
+ return mergeRebuildCommandResults([]);
2074
+ }
2075
+ if (options.command === 'cleanup') {
2690
2076
  if (!cleanupForce
2691
2077
  && cleanupTargets.length === 0
2692
2078
  && cleanupSelectionErrors.length === 0
2693
2079
  && legacyLiveTargets.length === 0) {
2694
2080
  const result = mergeRebuildCommandResults([]);
2695
- const operationPlan = await withNativeToolRemovalImpact(createOfflineCleanupOperationPlan([], {
2696
- scope: options.commandOptions.scope,
2697
- before: cleanupIntentBefore,
2698
- after: cleanupIntentAfter,
2699
- }, [...cleanupInventoryWarnings(cleanupInventory, []), ...cleanupUnavailableWarnings], retainedPackageActions), runtimeEnv);
2081
+ const operationPlan = await withNativeToolRemovalImpact(createOfflineCleanupOperationPlan([], { scope: options.commandOptions.scope, view: cleanupRequirements }, [...cleanupInventoryWarnings(cleanupInventory, []), ...cleanupUnavailableWarnings], retainedPackageActions), runtimeEnv);
2700
2082
  if (options.runtime?.expectedPlanFingerprint) {
2701
2083
  assertOperationPlanFingerprint(options.runtime.expectedPlanFingerprint, operationPlan);
2702
2084
  }
@@ -2744,6 +2126,8 @@ async function runRebuildCommandInSession(options) {
2744
2126
  damagedOnly: cleanupLiveTargets.length > 0,
2745
2127
  })));
2746
2128
  const forcePlan = await mergeForceCleanupPlans(livePlan, ...supplementAttempts.flatMap((attempt) => attempt.status === 'fulfilled' ? [attempt.value] : []));
2129
+ forcePlan.targets = forcePlan.targets.filter((target) => selectedExecutionTarget(target.identity));
2130
+ forcePlan.evidence = forcePlan.evidence.filter(selectedExecutionTarget);
2747
2131
  const forceCleanupTargets = [...new Map([
2748
2132
  ...cleanupTargets,
2749
2133
  ...forcePlan.targets.map((target) => ({
@@ -2786,11 +2170,7 @@ async function runRebuildCommandInSession(options) {
2786
2170
  && candidate.platform === target.platform
2787
2171
  && candidate.scope === target.scope)
2788
2172
  ?? intentOnlyOfflineCleanupEvidence(target));
2789
- const operationPlan = await withNativeToolRemovalImpact(createOfflineCleanupOperationPlan(forceEvidence, {
2790
- scope: options.commandOptions.scope,
2791
- before: cleanupIntentBefore,
2792
- after: cleanupIntentAfter,
2793
- }, cleanupInventoryWarnings(cleanupInventory, forceCleanupTargets), retainedPackageActions), runtimeEnv);
2173
+ const operationPlan = await withNativeToolRemovalImpact(createOfflineCleanupOperationPlan(forceEvidence, { scope: options.commandOptions.scope, view: cleanupRequirements }, cleanupInventoryWarnings(cleanupInventory, forceCleanupTargets), retainedPackageActions), runtimeEnv);
2794
2174
  if (options.runtime?.expectedPlanFingerprint) {
2795
2175
  assertOperationPlanFingerprint(options.runtime.expectedPlanFingerprint, operationPlan);
2796
2176
  }
@@ -2807,14 +2187,6 @@ async function runRebuildCommandInSession(options) {
2807
2187
  };
2808
2188
  }
2809
2189
  assertNativeToolChangeAuthorization(operationPlan, options);
2810
- if (calculatePluginIntentDigest(cleanupIntentBefore)
2811
- !== calculatePluginIntentDigest(cleanupIntentAfter)) {
2812
- await (options.runtime?.writePluginIntentState ?? writePluginIntentState)({
2813
- scope: options.commandOptions.scope,
2814
- cwd: options.commandOptions.cwd,
2815
- env: runtimeEnv,
2816
- }, cleanupIntentAfter);
2817
- }
2818
2190
  const forceResults = await Promise.all(forcePlan.targets.map(async (target) => {
2819
2191
  const integration = createRebuildIntegration({
2820
2192
  context: {
@@ -2824,7 +2196,16 @@ async function runRebuildCommandInSession(options) {
2824
2196
  debugLogger: options.runtime?.debugLogger ?? createNoopDebugLogger(),
2825
2197
  },
2826
2198
  });
2827
- const { finalizeCleanup: _finalizeCleanup, ...pathOnlyDriver } = integration.driver;
2199
+ const { finalizeCleanup: _finalizeCleanup, ...driver } = integration.driver;
2200
+ const pathOnlyDriver = {
2201
+ ...driver,
2202
+ beforeCleanupMutation: async (component) => {
2203
+ await driver.beforeCleanupMutation?.(component);
2204
+ await removeForceCleanupCodexRoleRegistration({
2205
+ component, cwd: options.commandOptions.cwd, env: runtimeEnv,
2206
+ });
2207
+ },
2208
+ };
2828
2209
  const result = await (options.runtime?.executeCleanupOperation ?? executeRebuildOperation)({
2829
2210
  command: 'cleanup', target, driver: pathOnlyDriver,
2830
2211
  });
@@ -2880,9 +2261,8 @@ async function runRebuildCommandInSession(options) {
2880
2261
  targetResults: allResults.map(toTargetResult),
2881
2262
  overallState: allResults.every((entry) => entry.status === 'succeeded')
2882
2263
  ? 'succeeded'
2883
- : operationPlan.intentChanges.length > 0
2884
- || allResults.some((entry) => entry.status === 'succeeded'
2885
- || entry.actions.some((action) => action.status === 'succeeded'))
2264
+ : allResults.some((entry) => entry.status === 'succeeded'
2265
+ || entry.actions.some((action) => action.status === 'succeeded'))
2886
2266
  ? 'partial' : 'failed',
2887
2267
  receiptTargets: allResults.map((entry) => entry.target),
2888
2268
  offlineCleanupPlans: forceEvidence,
@@ -2891,11 +2271,7 @@ async function runRebuildCommandInSession(options) {
2891
2271
  }
2892
2272
  if (cleanupTargets.length === 0) {
2893
2273
  const result = mergeRebuildCommandResults([]);
2894
- const operationPlan = await withNativeToolRemovalImpact(createOfflineCleanupOperationPlan([], {
2895
- scope: options.commandOptions.scope,
2896
- before: cleanupIntentBefore,
2897
- after: cleanupIntentAfter,
2898
- }, [...cleanupInventoryWarnings(cleanupInventory, []), ...cleanupUnavailableWarnings], retainedPackageActions), runtimeEnv);
2274
+ const operationPlan = await withNativeToolRemovalImpact(createOfflineCleanupOperationPlan([], { scope: options.commandOptions.scope, view: cleanupRequirements }, [...cleanupInventoryWarnings(cleanupInventory, []), ...cleanupUnavailableWarnings], retainedPackageActions), runtimeEnv);
2899
2275
  if (options.runtime?.expectedPlanFingerprint) {
2900
2276
  assertOperationPlanFingerprint(options.runtime.expectedPlanFingerprint, operationPlan);
2901
2277
  }
@@ -2924,7 +2300,9 @@ async function runRebuildCommandInSession(options) {
2924
2300
  sourceIdentity: target.sourceIdentity,
2925
2301
  platform: target.platform,
2926
2302
  scope: target.scope,
2927
- inventory: cleanupInventory,
2303
+ inventory: options.runtime?.preserveLegacyNativeSkills
2304
+ ? { ...cleanupInventory, references: cleanupInventory.references.filter((reference) => path.basename(reference.location) !== SKILL_OWNER_MARKER) }
2305
+ : cleanupInventory,
2928
2306
  });
2929
2307
  return value ? [value] : [];
2930
2308
  });
@@ -2932,11 +2310,7 @@ async function runRebuildCommandInSession(options) {
2932
2310
  && candidate.platform === target.platform
2933
2311
  && candidate.scope === target.scope)
2934
2312
  ?? intentOnlyOfflineCleanupEvidence(target));
2935
- const operationPlan = await withNativeToolRemovalImpact(createOfflineCleanupOperationPlan(evidence, {
2936
- scope: options.commandOptions.scope,
2937
- before: cleanupIntentBefore,
2938
- after: cleanupIntentAfter,
2939
- }, [...cleanupInventoryWarnings(cleanupInventory, cleanupTargets), ...cleanupUnavailableWarnings], retainedPackageActions), runtimeEnv);
2313
+ const operationPlan = await withNativeToolRemovalImpact(createOfflineCleanupOperationPlan(evidence, { scope: options.commandOptions.scope, view: cleanupRequirements }, [...cleanupInventoryWarnings(cleanupInventory, cleanupTargets), ...cleanupUnavailableWarnings], retainedPackageActions), runtimeEnv);
2940
2314
  if (options.runtime?.expectedPlanFingerprint) {
2941
2315
  assertOperationPlanFingerprint(options.runtime.expectedPlanFingerprint, operationPlan);
2942
2316
  }
@@ -2952,14 +2326,6 @@ async function runRebuildCommandInSession(options) {
2952
2326
  return previewResult;
2953
2327
  }
2954
2328
  assertNativeToolChangeAuthorization(operationPlan, options);
2955
- if (calculatePluginIntentDigest(cleanupIntentBefore)
2956
- !== calculatePluginIntentDigest(cleanupIntentAfter)) {
2957
- await (options.runtime?.writePluginIntentState ?? writePluginIntentState)({
2958
- scope: options.commandOptions.scope,
2959
- cwd: options.commandOptions.cwd,
2960
- env: runtimeEnv,
2961
- }, cleanupIntentAfter);
2962
- }
2963
2329
  const cleanupResult = await cleanupOfflineOwnedComponents({
2964
2330
  targets: cleanupLiveTargets,
2965
2331
  evidence,
@@ -2970,6 +2336,7 @@ async function runRebuildCommandInSession(options) {
2970
2336
  nativeCliRunner: options.runtime?.nativeCliRunner,
2971
2337
  codexManager: options.runtime?.codexManager,
2972
2338
  executeCleanupOperation: options.runtime?.executeCleanupOperation,
2339
+ preserveLegacyNativeSkills: options.runtime?.preserveLegacyNativeSkills,
2973
2340
  });
2974
2341
  const liveKeys = new Set(cleanupLiveTargets.map((target) => `${target.pluginId}\0${target.platform}\0${target.scope}`));
2975
2342
  const intentOnlyResults = cleanupTargets
@@ -2989,11 +2356,8 @@ async function runRebuildCommandInSession(options) {
2989
2356
  cleanupResult.platforms = [...new Set([...cleanupResult.platforms, ...intentOnlyResults.map((result) => result.target.platform)])];
2990
2357
  cleanupResult.plugins = [...new Set([...cleanupResult.plugins, ...intentOnlyResults.map((result) => result.target.pluginName)])];
2991
2358
  cleanupResult.operationPlan = operationPlan;
2992
- if (cleanupResult.overallState === 'failed'
2993
- && operationPlan.intentChanges.length > 0)
2994
- cleanupResult.overallState = 'partial';
2995
- else if (intentOnlyResults.some((result) => result.status === 'failed')) {
2996
- cleanupResult.overallState = operationPlan.intentChanges.length > 0 ? 'partial' : 'failed';
2359
+ if (intentOnlyResults.some((result) => result.status === 'failed')) {
2360
+ cleanupResult.overallState = 'failed';
2997
2361
  }
2998
2362
  if (malformedSelectedConfigPreserved) {
2999
2363
  cleanupResult.warnings.push(MALFORMED_CONFIG_PRESERVED_WARNING);
@@ -3046,6 +2410,10 @@ async function runRebuildCommandInSession(options) {
3046
2410
  }
3047
2411
  return matches[0];
3048
2412
  });
2413
+ const requestedIds = new Set(selectedEntries.map((entry) => entry.pluginId));
2414
+ if (executionInput.selectedTargets.some((target) => !requestedIds.has(target.pluginId))) {
2415
+ throw new Error('nativeExecutionTargetOutsideRequest: selected target must match a requested plugin.');
2416
+ }
3049
2417
  if (options.command === 'uninstall' || (options.command === 'cleanup' && options.all)) {
3050
2418
  await measureCommandPerformanceStage(options.runtime, 'catalog.validation', async () => {
3051
2419
  const selectedSources = new Map(selectedEntries.map((candidate) => [
@@ -3057,17 +2425,9 @@ async function runRebuildCommandInSession(options) {
3057
2425
  }
3058
2426
  });
3059
2427
  }
3060
- const intentCatalogEntries = availableEntries.map((candidate) => ({
3061
- pluginId: candidate.pluginId,
3062
- dependencyPluginIds: candidate.entry.dependencies.map((dependency) => formatPluginId({
3063
- sourceId: candidate.source.sourceId,
3064
- pluginName: dependency,
3065
- })),
3066
- }));
3067
2428
  const dependencyEnabled = options.command === 'install'
3068
2429
  || options.command === 'sync'
3069
2430
  || options.command === 'reinstall';
3070
- const intentCommand = options.taskTargets ? 'install' : options.command;
3071
2431
  const entryClosure = (roots) => {
3072
2432
  const result = [];
3073
2433
  const seen = new Set();
@@ -3138,9 +2498,20 @@ async function runRebuildCommandInSession(options) {
3138
2498
  }));
3139
2499
  const selected = selectedEntries.map((entry) => available.find((candidate) => candidate.pluginId === entry.pluginId));
3140
2500
  const selectedIds = new Set(selected.map((entry) => entry.pluginId));
3141
- const preserveOtherCodexScopeRootIds = new Set(selected.flatMap((entry) => analyzeCodexSurface(entry.plugin.manifest).scopeOverlap === 'native-compatible'
3142
- ? [entry.pluginId]
3143
- : []));
2501
+ const codexDemandRootsFor = (entry) => requirements.roots.filter((root) => {
2502
+ if (!root.platforms.includes('codex'))
2503
+ return false;
2504
+ const rootEntry = availableEntries.find((candidate) => candidate.pluginId === root.pluginId);
2505
+ if (!rootEntry || rootEntry.source.sourceId !== entry.source.sourceId)
2506
+ return false;
2507
+ try {
2508
+ return entryClosure([rootEntry]).some((candidate) => candidate.pluginId === entry.pluginId);
2509
+ }
2510
+ catch {
2511
+ return rootEntry.pluginId === entry.pluginId;
2512
+ }
2513
+ });
2514
+ const selectedCodexRoot = (pluginId) => executionInput.selectedTargets.some((target) => target.pluginId === pluginId && target.platform === 'codex');
3144
2515
  const resolveClosureEntries = (rootsBySource) => catalog.sources.flatMap((item) => {
3145
2516
  const source = item.resolution;
3146
2517
  const roots = rootsBySource
@@ -3157,12 +2528,13 @@ async function runRebuildCommandInSession(options) {
3157
2528
  });
3158
2529
  const closureEntriesByPlatform = new Map();
3159
2530
  if (dependencyEnabled) {
3160
- const selectedTaskPlatforms = options.taskTargets
3161
- ? [...new Set(options.taskTargets.map((target) => target.platform))]
2531
+ const selectionTargets = executionInput.selectedTargets;
2532
+ const selectedTaskPlatforms = selectionTargets
2533
+ ? [...new Set(selectionTargets.map((target) => target.platform))]
3162
2534
  : selectedPlatforms(options.commandOptions.platform);
3163
2535
  for (const platform of selectedTaskPlatforms) {
3164
- const platformRoots = options.taskTargets
3165
- ? selected.filter((entry) => options.taskTargets.some((target) => target.pluginId === entry.pluginId && target.platform === platform))
2536
+ const platformRoots = selectionTargets
2537
+ ? selected.filter((entry) => selectionTargets.some((target) => target.pluginId === entry.pluginId && target.platform === platform))
3166
2538
  : options.commandOptions.platform === 'both'
3167
2539
  ? selected.filter((entry) => supportsPlatform(entry.plugin.manifest, platform))
3168
2540
  : selected;
@@ -3174,59 +2546,37 @@ async function runRebuildCommandInSession(options) {
3174
2546
  : options.command === 'cleanup'
3175
2547
  ? resolveClosureEntries(selected)
3176
2548
  : selected;
3177
- const taskRootsByTarget = new Map();
3178
- for (const target of options.taskTargets ?? []) {
3179
- const key = `${target.pluginId}\0${target.platform}`;
3180
- taskRootsByTarget.set(key, [...(taskRootsByTarget.get(key) ?? []), target]);
2549
+ const rawExecutionSelections = executionInput.selectedTargets.flatMap((target) => {
2550
+ const root = selected.find((entry) => entry.pluginId === target.pluginId);
2551
+ if (!root)
2552
+ return [];
2553
+ return (dependencyEnabled ? resolveClosureEntries([root]) : [root]).map((entry) => ({
2554
+ entry, platform: target.platform,
2555
+ ...(entry.pluginId === root.pluginId ? { requestedScope: target.scope } : {}),
2556
+ requestedMethod: options.taskTargets?.find((candidate) => candidate.pluginId === entry.pluginId
2557
+ && candidate.platform === target.platform)?.method
2558
+ ?? options.taskRepairTargets?.find((candidate) => candidate.pluginId === entry.pluginId
2559
+ && candidate.platform === target.platform)?.method,
2560
+ }));
2561
+ });
2562
+ const normalizedExecutionSelections = [...new Map(rawExecutionSelections.map((selection) => [[
2563
+ selection.entry.pluginId,
2564
+ selection.entry.source.sourceIdentity,
2565
+ selection.platform,
2566
+ ].join('\0'), selection])).values()];
2567
+ if (dependencyEnabled) {
2568
+ const ordered = new Map();
2569
+ for (const [platform, closure] of closureEntriesByPlatform) {
2570
+ for (const entry of closure) {
2571
+ const selection = normalizedExecutionSelections.find((candidate) => candidate.entry.pluginId === entry.pluginId && candidate.platform === platform);
2572
+ if (selection)
2573
+ ordered.set(`${entry.pluginId}\0${platform}`, selection);
2574
+ }
2575
+ }
2576
+ normalizedExecutionSelections.splice(0, normalizedExecutionSelections.length, ...ordered.values());
3181
2577
  }
3182
- const rawExecutionSelections = options.taskTargets
3183
- ? options.taskTargets.flatMap((root) => {
3184
- const rootEntry = selected.find((entry) => entry.pluginId === root.pluginId);
3185
- return resolveClosureEntries([rootEntry]).flatMap((entry) => {
3186
- const exactRoots = taskRootsByTarget.get(`${entry.pluginId}\0${root.platform}`) ?? [];
3187
- if (exactRoots.length > 0) {
3188
- return exactRoots.map((exactRoot) => ({
3189
- entry,
3190
- platform: root.platform,
3191
- requestedScope: exactRoot.scope,
3192
- requestedMethod: exactRoot.method,
3193
- }));
3194
- }
3195
- return [{
3196
- entry,
3197
- platform: root.platform,
3198
- requestedMethod: options.taskRepairTargets?.find((target) => target.pluginId === entry.pluginId
3199
- && target.platform === root.platform)?.method,
3200
- }];
3201
- });
3202
- })
3203
- : options.all
3204
- ? liveTargets.flatMap((target) => {
3205
- const entry = selected.find((candidate) => candidate.pluginId === target.pluginId);
3206
- if (target.sourceIdentity !== 'unavailable'
3207
- && entry.source.sourceIdentity !== target.sourceIdentity) {
3208
- throw namespacedPreflightError(`sourceChanged: ${target.pluginId} live target has a different source identity.`, liveTargets, legacyLiveTargets);
3209
- }
3210
- const sourcePlugins = available
3211
- .filter((candidate) => candidate.source.sourceId === entry.source.sourceId)
3212
- .map((candidate) => candidate.plugin);
3213
- return (options.command === 'uninstall'
3214
- ? [entry.plugin]
3215
- : resolvePluginMetaDependencyClosure(sourcePlugins, [entry.plugin.manifest.name])).map((plugin) => ({
3216
- entry: available.find((candidate) => candidate.source.sourceId === entry.source.sourceId
3217
- && candidate.plugin.manifest.name === plugin.manifest.name),
3218
- platform: target.platform,
3219
- }));
3220
- })
3221
- : selectedPlatforms(options.commandOptions.platform).flatMap((platform) => (dependencyEnabled ? closureEntriesByPlatform.get(platform) ?? [] : closureEntries)
3222
- .flatMap((entry) => options.command !== 'cleanup'
3223
- && options.command !== 'uninstall'
3224
- && options.commandOptions.platform === 'both'
3225
- && !supportsPlatform(entry.plugin.manifest, platform)
3226
- ? []
3227
- : [{ entry, platform }]));
3228
2578
  const taskSelectionMethods = new Map();
3229
- for (const selection of rawExecutionSelections) {
2579
+ for (const selection of normalizedExecutionSelections) {
3230
2580
  if (!selection.requestedMethod)
3231
2581
  continue;
3232
2582
  const key = [
@@ -3240,18 +2590,11 @@ async function runRebuildCommandInSession(options) {
3240
2590
  }
3241
2591
  taskSelectionMethods.set(key, selection.requestedMethod);
3242
2592
  }
3243
- let executionSelections = [...new Map(rawExecutionSelections.map((selection) => [[
3244
- selection.entry.pluginId,
3245
- selection.entry.source.sourceIdentity,
3246
- selection.platform,
3247
- selection.requestedScope ?? '',
3248
- ].join('\0'), selection])).values()];
2593
+ let executionSelections = normalizedExecutionSelections;
3249
2594
  const planTargetFor = (plan, selection) => plan.targets.find((target) => target.pluginId === selection.entry.pluginId
3250
2595
  && target.platform === selection.platform
3251
- && (!options.taskTargets
3252
- || (selection.requestedScope === undefined
3253
- ? target.relation === 'dependency'
3254
- : target.requestedScope === selection.requestedScope)));
2596
+ && (selection.requestedScope === undefined
2597
+ || target.requestedScope === selection.requestedScope));
3255
2598
  const executionKey = (selection, plan) => {
3256
2599
  const target = planTargetFor(plan, selection);
3257
2600
  return [
@@ -3340,22 +2683,9 @@ async function runRebuildCommandInSession(options) {
3340
2683
  if (checked.omitted)
3341
2684
  stateInventory.errors = checked.inventory.errors;
3342
2685
  assertWriteInventoryComplete(checked.inventory);
3343
- const hasCodexSelection = executionSelections.some((selection) => selection.platform === 'codex');
3344
- const intentBeforeByScope = hasCodexSelection || dependencyEnabled
3345
- ? {
3346
- user: await readPluginIntentState({
3347
- scope: 'user', cwd: options.commandOptions.cwd, env: runtimeEnv,
3348
- }),
3349
- project: await readPluginIntentState({
3350
- scope: 'project', cwd: options.commandOptions.cwd, env: runtimeEnv,
3351
- }),
3352
- }
3353
- : {
3354
- user: createEmptyPluginIntentState(),
3355
- project: createEmptyPluginIntentState(),
3356
- };
3357
- const commandProjectKey = await resolveCanonicalRepoProjectKey(options.commandOptions.cwd);
3358
- const codexScopePathsAlias = codexProjectConfigAliasesUserHome(options.commandOptions.cwd, runtimeEnv);
2686
+ const requirements = executionInput.requirements;
2687
+ const commandProjectKey = executionInput.context.projectRoot ?? executionInput.context.executionCwd;
2688
+ const codexScopePathsAlias = options.commandOptions.scope === 'project' && codexProjectConfigAliasesUserHome(options.commandOptions.cwd, runtimeEnv);
3359
2689
  const selectedLegacy = selectedLegacyLiveReferences(stateInventory.references, new Set(executionSelections.map((selection) => selection.platform)), options.commandOptions.scope);
3360
2690
  const legacy = options.all
3361
2691
  ? selectedLegacy
@@ -3431,24 +2761,17 @@ async function runRebuildCommandInSession(options) {
3431
2761
  return marketplaceName;
3432
2762
  };
3433
2763
  const codexObservationScopeByMarketplace = new Map();
3434
- const projectIntentPluginIds = new Set();
3435
- for (const root of intentBeforeByScope.project.roots.filter((candidate) => candidate.platforms.includes('codex'))) {
3436
- const rootEntry = availableEntries.find((candidate) => candidate.pluginId === root.pluginId);
3437
- if (!rootEntry)
3438
- continue;
3439
- for (const candidate of entryClosure([rootEntry])) {
3440
- projectIntentPluginIds.add(candidate.pluginId);
3441
- }
3442
- }
3443
- const projectCodexConfig = await readCodexConfigFile(resolveCodexPaths(options.commandOptions.cwd, 'project', runtimeEnv).configTomlPath);
2764
+ const projectCodexConfig = options.commandOptions.scope === 'project'
2765
+ ? await readCodexConfigFile(resolveCodexPaths(options.commandOptions.cwd, 'project', runtimeEnv).configTomlPath)
2766
+ : undefined;
3444
2767
  for (const { entry, platform } of executionSelections) {
3445
2768
  if (platform !== 'codex')
3446
2769
  continue;
3447
2770
  const marketplaceName = marketplaceNameFor(entry, platform, options.commandOptions.scope);
3448
2771
  const codexPluginId = `${entry.plugin.manifest.name}@${marketplaceName}`;
3449
2772
  const requiredScope = options.commandOptions.scope === 'project'
3450
- || projectIntentPluginIds.has(entry.pluginId)
3451
- || Object.hasOwn(projectCodexConfig.snapshot.plugins, codexPluginId)
2773
+ || (projectCodexConfig !== undefined
2774
+ && Object.hasOwn(projectCodexConfig.snapshot.plugins, codexPluginId))
3452
2775
  ? 'both'
3453
2776
  : 'user';
3454
2777
  if (requiredScope === 'both'
@@ -3457,6 +2780,10 @@ async function runRebuildCommandInSession(options) {
3457
2780
  }
3458
2781
  }
3459
2782
  const executionContext = {
2783
+ preparedArtifactRenderResults: options.runtime?.preparedArtifactRenderResults,
2784
+ signal: options.runtime?.signal,
2785
+ referencePlugins: options.runtime?.referencePlugins,
2786
+ scriptTimeoutMs: options.commandOptions.scriptTimeoutMs,
3460
2787
  env: runtimeEnv,
3461
2788
  nativeCliRunner: options.runtime?.nativeCliRunner ?? getDefaultNativeCliRunner(),
3462
2789
  codexManager: options.runtime?.codexManager,
@@ -3467,92 +2794,14 @@ async function runRebuildCommandInSession(options) {
3467
2794
  codexObservationScopeByMarketplace,
3468
2795
  preApplyExecutionCache: options.runtime?.preApplyExecutionCache,
3469
2796
  managedCommandProducerCache: options.runtime?.managedCommandProducerCache,
2797
+ preserveLegacyNativeSkills: options.runtime?.preserveLegacyNativeSkills,
2798
+ nativeActivationOnlyTargets: options.runtime?.nativeActivationOnlyTargets,
3470
2799
  commandResults: options.runtime?.commandResults,
3471
2800
  };
3472
2801
  const inspection = createRebuildIntegration({
3473
2802
  context: executionContext,
3474
2803
  scriptTimeoutMs: options.commandOptions.scriptTimeoutMs,
3475
2804
  });
3476
- const selectedRootsForScope = (plan, scope) => [...new Map(plan.targets.flatMap((target) => target.relation === 'requested'
3477
- && (target.requestedScope ?? target.resolvedScope) === scope
3478
- ? [[target.pluginId, {
3479
- pluginId: target.pluginId,
3480
- platforms: plan.targets.flatMap((candidate) => candidate.relation === 'requested'
3481
- && candidate.pluginId === target.pluginId
3482
- && (candidate.requestedScope ?? candidate.resolvedScope) === scope
3483
- ? [candidate.platform]
3484
- : []),
3485
- }]]
3486
- : [])).values()];
3487
- const intentPlanForPreparedRoots = (plan, preparedKeys, scope) => {
3488
- const retainedRootKeys = new Set(plan.targets.flatMap((target) => target.relation === 'requested'
3489
- && (target.requestedScope ?? target.resolvedScope) === scope
3490
- && preparedKeys.has([
3491
- target.pluginId,
3492
- target.platform,
3493
- target.requestedScope ?? target.resolvedScope,
3494
- ].join('\0'))
3495
- ? [[
3496
- target.pluginId,
3497
- target.platform,
3498
- target.requestedScope ?? target.resolvedScope,
3499
- ].join('\0')]
3500
- : []));
3501
- const closureKeys = new Set(retainedRootKeys);
3502
- const pending = [...closureKeys];
3503
- while (pending.length > 0) {
3504
- const consumerKey = pending.pop();
3505
- const consumer = plan.targets.find((target) => [
3506
- target.pluginId,
3507
- target.platform,
3508
- target.requestedScope ?? target.resolvedScope,
3509
- ].join('\0') === consumerKey);
3510
- if (!consumer)
3511
- continue;
3512
- for (const dependency of plan.targets.filter((target) => (target.requiredByTargets ?? []).some((requiredBy) => requiredBy.pluginId === consumer.pluginId
3513
- && requiredBy.platform === consumer.platform
3514
- && requiredBy.scope === (consumer.requestedScope ?? consumer.resolvedScope)))) {
3515
- const dependencyKey = [
3516
- dependency.pluginId,
3517
- dependency.platform,
3518
- dependency.requestedScope ?? dependency.resolvedScope,
3519
- ].join('\0');
3520
- if (closureKeys.has(dependencyKey))
3521
- continue;
3522
- closureKeys.add(dependencyKey);
3523
- pending.push(dependencyKey);
3524
- }
3525
- }
3526
- return {
3527
- plan: {
3528
- ...plan,
3529
- requestedPluginIds: plan.requestedPluginIds.filter((pluginId) => plan.targets.some((target) => target.pluginId === pluginId
3530
- && retainedRootKeys.has([target.pluginId, target.platform,
3531
- target.requestedScope ?? target.resolvedScope].join('\0')))),
3532
- resolvedPluginIds: plan.resolvedPluginIds.filter((pluginId) => plan.targets.some((target) => target.pluginId === pluginId
3533
- && closureKeys.has([target.pluginId, target.platform,
3534
- target.requestedScope ?? target.resolvedScope].join('\0')))),
3535
- dependencyEdges: plan.dependencyEdges.filter((edge) => plan.targets.some((target) => target.pluginId === edge.from
3536
- && target.platform === edge.platform
3537
- && closureKeys.has([target.pluginId, target.platform,
3538
- target.requestedScope ?? target.resolvedScope].join('\0')))
3539
- && plan.targets.some((target) => target.pluginId === edge.to
3540
- && target.platform === edge.platform
3541
- && closureKeys.has([target.pluginId, target.platform,
3542
- target.requestedScope ?? target.resolvedScope].join('\0')))),
3543
- scopeOverrides: plan.scopeOverrides.filter((override) => plan.targets.some((target) => target.pluginId === override.pluginId
3544
- && target.platform === override.platform
3545
- && closureKeys.has([target.pluginId, target.platform,
3546
- target.requestedScope ?? target.resolvedScope].join('\0')))),
3547
- targets: plan.targets.filter((target) => closureKeys.has([target.pluginId, target.platform,
3548
- target.requestedScope ?? target.resolvedScope].join('\0'))),
3549
- },
3550
- selectedRoots: selectedRootsForScope(plan, scope).flatMap((root) => {
3551
- const platforms = root.platforms.filter((platform) => retainedRootKeys.has([root.pluginId, platform, scope].join('\0')));
3552
- return platforms.length > 0 ? [{ ...root, platforms }] : [];
3553
- }),
3554
- };
3555
- };
3556
2805
  let plannedCodexRegistrations = new Map();
3557
2806
  let plannedOwnedTextTargets = new Map();
3558
2807
  let plannedSkillMarkerTargets = new Map();
@@ -3594,7 +2843,10 @@ async function runRebuildCommandInSession(options) {
3594
2843
  const blockingTargets = new Map();
3595
2844
  const actualStates = new Map();
3596
2845
  const loadPlacementCodexConfigs = async () => {
3597
- const [user, project] = await Promise.all(['user', 'project'].map((scope) => readCodexConfigFile(resolveCodexPaths(options.commandOptions.cwd, scope, runtimeEnv).configTomlPath)));
2846
+ const user = await readCodexConfigFile(resolveCodexPaths(options.commandOptions.cwd, 'user', runtimeEnv).configTomlPath);
2847
+ const project = options.commandOptions.scope === 'project'
2848
+ ? await readCodexConfigFile(resolveCodexPaths(options.commandOptions.cwd, 'project', runtimeEnv).configTomlPath)
2849
+ : { content: '', snapshot: parseCodexConfig('') };
3598
2850
  return { user, project };
3599
2851
  };
3600
2852
  let placementCodexConfigs;
@@ -3604,11 +2856,16 @@ async function runRebuildCommandInSession(options) {
3604
2856
  };
3605
2857
  for (const [order, selection] of executionSelections.entries()) {
3606
2858
  const { entry, platform } = selection;
3607
- const relation = options.taskTargets
3608
- ? selection.requestedScope !== undefined ? 'requested' : 'dependency'
3609
- : selectedIds.has(entry.pluginId) ? 'requested' : 'dependency';
2859
+ const relation = executionInput.selectedTargets.some((target) => target.pluginId === entry.pluginId && target.platform === platform)
2860
+ ? 'requested' : 'dependency';
3610
2861
  const requestedScope = selection.requestedScope ?? options.commandOptions.scope;
3611
2862
  const requestedMethod = selection.requestedMethod ?? options.command;
2863
+ const activationOnly = isNativeActivationOnlyTarget(executionContext, {
2864
+ pluginId: entry.pluginId, platform, scope: requestedScope,
2865
+ });
2866
+ const otherScopeRetires = options.runtime?.previewOperationPlan === true
2867
+ && options.runtime.nativePlannedRetirements?.some((target) => target.pluginId === entry.pluginId && target.platform === platform
2868
+ && target.scope !== requestedScope) === true;
3612
2869
  let resolvedScope = requestedScope;
3613
2870
  const inspectScope = async (scope) => {
3614
2871
  const marketplaceName = marketplaceNameFor(entry, platform, scope);
@@ -3677,9 +2934,11 @@ async function runRebuildCommandInSession(options) {
3677
2934
  const otherScope = resolvedScope === 'user' ? 'project' : 'user';
3678
2935
  const [requestedInspection, otherInspection] = await Promise.all([
3679
2936
  inspectScope(resolvedScope),
3680
- platform === 'codex' && codexScopePathsAlias && resolvedScope === 'user'
3681
- ? Promise.resolve({ state: 'absent', components: [], errors: [] })
3682
- : inspectScope(otherScope),
2937
+ options.commandOptions.scope === 'user'
2938
+ ? Promise.resolve({ state: 'partial', components: [], errors: [] })
2939
+ : platform === 'codex' && codexScopePathsAlias && resolvedScope === 'user'
2940
+ ? Promise.resolve({ state: 'absent', components: [], errors: [] })
2941
+ : inspectScope(otherScope),
3683
2942
  ]);
3684
2943
  const surface = platform === 'codex'
3685
2944
  ? analyzeCodexSurface(entry.plugin.manifest)
@@ -3743,9 +3002,6 @@ async function runRebuildCommandInSession(options) {
3743
3002
  : ['project', 'user'];
3744
3003
  if (allowedScopes.length === 0)
3745
3004
  allowedScopes.push(requestedScope);
3746
- const hasWiderDesiredRoot = relation === 'dependency'
3747
- && requestedScope === 'project'
3748
- && intentBeforeByScope.user.roots.some((root) => root.pluginId === entry.pluginId && root.platforms.includes(platform));
3749
3005
  if (!placementNodes.some((node) => node.pluginId === entry.pluginId && node.platform === platform)) {
3750
3006
  placementNodes.push({
3751
3007
  pluginId: entry.pluginId,
@@ -3754,16 +3010,6 @@ async function runRebuildCommandInSession(options) {
3754
3010
  relation,
3755
3011
  depth: order,
3756
3012
  allowedScopes,
3757
- ...(relation === 'dependency'
3758
- && requestedScope === 'project'
3759
- && (otherPlacementState === 'present' || hasWiderDesiredRoot)
3760
- ? {
3761
- preferredScope: 'user',
3762
- ...(otherPlacementState === 'present'
3763
- ? { preferredScopeReason: 'existingWiderTarget' }
3764
- : {}),
3765
- }
3766
- : {}),
3767
3013
  });
3768
3014
  }
3769
3015
  let state = requestedPlacementState;
@@ -3773,13 +3019,20 @@ async function runRebuildCommandInSession(options) {
3773
3019
  : requestedMethod === 'reinstall' ? 'reinstall' : 'install'
3774
3020
  : requestedMethod === 'reinstall' ? 'reinstall' : 'install';
3775
3021
  let errorCode;
3776
- if (dependencyEnabled
3022
+ if (!dependencyEnabled && platform === 'codex'
3023
+ && relation === 'requested'
3024
+ && codexDemandRootsFor(entry).some((root) => !selectedCodexRoot(root.pluginId))) {
3025
+ action = 'block';
3026
+ errorCode = 'dependencyStillRequired';
3027
+ }
3028
+ else if (dependencyEnabled
3777
3029
  && platform === 'codex'
3778
3030
  && relation === 'requested'
3031
+ && !activationOnly
3032
+ && !otherScopeRetires
3779
3033
  && surface?.scopeOverlap === 'aw-owned-conflict'
3780
- && options.command !== 'sync'
3781
- && !(options.command === 'install'
3782
- && intentBeforeByScope[otherScope].roots.some((root) => root.pluginId === entry.pluginId && root.platforms.includes(platform)))
3034
+ && options.command !== 'cleanup'
3035
+ && options.command !== 'uninstall'
3783
3036
  && otherInspection.components.some((component) => component.state !== 'absent'
3784
3037
  && component.component.kind !== 'native-plugin'
3785
3038
  && !component.component.identity.artifactKey.startsWith('activation-')
@@ -3790,6 +3043,7 @@ async function runRebuildCommandInSession(options) {
3790
3043
  else if (dependencyEnabled && relation === 'requested'
3791
3044
  && (requestedInspection.state === 'error'
3792
3045
  || (requestedInspection.state === 'partial'
3046
+ && !activationOnly
3793
3047
  && platform !== 'codex'
3794
3048
  && requestedMethod !== 'reinstall'
3795
3049
  && options.command !== 'cleanup'))) {
@@ -3797,34 +3051,33 @@ async function runRebuildCommandInSession(options) {
3797
3051
  errorCode = 'dependencyNotReady';
3798
3052
  }
3799
3053
  if (relation === 'dependency'
3800
- && requestedPlacementState === 'present'
3801
- && otherPlacementState === 'present') {
3802
- action = 'block';
3803
- state = 'partial';
3804
- errorCode = 'dependencyScopeConflict';
3805
- }
3806
- else if (relation === 'dependency'
3807
- && requestedScope === 'project'
3808
- && otherInspection.state === 'present') {
3809
- resolvedScope = 'user';
3810
- state = 'present';
3811
- }
3812
- else if (relation === 'dependency'
3813
- && requestedScope === 'project'
3814
- && (otherPlacementState === 'partial' || otherPlacementState === 'error')) {
3815
- resolvedScope = 'user';
3816
- state = otherPlacementState;
3817
- if (otherPlacementState === 'error') {
3818
- action = 'block';
3819
- errorCode = 'dependencyNotReady';
3820
- }
3821
- }
3822
- else if (relation === 'dependency'
3823
3054
  && requestedScope === 'user'
3824
3055
  && otherPlacementState === 'error') {
3825
3056
  action = 'block';
3826
3057
  errorCode = 'dependencyNotReady';
3827
3058
  }
3059
+ const dependencyScopeOverride = relation === 'dependency'
3060
+ ? [
3061
+ ...(options.commandOptions.dependencyScopeOverrides ?? []),
3062
+ ...(requirements.dependencyScopeOverrides ?? []),
3063
+ ].find((override) => override.pluginId === entry.pluginId
3064
+ && (override.platform === undefined || override.platform === platform))
3065
+ : undefined;
3066
+ if (dependencyEnabled && relation === 'dependency' && platform === 'codex'
3067
+ && !activationOnly
3068
+ && !otherScopeRetires
3069
+ && surface?.scopeOverlap === 'aw-owned-conflict'
3070
+ && (dependencyScopeOverride?.scope ?? requestedScope) === requestedScope
3071
+ && !(requestedScope === 'project'
3072
+ && dependencyScopeOverride?.scope === 'project'
3073
+ && otherPlacementState === 'present')
3074
+ && otherInspection.components.some((component) => component.state !== 'absent'
3075
+ && component.component.kind !== 'native-plugin'
3076
+ && !component.component.identity.artifactKey.startsWith('activation-')
3077
+ && !component.component.identity.artifactKey.startsWith('marketplace-'))) {
3078
+ action = 'block';
3079
+ errorCode = 'codexExtensionScopeConflict';
3080
+ }
3828
3081
  const resolvedInspection = resolvedScope === requestedScope
3829
3082
  ? requestedInspection
3830
3083
  : otherInspection;
@@ -3871,6 +3124,7 @@ async function runRebuildCommandInSession(options) {
3871
3124
  manifest: entry.plugin.manifest,
3872
3125
  referencePlugins: options.runtime?.referencePlugins,
3873
3126
  scope,
3127
+ platform,
3874
3128
  cwd: options.commandOptions.cwd,
3875
3129
  env: runtimeEnv,
3876
3130
  })));
@@ -3913,21 +3167,11 @@ async function runRebuildCommandInSession(options) {
3913
3167
  const placement = createDependencyPlacementPlan({
3914
3168
  requestedPluginIds: selected.map((entry) => entry.pluginId),
3915
3169
  nodes: placementNodes,
3916
- roots: options.taskTargets
3917
- ? executionSelections.flatMap((selection) => selection.requestedScope
3918
- ? [{
3919
- pluginId: selection.entry.pluginId,
3920
- platform: selection.platform,
3921
- scope: selection.requestedScope,
3922
- }]
3923
- : [])
3924
- : placementNodes.flatMap((node) => selectedIds.has(node.pluginId)
3925
- ? [{
3926
- pluginId: node.pluginId,
3927
- platform: node.platform,
3928
- scope: options.commandOptions.scope,
3929
- }]
3930
- : []),
3170
+ roots: executionInput.selectedTargets.map((target) => ({
3171
+ pluginId: target.pluginId,
3172
+ platform: target.platform,
3173
+ scope: target.scope,
3174
+ })),
3931
3175
  dependencyEdges: closureEntries.flatMap((entry) => (entry.plugin.manifest.dependencies ?? []).flatMap((dependency) => {
3932
3176
  const dependencyId = formatPluginId({
3933
3177
  sourceId: entry.source.sourceId,
@@ -3939,13 +3183,17 @@ async function runRebuildCommandInSession(options) {
3939
3183
  : []);
3940
3184
  })),
3941
3185
  scopeOverrides: effectiveDependencyScopeOverrides({
3942
- state: intentBeforeByScope[options.commandOptions.scope],
3186
+ state: requirements,
3943
3187
  explicit: options.commandOptions
3944
3188
  .dependencyScopeOverrides ?? [],
3945
3189
  nodes: placementNodes,
3946
3190
  ignoredPersistedRootIds: selectedIds,
3947
3191
  }),
3948
3192
  });
3193
+ if (options.commandOptions.scope === 'user'
3194
+ && placement.targets.some((target) => target.resolvedScope === 'project')) {
3195
+ throw new Error('nativeUserProjectPlacementUnsupported: User execution cannot use project output.');
3196
+ }
3949
3197
  for (const repairTarget of options.taskRepairTargets ?? []) {
3950
3198
  if (!placement.targets.some((target) => target.pluginId === repairTarget.pluginId
3951
3199
  && target.platform === repairTarget.platform)) {
@@ -3970,34 +3218,21 @@ async function runRebuildCommandInSession(options) {
3970
3218
  ?? options.taskRepairTargets?.find((candidate) => candidate.pluginId === target.pluginId
3971
3219
  && candidate.platform === target.platform)?.method
3972
3220
  ?? options.command;
3973
- const otherScope = target.resolvedScope === 'user' ? 'project' : 'user';
3974
- const otherState = actualStates.get(`${target.pluginId}\0${target.platform}\0${otherScope}`);
3975
- const movesExistingPlacement = target.relation === 'dependency'
3976
- && target.resolvedScope === 'user'
3977
- && (state === 'absent' || state === 'partial')
3978
- && (otherState === 'present' || otherState === 'partial');
3979
- const preservesWiderPlacement = target.relation === 'dependency'
3980
- && target.resolvedScope === 'project'
3981
- && (state === 'absent' || state === 'partial')
3982
- && (otherState === 'present' || otherState === 'partial');
3983
3221
  const action = errorCode
3984
3222
  ? 'block'
3985
3223
  : options.command === 'cleanup' || options.command === 'uninstall'
3986
3224
  ? 'keep'
3987
- : movesExistingPlacement
3988
- ? 'move'
3989
- : target.relation === 'dependency' && state === 'present'
3990
- ? 'keep'
3991
- : state === 'partial' || requestedMethod === 'reinstall'
3992
- ? 'reinstall'
3993
- : 'install';
3225
+ : target.relation === 'dependency' && state === 'present'
3226
+ ? 'keep'
3227
+ : state === 'partial' || requestedMethod === 'reinstall'
3228
+ ? 'reinstall'
3229
+ : 'install';
3994
3230
  return {
3995
3231
  ...prior,
3996
3232
  scope: targetScope,
3997
3233
  state,
3998
3234
  action,
3999
3235
  ...(errorCode ? { errorCode } : {}),
4000
- ...(preservesWiderPlacement ? { preservedScope: 'user' } : {}),
4001
3236
  };
4002
3237
  }),
4003
3238
  });
@@ -4014,10 +3249,12 @@ async function runRebuildCommandInSession(options) {
4014
3249
  });
4015
3250
  return createReconcileOperationPlan({ placement: directPlacement, actual: targets });
4016
3251
  };
3252
+ const isHealthyWiderDependency = (target) => target.relation === 'dependency'
3253
+ && target.resolvedScope !== options.commandOptions.scope
3254
+ && target.action === 'keep';
4017
3255
  const createCodexPlans = async (plan, activationSupport = 'observed', codexManager) => {
4018
3256
  const codexTargets = plan.targets.filter((target) => target.platform === 'codex');
4019
3257
  const currentScope = options.commandOptions.scope;
4020
- const otherScope = currentScope === 'user' ? 'project' : 'user';
4021
3258
  const reconciledCodexTargetsByPlugin = new Map();
4022
3259
  for (const target of codexTargets) {
4023
3260
  if (!reconciledCodexTargetsByPlugin.has(target.pluginId)
@@ -4028,99 +3265,22 @@ async function runRebuildCommandInSession(options) {
4028
3265
  const reconciledCodexTargets = [...reconciledCodexTargetsByPlugin.values()];
4029
3266
  if (reconciledCodexTargets.length === 0)
4030
3267
  return [];
4031
- const beforeByScope = intentBeforeByScope;
4032
- const afterByScope = {
4033
- user: beforeByScope.user,
4034
- project: beforeByScope.project,
4035
- };
4036
- for (const scope of ['user', 'project']) {
4037
- const selectedRoots = selectedRootsForScope(plan, scope);
4038
- afterByScope[scope] = deriveDesiredIntentState({
4039
- command: intentCommand,
4040
- state: beforeByScope[scope],
4041
- selectedRoots,
4042
- plan,
4043
- catalogEntries: intentCatalogEntries,
4044
- });
4045
- }
4046
- if (options.command === 'install') {
4047
- for (const scope of ['user', 'project']) {
4048
- const relatedScope = scope === 'user' ? 'project' : 'user';
4049
- const protectedRoots = new Set(selectedRootsForScope(plan, relatedScope)
4050
- .flatMap((root) => root.platforms.map((platform) => `${root.pluginId}\0${platform}`)));
4051
- const selectedRoots = selectedRootsForScope(plan, scope).flatMap((root) => {
4052
- const platforms = root.platforms.filter((platform) => !protectedRoots.has(`${root.pluginId}\0${platform}`));
4053
- return platforms.length > 0 ? [{ ...root, platforms }] : [];
4054
- });
4055
- afterByScope[relatedScope] = removeSelectedRootPlatformsFromOtherScope({
4056
- state: afterByScope[relatedScope],
4057
- selectedRoots,
4058
- preserveCodexRootIds: preserveOtherCodexScopeRootIds,
4059
- });
4060
- }
4061
- }
4062
3268
  const desiredScopesForEntry = (entry) => {
4063
- const scopes = new Set();
4064
- for (const intentScope of ['user', 'project']) {
4065
- const intent = afterByScope[intentScope];
4066
- for (const root of intent.roots.filter((candidate) => candidate.platforms.includes('codex'))) {
4067
- const rootEntry = availableEntries.find((candidate) => candidate.pluginId === root.pluginId);
4068
- if (!rootEntry || rootEntry.source.sourceId !== entry.source.sourceId)
4069
- continue;
4070
- let closure;
4071
- try {
4072
- closure = entryClosure([rootEntry]);
4073
- }
4074
- catch {
4075
- closure = [rootEntry];
4076
- }
4077
- if (!closure.some((candidate) => candidate.pluginId === entry.pluginId)) {
4078
- continue;
4079
- }
4080
- const currentPlannedScope = selectedRootsForScope(plan, intentScope)
4081
- .some((selectedRoot) => selectedRoot.pluginId === root.pluginId)
4082
- ? plan.targets.find((target) => target.pluginId === entry.pluginId
4083
- && target.platform === 'codex'
4084
- && (target.relation === 'dependency'
4085
- || target.requestedScope === intentScope))?.resolvedScope
4086
- : undefined;
4087
- const override = intent.dependencyScopeOverrides.find((candidate) => candidate.pluginId === entry.pluginId && candidate.platform === 'codex');
4088
- const reusedUserDependency = intentScope === 'project'
4089
- && root.pluginId !== entry.pluginId
4090
- && scopes.has('user');
4091
- scopes.add(currentPlannedScope
4092
- ?? override?.scope
4093
- ?? (reusedUserDependency ? 'user' : intentScope));
4094
- }
4095
- }
4096
- return [...scopes].sort();
4097
- };
4098
- const intentScopesForEntry = (entry) => {
4099
- const scopes = new Set();
4100
- for (const intentScope of ['user', 'project']) {
4101
- for (const root of afterByScope[intentScope].roots.filter((candidate) => candidate.platforms.includes('codex'))) {
4102
- const rootEntry = availableEntries.find((candidate) => candidate.pluginId === root.pluginId);
4103
- if (!rootEntry || rootEntry.source.sourceId !== entry.source.sourceId)
4104
- continue;
4105
- let closure;
4106
- try {
4107
- closure = entryClosure([rootEntry]);
4108
- }
4109
- catch {
4110
- closure = [rootEntry];
4111
- }
4112
- if (closure.some((candidate) => candidate.pluginId === entry.pluginId)) {
4113
- scopes.add(intentScope);
4114
- }
4115
- }
4116
- }
4117
- return [...scopes].sort();
3269
+ const roots = codexDemandRootsFor(entry);
3270
+ const protectedRoot = roots.some((root) => !selectedCodexRoot(root.pluginId));
3271
+ const selectedForRemoval = selectedCodexRoot(entry.pluginId);
3272
+ if ((options.command === 'cleanup' || options.command === 'uninstall')
3273
+ && (roots.length === 0 || (selectedForRemoval && !protectedRoot)))
3274
+ return [];
3275
+ const planned = plan.targets.find((target) => target.pluginId === entry.pluginId && target.platform === 'codex');
3276
+ return [planned?.resolvedScope ?? currentScope];
4118
3277
  };
4119
- const inspectBothScopes = options.command !== 'cleanup' && options.command !== 'uninstall';
4120
- const [userConfig, projectConfig] = await Promise.all([
4121
- readCodexConfigFile(resolveCodexPaths(options.commandOptions.cwd, 'user', runtimeEnv).configTomlPath),
4122
- readCodexConfigFile(resolveCodexPaths(options.commandOptions.cwd, 'project', runtimeEnv).configTomlPath),
4123
- ]);
3278
+ const intentScopesForEntry = (entry) => codexDemandRootsFor(entry).length > 0 ? [currentScope] : [];
3279
+ const inspectBothScopes = currentScope === 'project';
3280
+ const userConfig = await readCodexConfigFile(resolveCodexPaths(options.commandOptions.cwd, 'user', runtimeEnv).configTomlPath);
3281
+ const projectConfig = currentScope === 'project'
3282
+ ? await readCodexConfigFile(resolveCodexPaths(options.commandOptions.cwd, 'project', runtimeEnv).configTomlPath)
3283
+ : { content: '', snapshot: parseCodexConfig('') };
4124
3284
  const catalogClosure = [];
4125
3285
  const liveEvidence = [];
4126
3286
  const desiredScopesByPluginId = new Map();
@@ -4142,12 +3302,22 @@ async function runRebuildCommandInSession(options) {
4142
3302
  }
4143
3303
  for (const planned of reconciledCodexTargets) {
4144
3304
  const entry = available.find((candidate) => candidate.pluginId === planned.pluginId);
4145
- const surface = analyzeCodexSurface(entry.plugin.manifest);
3305
+ const activationOnly = isNativeActivationOnlyTarget(executionContext, {
3306
+ pluginId: entry.pluginId, platform: 'codex', scope: planned.resolvedScope,
3307
+ });
3308
+ const declaredSurface = analyzeCodexSurface(entry.plugin.manifest);
3309
+ const surface = activationOnly ? {
3310
+ ...declaredSurface,
3311
+ components: declaredSurface.components.filter((component) => component.owner === 'codex-native'),
3312
+ extensionKinds: [],
3313
+ classification: 'native',
3314
+ deliveryOwner: 'codex-native',
3315
+ scopeOverlap: 'native-compatible',
3316
+ } : declaredSurface;
4146
3317
  const marketplaceName = marketplaceNameFor(entry, 'codex', planned.resolvedScope);
4147
3318
  const codexPluginId = `${entry.plugin.manifest.name}@${marketplaceName}`;
4148
3319
  const hasNativeRenderTargets = (entry.plugin.manifest.artifacts?.targets ?? []).some((target) => target.platform === 'codex'
4149
- && target.target === 'codex.skill'
4150
- && target.delivery?.mode === 'codex-native');
3320
+ && target.target === 'codex.skill');
4151
3321
  const staticContentDigest = hasNativeRenderTargets
4152
3322
  ? undefined
4153
3323
  : await calculateCodexSharedNativePayloadDigest(entry.plugin.rootDir);
@@ -4190,11 +3360,21 @@ async function runRebuildCommandInSession(options) {
4190
3360
  resolvedScope: planned.resolvedScope,
4191
3361
  desiredScopes,
4192
3362
  intentScopes,
3363
+ executionScopes: [...new Set(codexTargets.filter((target) => target.pluginId === entry.pluginId).map((target) => target.resolvedScope))],
4193
3364
  surface,
4194
3365
  });
4195
3366
  let sharedNative = sharedInspection?.sharedNative
4196
3367
  ?? { state: 'unobserved' };
4197
- if (options.command === 'sync' && sharedNative.state === 'usable'
3368
+ if (activationOnly && sharedNative.state !== 'usable') {
3369
+ if (options.runtime?.previewOperationPlan && sharedNative.state !== 'blocked') {
3370
+ // The joint request can establish its User provider before this target executes.
3371
+ sharedNative = { state: 'usable' };
3372
+ }
3373
+ else {
3374
+ throw new Error('nativeActivationOnlyRequiresHealthySharedPackage: the shared Native package must be current and owned before scope activation.');
3375
+ }
3376
+ }
3377
+ if (!activationOnly && options.command === 'sync' && sharedNative.state === 'usable'
4198
3378
  && await hasPreparedStaticNativeCommandDrift({ plugin: entry.plugin,
4199
3379
  cwd: options.commandOptions.cwd, env: runtimeEnv, context: executionContext,
4200
3380
  marketplaceRoot: path.join(resolveAwHome(runtimeEnv), 'marketplaces', entry.source.sourceId) })) {
@@ -4282,50 +3462,11 @@ async function runRebuildCommandInSession(options) {
4282
3462
  scope: currentScope,
4283
3463
  cwd: options.commandOptions.cwd,
4284
3464
  operation: options.command,
4285
- intentBefore: beforeByScope[currentScope],
4286
- intentAfter: afterByScope[currentScope],
4287
- relatedIntentBefore: beforeByScope[otherScope],
4288
- relatedIntentAfter: afterByScope[otherScope],
4289
3465
  catalogClosure,
4290
3466
  liveEvidence,
4291
3467
  activationSupport,
4292
3468
  });
4293
- const preservedScopes = new Map(plan.targets.flatMap((target) => target.platform === 'codex' && target.preservedScope
4294
- ? [[target.pluginId, target.preservedScope]]
4295
- : []));
4296
- if (preservedScopes.size === 0)
4297
- return [codexPlan];
4298
- const preservedNativePluginIds = new Set(catalogClosure.flatMap((target) => (options.command === 'install' || options.command === 'reinstall')
4299
- && preservedScopes.get(target.pluginId) === 'user'
4300
- && liveEvidence.find((evidence) => evidence.codexPluginId === target.codexPluginId)?.activations.user === true
4301
- ? [target.pluginId]
4302
- : []));
4303
- const preservedCodexIds = new Set(codexPlan.sharedNativeActions.flatMap((action) => preservedNativePluginIds.has(action.pluginId) ? [action.codexPluginId] : []));
4304
- return [{
4305
- ...codexPlan,
4306
- desiredTargets: codexPlan.desiredTargets.map((target) => {
4307
- const preservedScope = preservedNativePluginIds.has(target.pluginId)
4308
- ? preservedScopes.get(target.pluginId)
4309
- : undefined;
4310
- return preservedScope
4311
- ? { ...target, scopes: [...new Set([...target.scopes, preservedScope])].sort() }
4312
- : target;
4313
- }),
4314
- activationActions: {
4315
- user: codexPlan.activationActions.user.map((action) => preservedNativePluginIds.has(action.pluginId)
4316
- ? { ...action, action: 'none', desired: true }
4317
- : action),
4318
- project: codexPlan.activationActions.project,
4319
- },
4320
- extensionRemovals: codexPlan.extensionRemovals.filter((action) => preservedScopes.get(action.pluginId) !== action.scope),
4321
- staleActivationRemovals: codexPlan.staleActivationRemovals.filter((action) => !(action.scope === 'user' && preservedCodexIds.has(action.codexPluginId))),
4322
- postcheck: {
4323
- ...codexPlan.postcheck,
4324
- plugins: codexPlan.postcheck.plugins.map((plugin) => preservedNativePluginIds.has(plugin.pluginId)
4325
- ? { ...plugin, expectedUserEnabled: true }
4326
- : plugin),
4327
- },
4328
- }];
3469
+ return [codexPlan];
4329
3470
  };
4330
3471
  const operationNeedsNativeCli = async (platform, plan) => {
4331
3472
  for (const { entry, platform: selectedPlatform } of executionSelections) {
@@ -4354,9 +3495,7 @@ async function runRebuildCommandInSession(options) {
4354
3495
  scope,
4355
3496
  cwd: options.commandOptions.cwd,
4356
3497
  command: dependencyEnabled
4357
- ? planned.action === 'move'
4358
- ? planned.state === 'partial' ? 'reinstall' : 'install'
4359
- : planned.action
3498
+ ? planned.action
4360
3499
  : options.command,
4361
3500
  catalog: toCatalogResolution(entry.source),
4362
3501
  readOnlyInspectionPlan: true,
@@ -4394,27 +3533,19 @@ async function runRebuildCommandInSession(options) {
4394
3533
  const key = executionKey(selection, plan);
4395
3534
  if (planned.action === 'block')
4396
3535
  continue;
4397
- const desiredCleanup = platform === 'codex'
4398
- && options.command === 'cleanup'
4399
- && codexPlans.some((codexPlan) => codexPlan.desiredTargets.some((target) => target.pluginId === entry.pluginId && target.scopes.includes(planned.resolvedScope)));
4400
- const reconcileObsolete = platform === 'codex'
4401
- && options.command === 'sync'
4402
- && codexPlans.some((codexPlan) => codexPlan.desiredTargets.some((target) => target.pluginId === entry.pluginId && !target.scopes.includes(planned.resolvedScope)));
4403
- const buildCommand = reconcileObsolete
4404
- ? 'cleanup'
4405
- : desiredCleanup || planned.action === 'move'
4406
- ? planned.state === 'partial' ? 'reinstall' : 'install'
4407
- : planned.relation === 'dependency'
4408
- && (planned.action === 'install' || planned.action === 'reinstall')
4409
- ? planned.action
4410
- : selection.requestedMethod ?? options.command;
3536
+ const buildCommand = planned.relation === 'dependency'
3537
+ && (planned.action === 'install' || planned.action === 'reinstall')
3538
+ ? planned.action
3539
+ : selection.requestedMethod ?? options.command;
4411
3540
  const plannedNative = platform === 'codex'
4412
3541
  ? codexPlans.flatMap((codexPlan) => codexPlan.sharedNativeActions).find((action) => action.pluginId === entry.pluginId)
4413
3542
  : undefined;
4414
3543
  const codexNativeStillDesired = platform === 'codex'
4415
3544
  && codexPlans.some((codexPlan) => codexPlan.desiredTargets.some((target) => target.pluginId === entry.pluginId && target.scopes.length > 0));
4416
3545
  const preserveSharedNative = plannedNative?.action === 'reuse'
4417
- || (plannedNative?.action === 'none' && codexNativeStillDesired);
3546
+ || (plannedNative?.action === 'none' && codexNativeStillDesired)
3547
+ || options.command === 'cleanup'
3548
+ || options.command === 'uninstall';
4418
3549
  const marketplaceName = marketplaceNameFor(entry, platform, planned.resolvedScope);
4419
3550
  try {
4420
3551
  const target = await measureCommandPerformanceStage(options.runtime, 'target.build', () => integration.buildTarget({
@@ -4429,6 +3560,10 @@ async function runRebuildCommandInSession(options) {
4429
3560
  cwd: options.commandOptions.cwd,
4430
3561
  command: buildCommand,
4431
3562
  catalog: toCatalogResolution(entry.source),
3563
+ ...(platform === 'codex'
3564
+ && (buildCommand === 'cleanup' || buildCommand === 'uninstall')
3565
+ ? { includeNativeCleanupCandidate: false }
3566
+ : {}),
4432
3567
  }));
4433
3568
  if (plannedNative) {
4434
3569
  const writesSharedNative = plannedNative.action === 'install'
@@ -4459,12 +3594,9 @@ async function runRebuildCommandInSession(options) {
4459
3594
  target.codexNativeObservationExpectation = preserveSharedNative && hasExtensionMutation
4460
3595
  ? (plannedNative.action === 'reuse' ? 'installed' : 'observed')
4461
3596
  : undefined;
4462
- const removesSharedNative = !writesSharedNative
4463
- && !preserveSharedNative
4464
- && (buildCommand === 'cleanup' || buildCommand === 'uninstall');
4465
3597
  target.codexNativeSensitivePreflightRequired = preserveSharedNative
4466
3598
  && hasActivationMutation;
4467
- if (!writesSharedNative && !removesSharedNative) {
3599
+ if (!writesSharedNative) {
4468
3600
  const isSharedNativeComponent = (component) => component.kind === 'native-plugin'
4469
3601
  || (component.kind === 'shared-config'
4470
3602
  && component.identity.artifactKey.startsWith('marketplace-'));
@@ -4501,6 +3633,10 @@ async function runRebuildCommandInSession(options) {
4501
3633
  });
4502
3634
  }
4503
3635
  catch (error) {
3636
+ options.runtime?.signal?.throwIfAborted();
3637
+ if (error instanceof Error && error.name === 'AbortError'
3638
+ || readSafeOperationError(error))
3639
+ throw error;
4504
3640
  preparedTargetErrors.set(key, error);
4505
3641
  }
4506
3642
  }
@@ -4520,15 +3656,15 @@ async function runRebuildCommandInSession(options) {
4520
3656
  for (const scope of pluginConfigTargetRenderScopes(entry.plugin, {
4521
3657
  pluginName: entry.plugin.manifest.name, platform: selection.platform, scope: planned.resolvedScope,
4522
3658
  }))
4523
- configTargets.set(`${entry.pluginId}\0${scope}`, { entry, scope });
3659
+ configTargets.set(`${entry.pluginId}\0${selection.platform}\0${scope}`, { entry, scope, platform: selection.platform });
4524
3660
  for (const action of preparedTargets.get(executionKey(selection, plan)).target.operationActions ?? []) {
4525
3661
  if (action.kind === 'runPreApply' && action.pluginId === entry.pluginId) {
4526
- configTargets.set(`${entry.pluginId}\0${action.scope}`, { entry, scope: action.scope });
3662
+ configTargets.set(`${entry.pluginId}\0${selection.platform}\0${action.scope}`, { entry, scope: action.scope, platform: selection.platform });
4527
3663
  }
4528
3664
  }
4529
3665
  }
4530
3666
  }
4531
- const configDigests = await Promise.all([...configTargets.values()].map(async ({ entry, scope, }) => {
3667
+ const configDigests = await Promise.all([...configTargets.values()].map(async ({ entry, scope, platform, }) => {
4532
3668
  const effective = await resolveEffectivePluginConfig({
4533
3669
  sourceId: entry.source.sourceId,
4534
3670
  sourceIdentity: entry.source.sourceIdentity,
@@ -4537,12 +3673,14 @@ async function runRebuildCommandInSession(options) {
4537
3673
  manifest: entry.plugin.manifest,
4538
3674
  referencePlugins: options.runtime?.referencePlugins,
4539
3675
  targetScope: scope,
3676
+ platform,
4540
3677
  cwd: options.commandOptions.cwd,
4541
3678
  env: runtimeEnv,
4542
3679
  });
4543
3680
  return createOperationPlanConfigDigest({
4544
3681
  pluginId: entry.pluginId,
4545
3682
  scope,
3683
+ platform,
4546
3684
  effectiveValues: effective.values,
4547
3685
  });
4548
3686
  }));
@@ -4562,7 +3700,8 @@ async function runRebuildCommandInSession(options) {
4562
3700
  preview.preApplyAuthorization = executionSelections.flatMap((selection) => {
4563
3701
  const { entry, platform } = selection;
4564
3702
  const planned = planTargetFor(authorizationPlan, selection);
4565
- if (!planned || planned.action === 'block' || !entry.plugin.manifest.scripts?.preApply) {
3703
+ if (!planned || planned.action === 'block' || isHealthyWiderDependency(planned)
3704
+ || !entry.plugin.manifest.scripts?.preApply) {
4566
3705
  return [];
4567
3706
  }
4568
3707
  const relation = planned.relation === 'requested'
@@ -4609,15 +3748,15 @@ async function runRebuildCommandInSession(options) {
4609
3748
  const previewPreparation = await prepareRenderedTargets(initialPlan, initialCodexPlans, false);
4610
3749
  const preview = mergeRebuildCommandResults([]);
4611
3750
  preview.dependencyPlan = initialPlan;
3751
+ preview.nativeFootprints = preparedNativeFootprints([...previewPreparation.preparedTargets.values()].map((entry) => entry.target));
4612
3752
  preview.operationPlan = await withNativeToolRemovalImpact(createRebuildOperationPlan({
4613
- command: intentCommand,
4614
3753
  dependencyPlan: initialPlan,
4615
3754
  codexPlans: initialCodexPlans,
4616
3755
  targetActions: previewPreparation.targetActions,
4617
3756
  catalogSources: catalog.sources.flatMap((source) => source.resolution ? [source.resolution] : []),
4618
- intentBeforeByScope,
3757
+ requirements,
3758
+ scope: options.commandOptions.scope,
4619
3759
  configDigests: previewPreparation.configDigests,
4620
- catalogEntries: intentCatalogEntries,
4621
3760
  }), runtimeEnv);
4622
3761
  if (initialCodexPlans.length > 0)
4623
3762
  preview.codexReconcilePlans = initialCodexPlans;
@@ -4666,7 +3805,8 @@ async function runRebuildCommandInSession(options) {
4666
3805
  const preApplyRequired = lifecycleCommand && executionSelections.some(({ entry, platform }) => entry.plugin.manifest.scripts?.preApply !== undefined
4667
3806
  && lockedPlan.targets.some((target) => target.pluginId === entry.pluginId
4668
3807
  && target.platform === platform
4669
- && target.action !== 'block'));
3808
+ && target.action !== 'block'
3809
+ && !isHealthyWiderDependency(target)));
4670
3810
  if (preApplyRequired
4671
3811
  && options.commandOptions.yes !== true
4672
3812
  && options.runtime?.preApplyConfirmed !== true) {
@@ -4679,14 +3819,13 @@ async function runRebuildCommandInSession(options) {
4679
3819
  let lockedCodexPlans = provisionalLockedCodexPlans;
4680
3820
  let lockedPreparation = await prepareRenderedTargets(lockedPlan, lockedCodexPlans, true);
4681
3821
  let lockedOperationPlan = await withNativeToolRemovalImpact(createRebuildOperationPlan({
4682
- command: intentCommand,
4683
3822
  dependencyPlan: lockedPlan,
4684
3823
  codexPlans: lockedCodexPlans,
4685
3824
  targetActions: lockedPreparation.targetActions,
4686
3825
  catalogSources: catalog.sources.flatMap((source) => source.resolution ? [source.resolution] : []),
4687
- intentBeforeByScope,
3826
+ requirements,
3827
+ scope: options.commandOptions.scope,
4688
3828
  configDigests: lockedPreparation.configDigests,
4689
- catalogEntries: intentCatalogEntries,
4690
3829
  }), runtimeEnv);
4691
3830
  if (options.runtime?.expectedPlanFingerprint) {
4692
3831
  try {
@@ -4708,19 +3847,13 @@ async function runRebuildCommandInSession(options) {
4708
3847
  const operationPreparations = new Map();
4709
3848
  const operationPreparationFailures = new Map();
4710
3849
  const executionCommandFor = (selection) => {
4711
- const { entry, platform } = selection;
4712
3850
  const planned = planTargetFor(lockedPlan, selection);
4713
- const desiredInCurrentScope = platform === 'codex'
4714
- && lockedCodexPlans.some((codexPlan) => codexPlan.desiredTargets.some((target) => target.pluginId === entry.pluginId && target.scopes.includes(planned.resolvedScope)));
4715
- if (platform === 'codex' && options.command === 'sync' && !desiredInCurrentScope) {
4716
- return 'cleanup';
4717
- }
4718
- if (platform === 'codex' && options.command === 'cleanup' && desiredInCurrentScope) {
3851
+ if (isNativeActivationOnlyTarget(executionContext, {
3852
+ pluginId: selection.entry.pluginId,
3853
+ platform: selection.platform,
3854
+ scope: planned.resolvedScope,
3855
+ }))
4719
3856
  return 'install';
4720
- }
4721
- if (planned.action === 'move') {
4722
- return planned.state === 'partial' ? 'reinstall' : 'install';
4723
- }
4724
3857
  if (planned.relation === 'dependency'
4725
3858
  && (planned.action === 'install' || planned.action === 'reinstall')) {
4726
3859
  return planned.action;
@@ -4739,9 +3872,10 @@ async function runRebuildCommandInSession(options) {
4739
3872
  for (const selection of executionSelections) {
4740
3873
  const { entry, platform } = selection;
4741
3874
  const key = executionKey(selection, lockedPlan);
3875
+ const planned = planTargetFor(lockedPlan, selection);
4742
3876
  if (preparationOptions.eligibleKeys
4743
3877
  && !preparationOptions.eligibleKeys.has(key)) {
4744
- states.set(key, 'skipped');
3878
+ states.set(key, isHealthyWiderDependency(planned) ? 'ready' : 'skipped');
4745
3879
  continue;
4746
3880
  }
4747
3881
  const directDependencyFailed = (entry.plugin.manifest.dependencies ?? []).some((pluginName) => {
@@ -4754,11 +3888,14 @@ async function runRebuildCommandInSession(options) {
4754
3888
  const state = dependency ? states.get(planExecutionKey(dependency)) : undefined;
4755
3889
  return state === 'failed' || state === 'skipped';
4756
3890
  });
4757
- const planned = planTargetFor(lockedPlan, selection);
4758
3891
  if (directDependencyFailed || planned.action === 'block') {
4759
3892
  states.set(key, 'skipped');
4760
3893
  continue;
4761
3894
  }
3895
+ if (isHealthyWiderDependency(planned)) {
3896
+ states.set(key, 'ready');
3897
+ continue;
3898
+ }
4762
3899
  const preparationError = lockedPreparation.preparedTargetErrors.get(key);
4763
3900
  const prepared = lockedPreparation.preparedTargets.get(key);
4764
3901
  if (preparationError || !prepared) {
@@ -4943,14 +4080,13 @@ async function runRebuildCommandInSession(options) {
4943
4080
  lockedPreparation.integration.setNativeMarketplaceEligibleTargets(eligibleTargets);
4944
4081
  }
4945
4082
  lockedOperationPlan = await withNativeToolRemovalImpact(createRebuildOperationPlan({
4946
- command: intentCommand,
4947
4083
  dependencyPlan: lockedPlan,
4948
4084
  codexPlans: lockedCodexPlans,
4949
4085
  targetActions: lockedPreparation.targetActions,
4950
4086
  catalogSources: catalog.sources.flatMap((source) => source.resolution ? [source.resolution] : []),
4951
- intentBeforeByScope,
4087
+ requirements,
4088
+ scope: options.commandOptions.scope,
4952
4089
  configDigests: lockedPreparation.configDigests,
4953
- catalogEntries: intentCatalogEntries,
4954
4090
  }), runtimeEnv);
4955
4091
  if (options.runtime?.expectedPlanFingerprint) {
4956
4092
  assertOperationPlanFingerprint(options.runtime.expectedPlanFingerprint, lockedOperationPlan);
@@ -4962,7 +4098,9 @@ async function runRebuildCommandInSession(options) {
4962
4098
  && lockedCodexPlans.some((plan) => plan.review.requiresConfirmation)) {
4963
4099
  throw namespacedPreflightError('sharedNativeImpactAuthorizationRequired: Replacing or force-reinstalling shared Codex native state requires --yes.', preflightTargets);
4964
4100
  }
4965
- if (!executionContext.codexManager) {
4101
+ if (options.command !== 'cleanup'
4102
+ && options.command !== 'uninstall'
4103
+ && !executionContext.codexManager) {
4966
4104
  const removalTargets = [];
4967
4105
  for (const [key, prepared] of lockedPreparation.preparedTargets) {
4968
4106
  const target = prepared.target;
@@ -5027,47 +4165,6 @@ async function runRebuildCommandInSession(options) {
5027
4165
  }
5028
4166
  }
5029
4167
  }
5030
- if (options.runtime?.awHomeMutationToken) {
5031
- if (lifecycleCommand) {
5032
- const preparedKeys = new Set(operationPreparations.keys());
5033
- for (const scope of ['user', 'project']) {
5034
- const intentSelection = intentPlanForPreparedRoots(lockedPlan, preparedKeys, scope);
5035
- if (intentSelection.selectedRoots.length > 0) {
5036
- await writeDesiredIntentSuperset({
5037
- command: intentCommand,
5038
- removeOtherScopeRoots: !options.taskTargets,
5039
- cwd: options.commandOptions.cwd,
5040
- scope,
5041
- selectedRoots: intentSelection.selectedRoots,
5042
- plan: intentSelection.plan,
5043
- codexPlan: lockedCodexPlans[0],
5044
- preserveOtherCodexScopeRootIds,
5045
- catalogEntries: intentCatalogEntries,
5046
- env: runtimeEnv,
5047
- });
5048
- }
5049
- }
5050
- }
5051
- else {
5052
- for (const scope of ['user', 'project']) {
5053
- const selectedRoots = selectedRootsForScope(lockedPlan, scope);
5054
- if (selectedRoots.length === 0)
5055
- continue;
5056
- await writeDesiredIntentSuperset({
5057
- command: intentCommand,
5058
- removeOtherScopeRoots: !options.taskTargets,
5059
- cwd: options.commandOptions.cwd,
5060
- scope,
5061
- selectedRoots,
5062
- plan: lockedPlan,
5063
- codexPlan: lockedCodexPlans[0],
5064
- preserveOtherCodexScopeRootIds,
5065
- catalogEntries: intentCatalogEntries,
5066
- env: runtimeEnv,
5067
- });
5068
- }
5069
- }
5070
- }
5071
4168
  for (const pluginName of new Set(lockedPlan.targets.flatMap((target) => target.platform === 'claude'
5072
4169
  && (target.action === 'install'
5073
4170
  || target.action === 'reinstall')
@@ -5082,6 +4179,11 @@ async function runRebuildCommandInSession(options) {
5082
4179
  const claudeCliRequired = await operationNeedsNativeCli('claude', lockedPlan);
5083
4180
  const codexUserActivationDesired = new Set(lockedCodexPlans.flatMap((plan) => plan.activationActions.user.flatMap((action) => action.desired ? [action.codexPluginId] : [])));
5084
4181
  executionContext.codexUserActivationDesired = codexUserActivationDesired;
4182
+ const originalUserActivations = options.commandOptions.scope === 'project'
4183
+ && lockedCodexPlans.length > 0
4184
+ ? new Map(Object.entries((await readCodexConfigFile(resolveCodexPaths(options.commandOptions.cwd, 'user', runtimeEnv).configTomlPath)).snapshot.plugins)
4185
+ .map(([pluginId, activation]) => [pluginId, activation.enabled === true]))
4186
+ : undefined;
5085
4187
  await assertClaudeVersion({
5086
4188
  platforms: claudeCliRequired ? ['claude'] : [],
5087
4189
  cwd: options.commandOptions.cwd,
@@ -5097,6 +4199,7 @@ async function runRebuildCommandInSession(options) {
5097
4199
  const removalStateInventory = await readInventory({
5098
4200
  cwd: options.commandOptions.cwd,
5099
4201
  env: runtimeEnv,
4202
+ scope: options.commandOptions.scope,
5100
4203
  });
5101
4204
  if (options.command !== 'cleanup') {
5102
4205
  const checked = options.command === 'uninstall'
@@ -5225,6 +4328,14 @@ async function runRebuildCommandInSession(options) {
5225
4328
  }
5226
4329
  const results = [];
5227
4330
  const dependencyStates = new Map();
4331
+ const nativeTargetExecution = lifecycleCommand ? options.runtime?.nativeTargetExecution : undefined;
4332
+ const recordTargetCommands = (commands) => {
4333
+ executionContext.commandResults ??= [];
4334
+ for (const result of commands) {
4335
+ if (!executionContext.commandResults.some((entry) => JSON.stringify(entry) === JSON.stringify(result)))
4336
+ executionContext.commandResults.push(result);
4337
+ }
4338
+ };
5228
4339
  for (const selection of executionSelections) {
5229
4340
  const { entry, platform } = selection;
5230
4341
  const planned = planTargetFor(lockedPlan, selection);
@@ -5256,18 +4367,6 @@ async function runRebuildCommandInSession(options) {
5256
4367
  const codexReconcilePlan = platform === 'codex'
5257
4368
  ? lockedCodexPlans.find((plan) => plan.desiredTargets.some((target) => target.pluginId === entry.pluginId))
5258
4369
  : undefined;
5259
- const desiredInCurrentScope = codexReconcilePlan?.desiredTargets.some((target) => target.pluginId === entry.pluginId && target.scopes.includes(planned.resolvedScope)) === true;
5260
- const reconcileObsolete = platform === 'codex'
5261
- && options.command === 'sync'
5262
- && !desiredInCurrentScope;
5263
- const desiredCleanupMutation = codexReconcilePlan !== undefined && (codexReconcilePlan.sharedNativeActions.some((action) => action.pluginId === entry.pluginId
5264
- && (action.action === 'install'
5265
- || action.action === 'replace'
5266
- || action.action === 'repair'))
5267
- || [...codexReconcilePlan.activationActions.user,
5268
- ...codexReconcilePlan.activationActions.project].some((action) => action.pluginId === entry.pluginId && action.action !== 'none')
5269
- || [...codexReconcilePlan.extensionWrites,
5270
- ...codexReconcilePlan.extensionRemovals].some((action) => action.pluginId === entry.pluginId));
5271
4370
  const plannedNativeAction = codexReconcilePlan?.sharedNativeActions.find((action) => action.pluginId === entry.pluginId);
5272
4371
  const hasCodexExtensionMutation = codexReconcilePlan !== undefined
5273
4372
  && [...codexReconcilePlan.extensionWrites, ...codexReconcilePlan.extensionRemovals]
@@ -5282,17 +4381,20 @@ async function runRebuildCommandInSession(options) {
5282
4381
  const code = failedConsumer
5283
4382
  ? 'dependencyStillRequired'
5284
4383
  : failedDependency ? 'dependencyInstallFailed' : planned.errorCode ?? 'dependencyNotReady';
4384
+ recordTargetCommands(nativeTargetExecution?.blocked?.(identity, code) ?? []);
5285
4385
  const message = `${code}: ${failedConsumer
5286
4386
  ? `${entry.pluginId} retained because consumer ${failedConsumer.split('\0', 1)[0]} was not removed.`
5287
4387
  : failedDependency
5288
4388
  ? `${entry.pluginId} requires failed dependency ${failedDependency}.`
5289
4389
  : code === 'exclusiveOwnerEvidenceInvalid'
5290
- ? `${entry.pluginId} has damaged or missing exclusive owner evidence. Run aw cleanup ${entry.pluginId} --platform ${platform} --scope ${planned.resolvedScope} --force, then run aw reinstall ${entry.pluginId} --platform ${platform} --scope ${planned.resolvedScope}.`
4390
+ ? `${entry.pluginId} has damaged or missing exclusive owner evidence. Run aw plugin cleanup ${entry.pluginId} --target ${platform} --scope ${planned.resolvedScope} --force, then run aw plugin sync ${entry.pluginId} --target ${platform} --context ${planned.resolvedScope} --rebuild.`
5291
4391
  : code === 'artifactCollision'
5292
4392
  ? `${entry.pluginId} has foreign-owned output. Remove or relocate it, then run cleanup and reinstall.`
5293
4393
  : code === 'dependencyNotReady'
5294
4394
  ? `${entry.pluginId} live state is unreadable. Run cleanup, then reinstall.`
5295
- : `${entry.pluginId} dependency preflight is blocked.`}`;
4395
+ : code === 'dependencyStillRequired'
4396
+ ? `${entry.pluginId} remains required by an unselected declared root.`
4397
+ : `${entry.pluginId} dependency preflight is blocked.`}`;
5296
4398
  const result = failedResult(requestedMethod, identity, message);
5297
4399
  results.push({
5298
4400
  platforms: [platform], plugins: [], warnings: [], rebuildResults: [result],
@@ -5304,8 +4406,8 @@ async function runRebuildCommandInSession(options) {
5304
4406
  message,
5305
4407
  ...(code === 'exclusiveOwnerEvidenceInvalid' ? {
5306
4408
  recoveryActions: [
5307
- `run aw cleanup ${entry.pluginId} --platform ${platform} --scope ${planned.resolvedScope} --force`,
5308
- `then run aw reinstall ${entry.pluginId} --platform ${platform} --scope ${planned.resolvedScope}`,
4409
+ `run aw plugin cleanup ${entry.pluginId} --target ${platform} --scope ${planned.resolvedScope} --force`,
4410
+ `then run aw plugin sync ${entry.pluginId} --target ${platform} --context ${planned.resolvedScope} --rebuild`,
5309
4411
  ],
5310
4412
  } : {}),
5311
4413
  }],
@@ -5318,9 +4420,23 @@ async function runRebuildCommandInSession(options) {
5318
4420
  dependencyStates.set(key, failedDependency || failedConsumer ? 'skipped' : 'failed');
5319
4421
  continue;
5320
4422
  }
4423
+ if (isHealthyWiderDependency(planned)) {
4424
+ results.push({
4425
+ platforms: [platform], plugins: [identity.pluginName], warnings: [], rebuildResults: [],
4426
+ targetResults: [{ target: identity, action: 'keep', state: 'completed' }],
4427
+ overallState: 'succeeded',
4428
+ receiptTargets: [{
4429
+ pluginName: identity.pluginName, pluginId: identity.pluginId,
4430
+ sourceId: identity.sourceId, platform, scope: planned.resolvedScope,
4431
+ }],
4432
+ });
4433
+ dependencyStates.set(key, 'kept');
4434
+ continue;
4435
+ }
5321
4436
  const operationPreparationFailure = operationPreparationFailures.get(key);
5322
4437
  if (operationPreparationFailure) {
5323
4438
  const targetResult = toTargetResult(operationPreparationFailure);
4439
+ recordTargetCommands(nativeTargetExecution?.blocked?.(identity, targetResult.errorCode ?? 'targetPreparationFailed') ?? []);
5324
4440
  results.push({
5325
4441
  platforms: [platform],
5326
4442
  plugins: [],
@@ -5339,6 +4455,37 @@ async function runRebuildCommandInSession(options) {
5339
4455
  dependencyStates.set(key, 'failed');
5340
4456
  continue;
5341
4457
  }
4458
+ if (nativeTargetExecution) {
4459
+ let packageFailure;
4460
+ try {
4461
+ const commands = await nativeTargetExecution.beforeApply(identity);
4462
+ recordTargetCommands(commands);
4463
+ if (commands.some((result) => result.state !== 'completed')) {
4464
+ packageFailure = 'commandPackageProviderNotSatisfied';
4465
+ }
4466
+ }
4467
+ catch (error) {
4468
+ executionContext.signal?.throwIfAborted();
4469
+ if (error instanceof Error && error.name === 'AbortError')
4470
+ throw error;
4471
+ packageFailure = 'commandPackageApplyFailed';
4472
+ recordTargetCommands(nativeTargetExecution.blocked?.(identity, packageFailure) ?? []);
4473
+ }
4474
+ if (packageFailure) {
4475
+ const result = failedResult(requestedMethod, identity, `${packageFailure}: ${entry.pluginId} requires a completed command package provider.`);
4476
+ results.push({
4477
+ platforms: [platform], plugins: [], warnings: [], rebuildResults: [result],
4478
+ targetResults: [{ ...toTargetResult(result), action: planned.action }],
4479
+ overallState: 'failed',
4480
+ receiptTargets: [{
4481
+ pluginName: identity.pluginName, pluginId: identity.pluginId,
4482
+ sourceId: identity.sourceId, platform, scope: planned.resolvedScope,
4483
+ }],
4484
+ });
4485
+ dependencyStates.set(key, 'failed');
4486
+ continue;
4487
+ }
4488
+ }
5342
4489
  if (reconcileActivationOnly) {
5343
4490
  const preparedError = preparedTargetErrors.get(key);
5344
4491
  if (preparedError) {
@@ -5368,11 +4515,10 @@ async function runRebuildCommandInSession(options) {
5368
4515
  ?? [];
5369
4516
  const preparedTarget = preparedTargets.get(key)?.target;
5370
4517
  let result;
5371
- if (preparedTarget) {
4518
+ if (preparedTarget && executionContext.preserveLegacyNativeSkills !== true) {
5372
4519
  const nativeSkillKeys = new Set((entry.plugin.manifest.artifacts?.targets ?? [])
5373
4520
  .filter((target) => target.platform === 'codex'
5374
- && target.target === 'codex.skill'
5375
- && target.delivery?.mode === 'codex-native')
4521
+ && target.target === 'codex.skill')
5376
4522
  .map((target) => target.skillKey));
5377
4523
  const marketplaceRoot = preparedTarget.identity.marketplaceRoot;
5378
4524
  const legacyNativeSkills = preparedTarget.ownedComponents.filter((component) => component.kind === 'skill-directory'
@@ -5405,7 +4551,14 @@ async function runRebuildCommandInSession(options) {
5405
4551
  plugins: result.status === 'succeeded' ? [identity.pluginName] : [],
5406
4552
  warnings: result.failures.map((failure) => failure.message),
5407
4553
  rebuildResults: [result],
5408
- targetResults: [{ ...toTargetResult(result), action: planned.action }],
4554
+ targetResults: [{
4555
+ ...toTargetResult(result),
4556
+ action: result.status === 'succeeded' && result.removed.length === 0
4557
+ && codexReconcilePlan
4558
+ && [...codexReconcilePlan.activationActions.user,
4559
+ ...codexReconcilePlan.activationActions.project].every((action) => action.pluginId !== entry.pluginId || action.action === 'none')
4560
+ ? 'keep' : planned.action,
4561
+ }],
5409
4562
  overallState: result.status === 'succeeded' ? 'succeeded' : 'failed',
5410
4563
  receiptTargets: [{
5411
4564
  pluginName: identity.pluginName, pluginId: identity.pluginId,
@@ -5415,28 +4568,8 @@ async function runRebuildCommandInSession(options) {
5415
4568
  dependencyStates.set(key, result.status === 'succeeded' ? 'completed' : 'failed');
5416
4569
  continue;
5417
4570
  }
5418
- if (platform === 'codex'
5419
- && desiredInCurrentScope
5420
- && (options.command === 'uninstall'
5421
- || (options.command === 'cleanup' && !desiredCleanupMutation))) {
5422
- const result = {
5423
- status: 'succeeded', command: options.command, target: identity, actions: [],
5424
- cleanupAttempted: false, removed: [], absent: [], residual: [], quarantined: [], failures: [],
5425
- };
5426
- results.push({
5427
- platforms: [platform], plugins: [identity.pluginName], warnings: [], rebuildResults: [result],
5428
- targetResults: [{ target: identity, action: 'keep', state: 'completed' }],
5429
- overallState: 'succeeded',
5430
- receiptTargets: [{
5431
- pluginName: identity.pluginName, pluginId: identity.pluginId,
5432
- sourceId: identity.sourceId, platform, scope: planned.resolvedScope,
5433
- }],
5434
- });
5435
- dependencyStates.set(key, 'kept');
5436
- continue;
5437
- }
5438
4571
  if (planned.action === 'keep'
5439
- && !reconcileObsolete
4572
+ && !(preparedTargets.get(key)?.target.writeNativeRequired ?? false)
5440
4573
  && options.command !== 'cleanup'
5441
4574
  && options.command !== 'uninstall') {
5442
4575
  const preparedError = preparedTargetErrors.get(key);
@@ -5491,20 +4624,12 @@ async function runRebuildCommandInSession(options) {
5491
4624
  marketplaceRoot: path.join(resolveAwHome(runtimeEnv), 'marketplaces', entry.source.sourceId),
5492
4625
  };
5493
4626
  entry.plugin.marketplaceName = marketplaceName;
5494
- const reconcileDesiredCleanup = platform === 'codex'
5495
- && options.command === 'cleanup'
5496
- && desiredInCurrentScope;
5497
4627
  const executionResult = await runSingleSourceRebuildCommand({
5498
4628
  ...options,
5499
- command: reconcileObsolete
5500
- ? 'cleanup'
5501
- : reconcileDesiredCleanup
5502
- ? 'install'
5503
- : planned.action === 'move'
5504
- ? planned.state === 'partial' ? 'reinstall' : 'install'
5505
- : planned.relation === 'dependency'
5506
- ? planned.action
5507
- : requestedMethod,
4629
+ command: isNativeActivationOnlyTarget(executionContext, identity) ? 'install'
4630
+ : planned.relation === 'dependency' && planned.action !== 'keep'
4631
+ ? planned.action
4632
+ : requestedMethod,
5508
4633
  pluginNames: [entry.plugin.manifest.name],
5509
4634
  all: false,
5510
4635
  commandOptions: { ...options.commandOptions, platform, scope: planned.resolvedScope },
@@ -5543,15 +4668,8 @@ async function runRebuildCommandInSession(options) {
5543
4668
  operationPreparation: operationPreparations.get(key),
5544
4669
  integration: lockedPreparation.integration,
5545
4670
  preparationError: preparedTargetErrors.get(key),
5546
- skipPreApply: reconcileDesiredCleanup,
5547
4671
  },
5548
4672
  });
5549
- if (reconcileDesiredCleanup) {
5550
- for (const result of executionResult.rebuildResults)
5551
- result.command = 'cleanup';
5552
- for (const result of executionResult.targetResults)
5553
- result.action = 'cleanup';
5554
- }
5555
4673
  results.push(executionResult);
5556
4674
  dependencyStates.set(key, executionResult.overallState === 'succeeded' ? 'completed' : 'failed');
5557
4675
  }
@@ -5634,17 +4752,17 @@ async function runRebuildCommandInSession(options) {
5634
4752
  || dependencyStates.get(executionKey(selection, lockedPlan)) === 'kept')
5635
4753
  ? [`${selection.entry.pluginId}\0${planTargetFor(lockedPlan, selection).resolvedScope}`]
5636
4754
  : []));
5637
- const [activationUserBefore, activationProjectBefore] = await Promise.all([
5638
- readCodexConfigFile(resolveCodexPaths(options.commandOptions.cwd, 'user', runtimeEnv).configTomlPath),
5639
- readCodexConfigFile(resolveCodexPaths(options.commandOptions.cwd, 'project', runtimeEnv).configTomlPath),
5640
- ]);
4755
+ const activationUserBefore = await readCodexConfigFile(resolveCodexPaths(options.commandOptions.cwd, 'user', runtimeEnv).configTomlPath);
4756
+ const activationProjectBefore = options.commandOptions.scope === 'project'
4757
+ ? await readCodexConfigFile(resolveCodexPaths(options.commandOptions.cwd, 'project', runtimeEnv).configTomlPath)
4758
+ : undefined;
5641
4759
  const preservedActivations = {
5642
4760
  user: new Map(Object.entries(activationUserBefore.snapshot.plugins)
5643
4761
  .map(([pluginId, activation]) => [pluginId, activation.enabled === true])),
5644
- project: new Map(Object.entries(activationProjectBefore.snapshot.plugins)
4762
+ project: new Map(Object.entries(activationProjectBefore?.snapshot.plugins ?? {})
5645
4763
  .map(([pluginId, activation]) => [pluginId, activation.enabled === true])),
5646
4764
  };
5647
- const activationPlans = selectCodexActivationPlans(lockedCodexPlans, successfulCodexTargetScopes, preservedActivations);
4765
+ const activationPlans = selectCodexActivationPlans(lockedCodexPlans, successfulCodexTargetScopes, preservedActivations, originalUserActivations);
5648
4766
  if (activationPlans.length > 0
5649
4767
  || lockedPreparation.integration.driver.completePostchecks) {
5650
4768
  let activationError;
@@ -5691,7 +4809,7 @@ async function runRebuildCommandInSession(options) {
5691
4809
  if (!activationError && activationPlans.length > 0) {
5692
4810
  const postcheckTargetScopes = new Set([...successfulCodexTargetScopes]
5693
4811
  .filter((key) => !failedCodexTargetScopes.has(key)));
5694
- const postcheckPlans = selectCodexActivationPlans(activationPlans, postcheckTargetScopes, preservedActivations);
4812
+ const postcheckPlans = selectCodexActivationPlans(activationPlans, postcheckTargetScopes, preservedActivations, originalUserActivations);
5695
4813
  try {
5696
4814
  await postcheckCodexReconcileActivations({
5697
4815
  plans: postcheckPlans,
@@ -5773,7 +4891,7 @@ async function runRebuildCommandInSession(options) {
5773
4891
  identity,
5774
4892
  message,
5775
4893
  recoveryActions: [
5776
- `run aw sync --all --platform codex --scope ${planned.resolvedScope}`,
4894
+ `run aw plugin sync ${selection.entry.pluginId} --target codex --context ${planned.resolvedScope}`,
5777
4895
  ],
5778
4896
  })) {
5779
4897
  failedCodexTargetScopes.add(targetScopeKey);
@@ -5783,78 +4901,6 @@ async function runRebuildCommandInSession(options) {
5783
4901
  }
5784
4902
  }
5785
4903
  }
5786
- for (const result of results) {
5787
- result.targetResults = result.targetResults.map((target) => {
5788
- if (target.state !== 'completed')
5789
- return target;
5790
- const planned = lockedPlan.targets.find((candidate) => candidate.pluginId === target.target.pluginId
5791
- && candidate.platform === target.target.platform
5792
- && candidate.resolvedScope === target.target.scope);
5793
- if (!planned?.preservedScope)
5794
- return target;
5795
- return {
5796
- ...target,
5797
- recoveryActions: [
5798
- `remove every direct and indirect dependent of ${planned.pluginId} in consumer-first order; if dependencyStillRequired is reported, handle the other reported dependents first`,
5799
- `then run aw cleanup ${planned.pluginId} --platform ${planned.platform} --scope ${planned.preservedScope}`,
5800
- `then reinstall every removed root at its original platform and scope, selecting --dependency-scope ${planned.pluginId}#${planned.platform}=${planned.resolvedScope} during each install`,
5801
- ],
5802
- };
5803
- });
5804
- }
5805
- if (options.command === 'install' || options.command === 'sync') {
5806
- for (const selection of executionSelections) {
5807
- const { entry, platform } = selection;
5808
- const planned = planTargetFor(lockedPlan, selection);
5809
- const key = executionKey(selection, lockedPlan);
5810
- if (dependencyStates.get(key) !== 'completed')
5811
- continue;
5812
- const previousScope = planned.resolvedScope === 'user' ? 'project' : 'user';
5813
- const removesPreviousScope = planned.resolvedScope === 'user'
5814
- && (planned.action === 'move'
5815
- || platform === 'codex' && lockedCodexPlans.some((plan) => plan.extensionRemovals.some((action) => action.pluginId === entry.pluginId && action.scope === previousScope)));
5816
- if (!removesPreviousScope) {
5817
- continue;
5818
- }
5819
- const marketplaceName = marketplaceNameFor(entry, platform, previousScope);
5820
- entry.plugin.marketplaceName = marketplaceName;
5821
- const migration = await runSingleSourceRebuildCommand({
5822
- command: 'cleanup',
5823
- pluginNames: [entry.plugin.manifest.name],
5824
- commandOptions: { ...options.commandOptions, platform, scope: previousScope },
5825
- runtime: {
5826
- ...options.runtime,
5827
- env: runtimeEnv,
5828
- ...(executionContext.codexManager
5829
- ? { codexManager: executionContext.codexManager }
5830
- : {}),
5831
- catalogResolution: toCatalogResolution(entry.source),
5832
- resolvedCatalogPlugins: available
5833
- .filter((candidate) => candidate.source.sourceId === entry.source.sourceId)
5834
- .map((candidate) => candidate.plugin),
5835
- namespacedSource: {
5836
- sourceId: entry.source.sourceId,
5837
- sourceIdentity: entry.source.sourceIdentity,
5838
- logicalIdentity: entry.source.logicalIdentity,
5839
- marketplaceName,
5840
- marketplaceRoot: path.join(resolveAwHome(runtimeEnv), 'marketplaces', entry.source.sourceId),
5841
- },
5842
- },
5843
- execution: { reviewCompleted: true },
5844
- });
5845
- results.push({
5846
- ...migration,
5847
- targetResults: migration.targetResults.map((target) => ({
5848
- ...target,
5849
- relation: planned.relation,
5850
- requiredBy: planned.requiredBy,
5851
- })),
5852
- });
5853
- if (migration.overallState !== 'succeeded') {
5854
- dependencyStates.set(key, 'failed');
5855
- }
5856
- }
5857
- }
5858
4904
  const merged = mergeRebuildCommandResults(results);
5859
4905
  if (malformedSelectedConfigPreserved) {
5860
4906
  merged.warnings.push(MALFORMED_CONFIG_PRESERVED_WARNING);
@@ -5864,6 +4910,7 @@ async function runRebuildCommandInSession(options) {
5864
4910
  if (lockedCodexPlans.length > 0)
5865
4911
  merged.codexReconcilePlans = lockedCodexPlans;
5866
4912
  merged.operationPlan = lockedOperationPlan;
4913
+ merged.nativeFootprints = preparedNativeFootprints([...lockedPreparation.preparedTargets.values()].map((entry) => entry.target));
5867
4914
  return leaseSafeRebuildResult(merged);
5868
4915
  },
5869
4916
  });
@@ -5899,6 +4946,10 @@ async function runRebuildCommandInSession(options) {
5899
4946
  }
5900
4947
  }
5901
4948
  export async function runRebuildCommand(options) {
4949
+ const executionInput = options.runtime?.executionInput;
4950
+ if (!executionInput) {
4951
+ throw new Error('nativeExecutionInputRequired: Native execution requires an explicit scope context and complete demand.');
4952
+ }
5902
4953
  const commandResults = options.runtime?.commandResults ?? [];
5903
4954
  options = { ...options, runtime: { ...options.runtime, commandResults, managedCommandProducerCache: options.runtime?.managedCommandProducerCache ?? new Map() } };
5904
4955
  const withCommands = (result) => {
@@ -5915,20 +4966,25 @@ export async function runRebuildCommand(options) {
5915
4966
  const failed = commands.some((entry) => entry.state !== 'completed');
5916
4967
  return { ...result, commandResults: commands, overallState: failed ? result.targetResults.some((entry) => entry.state === 'completed') || commands.some((entry) => entry.state === 'completed') ? 'partial' : 'failed' : result.overallState };
5917
4968
  };
5918
- if (options.runtime?.codexObservationSession) {
5919
- return withCommands(await runRebuildCommandInSession(options));
4969
+ const scopedOptions = {
4970
+ ...options,
4971
+ commandOptions: { ...options.commandOptions, cwd: executionInput.context.executionCwd },
4972
+ runtime: { ...options.runtime, env: executionInput.context.env },
4973
+ };
4974
+ if (scopedOptions.runtime?.codexObservationSession) {
4975
+ return withCommands(await runRebuildCommandInSession(scopedOptions));
5920
4976
  }
5921
- const env = options.runtime?.env ?? process.env;
4977
+ const env = executionInput.context.env;
5922
4978
  const session = await createCodexNativeObservationSession({
5923
- exactCwd: options.commandOptions.cwd,
4979
+ exactCwd: executionInput.context.executionCwd,
5924
4980
  env,
5925
- runner: options.runtime?.nativeCliRunner ?? getDefaultNativeCliRunner(),
5926
- manager: options.runtime?.codexManager,
4981
+ runner: scopedOptions.runtime?.nativeCliRunner ?? getDefaultNativeCliRunner(),
4982
+ manager: scopedOptions.runtime?.codexManager,
5927
4983
  });
5928
4984
  try {
5929
4985
  return withCommands(await runRebuildCommandInSession({
5930
- ...options,
5931
- runtime: { ...options.runtime, codexObservationSession: session },
4986
+ ...scopedOptions,
4987
+ runtime: { ...scopedOptions.runtime, codexObservationSession: session },
5932
4988
  }));
5933
4989
  }
5934
4990
  finally {
@@ -5982,6 +5038,12 @@ export async function runRebuildTaskCommand(targets, options, runtime = {}) {
5982
5038
  const repairTargets = deduplicate(normalizedRepairs);
5983
5039
  const commandOptions = { ...options };
5984
5040
  delete commandOptions.repairTargets;
5041
+ if (!runtime.executionInput) {
5042
+ throw new Error('nativeExecutionInputRequired: rebuild tasks require a scope context and complete demand.');
5043
+ }
5044
+ if (normalized.some((target) => target.scope !== runtime.executionInput.context.scope)) {
5045
+ throw new Error('nativeExecutionTargetScopeInvalid: rebuild tasks cannot mix scopes.');
5046
+ }
5985
5047
  return runRebuildCommand({
5986
5048
  command: 'sync',
5987
5049
  pluginNames: [...new Set(normalized.map((target) => target.pluginId))],
@@ -5990,7 +5052,13 @@ export async function runRebuildTaskCommand(targets, options, runtime = {}) {
5990
5052
  platform: 'both',
5991
5053
  scope: normalized[0].scope,
5992
5054
  },
5993
- runtime,
5055
+ runtime: {
5056
+ ...runtime,
5057
+ executionInput: {
5058
+ ...runtime.executionInput,
5059
+ selectedTargets: normalized.map(({ pluginId, platform, scope }) => ({ pluginId, platform, scope })),
5060
+ },
5061
+ },
5994
5062
  taskTargets: normalized,
5995
5063
  taskRepairTargets: repairTargets,
5996
5064
  });