@lesliechan721/aw 0.1.0-beta.7 → 0.1.0-beta.9

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 (341) hide show
  1. package/dist/authoring/scripts/add-skill.mjs +4 -4
  2. package/dist/authoring/scripts/add-skill.mjs.map +1 -1
  3. package/dist/authoring/scripts/init-plugin.mjs +8 -37
  4. package/dist/authoring/scripts/init-plugin.mjs.map +1 -1
  5. package/dist/authoring/scripts/lib/plugin-version-check.mjs +1 -1
  6. package/dist/authoring/scripts/lib/skill-scaffold.d.mts +77 -90
  7. package/dist/authoring/scripts/lib/skill-scaffold.mjs +99 -120
  8. package/dist/authoring/scripts/lib/skill-scaffold.mjs.map +1 -1
  9. package/dist/authoring/scripts/plugin-status.mjs +8 -11
  10. package/dist/authoring/scripts/plugin-status.mjs.map +1 -1
  11. package/dist/authoring/scripts/remove-plugin.mjs +2 -1
  12. package/dist/authoring/scripts/remove-plugin.mjs.map +1 -1
  13. package/dist/authoring/scripts/validate-plugin.mjs +64 -758
  14. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
  15. package/dist/scripts/aw-skill-eval-materializer.d.ts +24 -0
  16. package/dist/scripts/{plugin-eval-materializer.js → aw-skill-eval-materializer.js} +34 -145
  17. package/dist/scripts/aw-skill-eval-materializer.js.map +1 -0
  18. package/dist/scripts/bundle-plugins.d.ts +1 -0
  19. package/dist/scripts/bundle-plugins.js +2 -1
  20. package/dist/scripts/bundle-plugins.js.map +1 -1
  21. package/dist/scripts/plugin-staging.d.ts +6 -8
  22. package/dist/scripts/plugin-staging.js +15 -28
  23. package/dist/scripts/plugin-staging.js.map +1 -1
  24. package/dist/scripts/test-plugin-scripts.js +2 -33
  25. package/dist/scripts/test-plugin-scripts.js.map +1 -1
  26. package/dist/src/adapters/helpers.js +2 -2
  27. package/dist/src/adapters/helpers.js.map +1 -1
  28. package/dist/src/authoring/workspace-commands.d.ts +17 -1
  29. package/dist/src/authoring/workspace-commands.js +21 -4
  30. package/dist/src/authoring/workspace-commands.js.map +1 -1
  31. package/dist/src/commands/aw-capabilities.d.ts +78 -0
  32. package/dist/src/commands/aw-capabilities.js +254 -0
  33. package/dist/src/commands/aw-capabilities.js.map +1 -0
  34. package/dist/src/commands/catalog-dev-source.d.ts +118 -0
  35. package/dist/src/commands/catalog-dev-source.js +417 -0
  36. package/dist/src/commands/catalog-dev-source.js.map +1 -0
  37. package/dist/src/commands/catalog-generation.d.ts +13 -0
  38. package/dist/src/commands/catalog-generation.js +13 -0
  39. package/dist/src/commands/catalog-generation.js.map +1 -0
  40. package/dist/src/commands/catalog-publication-inspection.d.ts +29 -28
  41. package/dist/src/commands/catalog-publication-inspection.js +138 -50
  42. package/dist/src/commands/catalog-publication-inspection.js.map +1 -1
  43. package/dist/src/commands/catalog-publication-journal.d.ts +49 -0
  44. package/dist/src/commands/catalog-publication-journal.js +147 -0
  45. package/dist/src/commands/catalog-publication-journal.js.map +1 -0
  46. package/dist/src/commands/catalog-publication-target.d.ts +18 -0
  47. package/dist/src/commands/catalog-publication-target.js +95 -0
  48. package/dist/src/commands/catalog-publication-target.js.map +1 -0
  49. package/dist/src/commands/catalog-publish.d.ts +58 -15
  50. package/dist/src/commands/catalog-publish.js +590 -210
  51. package/dist/src/commands/catalog-publish.js.map +1 -1
  52. package/dist/src/commands/catalog-source.d.ts +12 -15
  53. package/dist/src/commands/catalog-source.js +61 -86
  54. package/dist/src/commands/catalog-source.js.map +1 -1
  55. package/dist/src/commands/catalog-status.d.ts +18 -22
  56. package/dist/src/commands/catalog-status.js +81 -115
  57. package/dist/src/commands/catalog-status.js.map +1 -1
  58. package/dist/src/commands/catalog-workspace-resolution.d.ts +13 -0
  59. package/dist/src/commands/catalog-workspace-resolution.js +42 -0
  60. package/dist/src/commands/catalog-workspace-resolution.js.map +1 -0
  61. package/dist/src/commands/dependency-operation-plan.d.ts +88 -9
  62. package/dist/src/commands/dependency-operation-plan.js +331 -6
  63. package/dist/src/commands/dependency-operation-plan.js.map +1 -1
  64. package/dist/src/commands/init.d.ts +5 -11
  65. package/dist/src/commands/init.js +30 -51
  66. package/dist/src/commands/init.js.map +1 -1
  67. package/dist/src/commands/offline-cleanup-evidence.d.ts +43 -0
  68. package/dist/src/commands/offline-cleanup-evidence.js +116 -0
  69. package/dist/src/commands/offline-cleanup-evidence.js.map +1 -0
  70. package/dist/src/commands/options.d.ts +7 -1
  71. package/dist/src/commands/options.js +65 -6
  72. package/dist/src/commands/options.js.map +1 -1
  73. package/dist/src/commands/plugin-capabilities.js +5 -1
  74. package/dist/src/commands/plugin-capabilities.js.map +1 -1
  75. package/dist/src/commands/rebuild-command.d.ts +27 -13
  76. package/dist/src/commands/rebuild-command.js +1472 -471
  77. package/dist/src/commands/rebuild-command.js.map +1 -1
  78. package/dist/src/commands/rebuild-integration.d.ts +63 -8
  79. package/dist/src/commands/rebuild-integration.js +761 -439
  80. package/dist/src/commands/rebuild-integration.js.map +1 -1
  81. package/dist/src/commands/rebuild-operations.d.ts +2 -8
  82. package/dist/src/commands/rebuild-operations.js +48 -132
  83. package/dist/src/commands/rebuild-operations.js.map +1 -1
  84. package/dist/src/commands/runtime.js +21 -34
  85. package/dist/src/commands/runtime.js.map +1 -1
  86. package/dist/src/commands/skill-eval.d.ts +33 -0
  87. package/dist/src/commands/skill-eval.js +363 -0
  88. package/dist/src/commands/skill-eval.js.map +1 -0
  89. package/dist/src/commands/status-v6.d.ts +202 -0
  90. package/dist/src/commands/status-v6.js +1938 -0
  91. package/dist/src/commands/status-v6.js.map +1 -0
  92. package/dist/src/commands/sync-command.d.ts +21 -0
  93. package/dist/src/commands/sync-command.js +206 -0
  94. package/dist/src/commands/sync-command.js.map +1 -0
  95. package/dist/src/commands/wizard.d.ts +2 -2
  96. package/dist/src/commands/wizard.js +2 -2
  97. package/dist/src/commands/wizard.js.map +1 -1
  98. package/dist/src/commands/write-commands.js +33 -19
  99. package/dist/src/commands/write-commands.js.map +1 -1
  100. package/dist/src/constants.d.ts +7 -1
  101. package/dist/src/constants.js +7 -1
  102. package/dist/src/constants.js.map +1 -1
  103. package/dist/src/eval/manifest-v1.d.ts +66 -0
  104. package/dist/src/eval/manifest-v1.js +326 -0
  105. package/dist/src/eval/manifest-v1.js.map +1 -0
  106. package/dist/src/generators/local-plugin.d.ts +3 -0
  107. package/dist/src/generators/local-plugin.js +167 -45
  108. package/dist/src/generators/local-plugin.js.map +1 -1
  109. package/dist/src/index.d.ts +6 -1
  110. package/dist/src/index.js +863 -126
  111. package/dist/src/index.js.map +1 -1
  112. package/dist/src/output/command-receipt.d.ts +58 -8
  113. package/dist/src/output/command-receipt.js +141 -11
  114. package/dist/src/output/command-receipt.js.map +1 -1
  115. package/dist/src/output/model.d.ts +3 -44
  116. package/dist/src/output/model.js +1 -144
  117. package/dist/src/output/model.js.map +1 -1
  118. package/dist/src/output/rebuild-progress.js +0 -3
  119. package/dist/src/output/rebuild-progress.js.map +1 -1
  120. package/dist/src/portable/commands.d.ts +6 -2
  121. package/dist/src/portable/commands.js +57 -6
  122. package/dist/src/portable/commands.js.map +1 -1
  123. package/dist/src/portable/materializer.d.ts +4 -4
  124. package/dist/src/portable/materializer.js +61 -12
  125. package/dist/src/portable/materializer.js.map +1 -1
  126. package/dist/src/tui/catalog-generation.d.ts +35 -0
  127. package/dist/src/tui/catalog-generation.js +85 -0
  128. package/dist/src/tui/catalog-generation.js.map +1 -0
  129. package/dist/src/tui/components.js +1 -2
  130. package/dist/src/tui/components.js.map +1 -1
  131. package/dist/src/tui/console-app-v4.d.ts +1 -1
  132. package/dist/src/tui/console-app-v4.js +647 -196
  133. package/dist/src/tui/console-app-v4.js.map +1 -1
  134. package/dist/src/tui/controller-runtime.d.ts +6 -2
  135. package/dist/src/tui/controller-runtime.js +646 -137
  136. package/dist/src/tui/controller-runtime.js.map +1 -1
  137. package/dist/src/tui/controller.d.ts +77 -15
  138. package/dist/src/tui/controller.js +647 -306
  139. package/dist/src/tui/controller.js.map +1 -1
  140. package/dist/src/tui/diagnostics.d.ts +1 -2
  141. package/dist/src/tui/diagnostics.js.map +1 -1
  142. package/dist/src/tui/index.d.ts +1 -0
  143. package/dist/src/tui/index.js +1 -0
  144. package/dist/src/tui/index.js.map +1 -1
  145. package/dist/src/tui/input-display.d.ts +8 -0
  146. package/dist/src/tui/input-display.js +36 -0
  147. package/dist/src/tui/input-display.js.map +1 -0
  148. package/dist/src/tui/model.d.ts +102 -14
  149. package/dist/src/tui/model.js +630 -139
  150. package/dist/src/tui/model.js.map +1 -1
  151. package/dist/src/tui/router.d.ts +35 -0
  152. package/dist/src/tui/router.js.map +1 -1
  153. package/dist/src/tui/source-ref-presets.d.ts +3 -0
  154. package/dist/src/tui/source-ref-presets.js +11 -0
  155. package/dist/src/tui/source-ref-presets.js.map +1 -0
  156. package/dist/src/tui/views.js +31 -3
  157. package/dist/src/tui/views.js.map +1 -1
  158. package/dist/src/types.d.ts +104 -13
  159. package/dist/src/types.js.map +1 -1
  160. package/dist/src/utils/agent-version.d.ts +7 -1
  161. package/dist/src/utils/agent-version.js +41 -8
  162. package/dist/src/utils/agent-version.js.map +1 -1
  163. package/dist/src/utils/aw-home-mutation-lock.d.ts +1 -0
  164. package/dist/src/utils/aw-home-mutation-lock.js +32 -4
  165. package/dist/src/utils/aw-home-mutation-lock.js.map +1 -1
  166. package/dist/src/utils/bundle-integrity.d.ts +0 -1
  167. package/dist/src/utils/bundle-integrity.js +0 -25
  168. package/dist/src/utils/bundle-integrity.js.map +1 -1
  169. package/dist/src/utils/bundle-layout.js +8 -6
  170. package/dist/src/utils/bundle-layout.js.map +1 -1
  171. package/dist/src/utils/catalog-digests.d.ts +7 -0
  172. package/dist/src/utils/catalog-digests.js +40 -2
  173. package/dist/src/utils/catalog-digests.js.map +1 -1
  174. package/dist/src/utils/catalog-generation-control-snapshot.d.ts +11 -0
  175. package/dist/src/utils/catalog-generation-control-snapshot.js +45 -0
  176. package/dist/src/utils/catalog-generation-control-snapshot.js.map +1 -0
  177. package/dist/src/utils/catalog-generation-error.d.ts +13 -0
  178. package/dist/src/utils/catalog-generation-error.js +28 -0
  179. package/dist/src/utils/catalog-generation-error.js.map +1 -0
  180. package/dist/src/utils/catalog-generation-resolver.d.ts +20 -0
  181. package/dist/src/utils/catalog-generation-resolver.js +143 -0
  182. package/dist/src/utils/catalog-generation-resolver.js.map +1 -0
  183. package/dist/src/utils/catalog-generation-service.d.ts +43 -0
  184. package/dist/src/utils/catalog-generation-service.js +306 -0
  185. package/dist/src/utils/catalog-generation-service.js.map +1 -0
  186. package/dist/src/utils/catalog-generation-state.d.ts +17 -0
  187. package/dist/src/utils/catalog-generation-state.js +168 -0
  188. package/dist/src/utils/catalog-generation-state.js.map +1 -0
  189. package/dist/src/utils/catalog-generation-store.d.ts +34 -0
  190. package/dist/src/utils/catalog-generation-store.js +251 -0
  191. package/dist/src/utils/catalog-generation-store.js.map +1 -0
  192. package/dist/src/utils/catalog-manifest-v6.d.ts +5 -0
  193. package/dist/src/utils/{catalog-manifest-v4.js → catalog-manifest-v6.js} +35 -43
  194. package/dist/src/utils/catalog-manifest-v6.js.map +1 -0
  195. package/dist/src/utils/catalog-operation-context.d.ts +20 -0
  196. package/dist/src/utils/catalog-operation-context.js +141 -0
  197. package/dist/src/utils/catalog-operation-context.js.map +1 -0
  198. package/dist/src/utils/catalog-public-error.d.ts +7 -3
  199. package/dist/src/utils/catalog-public-error.js +21 -8
  200. package/dist/src/utils/catalog-public-error.js.map +1 -1
  201. package/dist/src/utils/catalog-runtime-projection.d.ts +3 -0
  202. package/dist/src/utils/catalog-runtime-projection.js +5 -40
  203. package/dist/src/utils/catalog-runtime-projection.js.map +1 -1
  204. package/dist/src/utils/catalog-snapshot.d.ts +5 -3
  205. package/dist/src/utils/catalog-snapshot.js +11 -3
  206. package/dist/src/utils/catalog-snapshot.js.map +1 -1
  207. package/dist/src/utils/catalog-source-identity.d.ts +1 -0
  208. package/dist/src/utils/catalog-source-identity.js +21 -2
  209. package/dist/src/utils/catalog-source-identity.js.map +1 -1
  210. package/dist/src/utils/catalog-source-references.d.ts +7 -0
  211. package/dist/src/utils/catalog-source-references.js +44 -55
  212. package/dist/src/utils/catalog-source-references.js.map +1 -1
  213. package/dist/src/utils/catalog-sources.d.ts +1 -0
  214. package/dist/src/utils/catalog-sources.js +55 -20
  215. package/dist/src/utils/catalog-sources.js.map +1 -1
  216. package/dist/src/utils/codex-config.d.ts +2 -0
  217. package/dist/src/utils/codex-config.js +26 -5
  218. package/dist/src/utils/codex-config.js.map +1 -1
  219. package/dist/src/utils/codex-manager.d.ts +81 -0
  220. package/dist/src/utils/codex-manager.js +810 -0
  221. package/dist/src/utils/codex-manager.js.map +1 -0
  222. package/dist/src/utils/codex-reconciler.d.ts +131 -0
  223. package/dist/src/utils/codex-reconciler.js +327 -0
  224. package/dist/src/utils/codex-reconciler.js.map +1 -0
  225. package/dist/src/utils/codex-shared-native.d.ts +56 -0
  226. package/dist/src/utils/codex-shared-native.js +87 -0
  227. package/dist/src/utils/codex-shared-native.js.map +1 -0
  228. package/dist/src/utils/codex-skill-staging.d.ts +2 -0
  229. package/dist/src/utils/codex-skill-staging.js.map +1 -1
  230. package/dist/src/utils/codex-surface-analysis.d.ts +44 -0
  231. package/dist/src/utils/codex-surface-analysis.js +247 -0
  232. package/dist/src/utils/codex-surface-analysis.js.map +1 -0
  233. package/dist/src/utils/dynamic-skill-tree.d.ts +4 -0
  234. package/dist/src/utils/dynamic-skill-tree.js +35 -5
  235. package/dist/src/utils/dynamic-skill-tree.js.map +1 -1
  236. package/dist/src/utils/git-catalog-provider.js +66 -140
  237. package/dist/src/utils/git-catalog-provider.js.map +1 -1
  238. package/dist/src/utils/lifecycle-active-store.d.ts +0 -28
  239. package/dist/src/utils/lifecycle-active-store.js +35 -247
  240. package/dist/src/utils/lifecycle-active-store.js.map +1 -1
  241. package/dist/src/utils/manifest.d.ts +1 -3
  242. package/dist/src/utils/manifest.js +1 -3
  243. package/dist/src/utils/manifest.js.map +1 -1
  244. package/dist/src/utils/multi-source-catalog.d.ts +3 -9
  245. package/dist/src/utils/multi-source-catalog.js +53 -110
  246. package/dist/src/utils/multi-source-catalog.js.map +1 -1
  247. package/dist/src/utils/native-cli.d.ts +49 -6
  248. package/dist/src/utils/native-cli.js +116 -33
  249. package/dist/src/utils/native-cli.js.map +1 -1
  250. package/dist/src/utils/orphan-marketplace-cleanup.js +1 -3
  251. package/dist/src/utils/orphan-marketplace-cleanup.js.map +1 -1
  252. package/dist/src/utils/ownership.d.ts +10 -11
  253. package/dist/src/utils/ownership.js +55 -112
  254. package/dist/src/utils/ownership.js.map +1 -1
  255. package/dist/src/utils/path-catalog-provider.d.ts +1 -1
  256. package/dist/src/utils/path-catalog-provider.js +52 -65
  257. package/dist/src/utils/path-catalog-provider.js.map +1 -1
  258. package/dist/src/utils/paths.d.ts +0 -1
  259. package/dist/src/utils/paths.js +0 -1
  260. package/dist/src/utils/paths.js.map +1 -1
  261. package/dist/src/utils/plugin-config-write.js +18 -40
  262. package/dist/src/utils/plugin-config-write.js.map +1 -1
  263. package/dist/src/utils/plugin-config.d.ts +1 -12
  264. package/dist/src/utils/plugin-config.js +107 -226
  265. package/dist/src/utils/plugin-config.js.map +1 -1
  266. package/dist/src/utils/plugin-intent-state.d.ts +39 -0
  267. package/dist/src/utils/plugin-intent-state.js +325 -0
  268. package/dist/src/utils/plugin-intent-state.js.map +1 -0
  269. package/dist/src/utils/plugin-scripts.d.ts +1 -1
  270. package/dist/src/utils/plugin-scripts.js +1 -1
  271. package/dist/src/utils/plugin-scripts.js.map +1 -1
  272. package/dist/src/utils/state-paths.js +7 -2
  273. package/dist/src/utils/state-paths.js.map +1 -1
  274. package/dist/src/utils/target-eligibility.d.ts +8 -2
  275. package/dist/src/utils/target-eligibility.js +9 -5
  276. package/dist/src/utils/target-eligibility.js.map +1 -1
  277. package/package.json +6 -4
  278. package/dist/authoring/scripts/check-plugin-versions.d.mts +0 -2
  279. package/dist/authoring/scripts/check-plugin-versions.mjs +0 -5
  280. package/dist/authoring/scripts/check-plugin-versions.mjs.map +0 -1
  281. package/dist/scripts/aw-skill-eval-engine.d.ts +0 -13
  282. package/dist/scripts/aw-skill-eval-engine.js +0 -31
  283. package/dist/scripts/aw-skill-eval-engine.js.map +0 -1
  284. package/dist/scripts/codex-eval-app-server.d.ts +0 -89
  285. package/dist/scripts/codex-eval-app-server.js +0 -1228
  286. package/dist/scripts/codex-eval-app-server.js.map +0 -1
  287. package/dist/scripts/codex-eval-isolation.d.ts +0 -61
  288. package/dist/scripts/codex-eval-isolation.js +0 -318
  289. package/dist/scripts/codex-eval-isolation.js.map +0 -1
  290. package/dist/scripts/codex-eval-process-management.d.ts +0 -8
  291. package/dist/scripts/codex-eval-process-management.js +0 -69
  292. package/dist/scripts/codex-eval-process-management.js.map +0 -1
  293. package/dist/scripts/codex-eval-process-tree.d.ts +0 -38
  294. package/dist/scripts/codex-eval-process-tree.js +0 -803
  295. package/dist/scripts/codex-eval-process-tree.js.map +0 -1
  296. package/dist/scripts/codex-eval-runtime-config.d.ts +0 -62
  297. package/dist/scripts/codex-eval-runtime-config.js +0 -843
  298. package/dist/scripts/codex-eval-runtime-config.js.map +0 -1
  299. package/dist/scripts/codex-trigger-eval.d.ts +0 -84
  300. package/dist/scripts/codex-trigger-eval.js +0 -968
  301. package/dist/scripts/codex-trigger-eval.js.map +0 -1
  302. package/dist/scripts/eval-plugin-skills-audited.d.ts +0 -6
  303. package/dist/scripts/eval-plugin-skills-audited.js +0 -261
  304. package/dist/scripts/eval-plugin-skills-audited.js.map +0 -1
  305. package/dist/scripts/eval-plugin-skills.d.ts +0 -45
  306. package/dist/scripts/eval-plugin-skills.js +0 -1586
  307. package/dist/scripts/eval-plugin-skills.js.map +0 -1
  308. package/dist/scripts/eval-run-layout.d.ts +0 -17
  309. package/dist/scripts/eval-run-layout.js +0 -26
  310. package/dist/scripts/eval-run-layout.js.map +0 -1
  311. package/dist/scripts/plugin-eval-contract.d.ts +0 -185
  312. package/dist/scripts/plugin-eval-contract.js +0 -695
  313. package/dist/scripts/plugin-eval-contract.js.map +0 -1
  314. package/dist/scripts/plugin-eval-materializer.d.ts +0 -13
  315. package/dist/scripts/plugin-eval-materializer.js.map +0 -1
  316. package/dist/scripts/skill-reporting.d.ts +0 -212
  317. package/dist/scripts/skill-reporting.js +0 -922
  318. package/dist/scripts/skill-reporting.js.map +0 -1
  319. package/dist/src/commands/status-v4.d.ts +0 -123
  320. package/dist/src/commands/status-v4.js +0 -905
  321. package/dist/src/commands/status-v4.js.map +0 -1
  322. package/dist/src/commands/status.d.ts +0 -93
  323. package/dist/src/commands/status.js +0 -442
  324. package/dist/src/commands/status.js.map +0 -1
  325. package/dist/src/utils/active-marketplace-runtime-lock.d.ts +0 -12
  326. package/dist/src/utils/active-marketplace-runtime-lock.js +0 -133
  327. package/dist/src/utils/active-marketplace-runtime-lock.js.map +0 -1
  328. package/dist/src/utils/catalog-manifest-v3.d.ts +0 -2
  329. package/dist/src/utils/catalog-manifest-v3.js +0 -3
  330. package/dist/src/utils/catalog-manifest-v3.js.map +0 -1
  331. package/dist/src/utils/catalog-manifest-v4.d.ts +0 -5
  332. package/dist/src/utils/catalog-manifest-v4.js.map +0 -1
  333. package/dist/src/utils/catalog-source-cache.d.ts +0 -62
  334. package/dist/src/utils/catalog-source-cache.js +0 -296
  335. package/dist/src/utils/catalog-source-cache.js.map +0 -1
  336. package/dist/src/utils/codex-project-surface.d.ts +0 -3
  337. package/dist/src/utils/codex-project-surface.js +0 -49
  338. package/dist/src/utils/codex-project-surface.js.map +0 -1
  339. package/dist/src/utils/plugin-release.d.ts +0 -2
  340. package/dist/src/utils/plugin-release.js +0 -2
  341. package/dist/src/utils/plugin-release.js.map +0 -1
@@ -1,74 +1,68 @@
1
1
  import path from 'node:path';
2
2
  import fs from 'fs-extra';
3
- import { probeAgentVersion } from '../utils/agent-version.js';
3
+ import { getCodexOperationRequirement, probeAgentVersion } from '../utils/agent-version.js';
4
+ import { previewCodexManager, resolveCodexOperationConsumer, } from '../utils/codex-manager.js';
4
5
  import { createNoopDebugLogger } from '../output/debug-log.js';
5
6
  import { createNoopReporter } from '../output/reporter.js';
6
7
  import { getDefaultNativeCliRunner } from '../utils/native-cli.js';
7
- import { readPluginManifest, resolvePluginsFromCatalog } from '../utils/manifest.js';
8
- import { resolveAwHome } from '../utils/paths.js';
8
+ import { readPluginManifest } from '../utils/manifest.js';
9
+ import { resolveAwHome, resolveCodexPaths } from '../utils/paths.js';
9
10
  import { resolveCliPackageRoot } from '../utils/paths.js';
10
11
  import { supportsPlatform } from '../utils/platform-support.js';
11
- import { hasCodexProjectManagedArtifacts } from '../utils/codex-project-surface.js';
12
- import { createRebuildActiveStore, executeRebuildOperation, releaseRebuildOperationLease, } from './rebuild-operations.js';
13
- import { assertNoClaudeUnboundNativeNameCollision, createRebuildIntegration, discoverRebuildTargetNames, inspectNativeMarketplaceRegistrations, } from './rebuild-integration.js';
14
- import { getNamespacedMarketplaceName } from '../utils/local-marketplace.js';
12
+ import { analyzeCodexSurface } from '../utils/codex-surface-analysis.js';
13
+ import { createCodexReconcilePlan, } from '../utils/codex-reconciler.js';
14
+ import { readCodexConfigFile } from '../utils/codex-config.js';
15
+ import { createRebuildActiveStore, executeRebuildOperation, } from './rebuild-operations.js';
16
+ import { assertNoClaudeUnboundNativeNameCollision, createRebuildIntegration, discoverRebuildTargetNames, executeCodexReconcileActivations, inspectCodexSharedNativeLive, inspectNativeMarketplaceRegistrations, } from './rebuild-integration.js';
17
+ import { getNamespacedMarketplaceName, readStrictNamespacedMarketplaceRoot, } from '../utils/local-marketplace.js';
15
18
  import { formatPluginId, parsePluginId, planPluginSelectorSourceIds, preflightDesiredArtifactCollisions, withResolvedMultiSourceCatalog, } from '../utils/multi-source-catalog.js';
19
+ import { calculateCodexSharedNativePayloadDigest } from '../utils/catalog-digests.js';
20
+ import { resolveCatalogOperationContext, } from '../utils/catalog-operation-context.js';
16
21
  import { assertStrictSemVer } from '../utils/manifest.js';
17
22
  import { assertCatalogSourceInventoryComplete, canonicalMarketplaceRegistrationNames, inventoryCatalogSourceReferences, } from '../utils/catalog-source-references.js';
18
23
  import { resolveEffectivePluginConfig } from '../utils/plugin-config.js';
19
- import { cleanupCorruptExclusivePluginDirectory, inspectOwnedComponents, } from '../utils/ownership.js';
20
- import { codexProjectConfigAliasesUserHome, getClaudeHome, getCodexHome, resolveCanonicalRepoProjectKey, resolveRepoRootSync, } from '../utils/state-paths.js';
24
+ import { inspectOwnedComponents, } from '../utils/ownership.js';
25
+ import { codexProjectConfigAliasesUserHome, resolveCanonicalRepoProjectKey, } from '../utils/state-paths.js';
21
26
  import { resolvePluginMetaDependencyClosure } from '../utils/plugin-dependencies.js';
22
- import { createDependencyOperationPlan, } from './dependency-operation-plan.js';
23
- let rebuildCommandTestHooks;
24
- async function emitRebuildActionEvent(runtime, event) {
25
- await Promise.allSettled([
26
- Promise.resolve().then(() => runtime.reporter.event(event)),
27
- Promise.resolve().then(() => runtime.debugLogger.log(event)),
28
- ]);
29
- }
30
- async function withRebuildActionEvent(options) {
31
- const source = {
32
- plugin: options.identity.pluginName,
33
- platform: options.identity.platform,
34
- scope: options.identity.scope,
35
- action: options.command,
36
- };
27
+ import { readCatalogSourcesConfigSnapshot } from '../utils/catalog-sources.js';
28
+ import { readCatalogStoreEntry } from '../utils/catalog-generation-store.js';
29
+ import { catalogGenerationErrorCode, isOfflineCleanupCatalogGenerationError, } from '../utils/catalog-generation-error.js';
30
+ import { canonicalizePluginIntentState, convergePluginIntentManagedCodexOwnership, createEmptyPluginIntentState, mergePluginIntentRootPlatforms, readPluginIntentState, removePluginIntentRootPlatforms, widenPluginIntentManagedCodexOwnership, writePluginIntentState, } from '../utils/plugin-intent-state.js';
31
+ import { validateCatalogSnapshotPluginEntriesComplete } from '../utils/catalog-snapshot.js';
32
+ import { AwCapabilityUnsupportedError, IMPLEMENTED_AW_CAPABILITIES, } from './aw-capabilities.js';
33
+ import { checkAwCapabilitySupport } from '../utils/manifest.js';
34
+ import { createDependencyOperationPlan, createDependencyPlacementPlan, } from './dependency-operation-plan.js';
35
+ import { offlineCleanupEvidenceFromInventory, offlineCleanupEvidenceMatches, } from './offline-cleanup-evidence.js';
36
+ async function measureCommandPerformanceStage(runtime, name, operation) {
37
37
  const startedAt = Date.now();
38
- await emitRebuildActionEvent(options.runtime, {
39
- type: 'rebuild:action',
40
- source,
41
- data: { name: options.name, phase: 'start' },
42
- });
43
- let result;
38
+ let phase = 'success';
44
39
  try {
45
- result = await options.operation();
40
+ return await operation();
46
41
  }
47
42
  catch (error) {
48
- await emitRebuildActionEvent(options.runtime, {
49
- type: 'rebuild:action',
50
- source,
43
+ phase = 'failure';
44
+ throw error;
45
+ }
46
+ finally {
47
+ await Promise.resolve().then(() => runtime?.debugLogger?.log({
48
+ type: 'performance:stage',
49
+ level: 'debug',
50
+ target: 'debugLog',
51
+ message: 'Command performance stage completed.',
51
52
  data: {
52
- name: options.name,
53
- phase: 'failure',
53
+ name,
54
+ phase,
54
55
  durationMs: Math.max(0, Date.now() - startedAt),
55
- message: options.name === 'operation.lock'
56
- ? '获取操作锁失败。'
57
- : '释放操作锁失败。',
58
56
  },
59
- });
60
- throw error;
57
+ })).catch(() => undefined);
61
58
  }
62
- await emitRebuildActionEvent(options.runtime, {
63
- type: 'rebuild:action',
64
- source,
65
- data: {
66
- name: options.name,
67
- phase: 'success',
68
- durationMs: Math.max(0, Date.now() - startedAt),
69
- },
70
- });
71
- return result;
59
+ }
60
+ let rebuildCommandTestHooks;
61
+ async function emitRebuildActionEvent(runtime, event) {
62
+ await Promise.allSettled([
63
+ Promise.resolve().then(() => runtime.reporter.event(event)),
64
+ Promise.resolve().then(() => runtime.debugLogger.log(event)),
65
+ ]);
72
66
  }
73
67
  export function setRebuildCommandTestHooksForTests(hooks) {
74
68
  rebuildCommandTestHooks = hooks;
@@ -76,17 +70,91 @@ export function setRebuildCommandTestHooksForTests(hooks) {
76
70
  function selectedPlatforms(selection) {
77
71
  return selection === 'both' ? ['codex', 'claude'] : [selection];
78
72
  }
73
+ function updateIntentOverrides(options) {
74
+ const plannedKeys = new Set(options.plan.targets.map((target) => `${target.pluginId}\0${target.platform}`));
75
+ return canonicalizePluginIntentState({
76
+ ...options.state,
77
+ dependencyScopeOverrides: [
78
+ ...options.state.dependencyScopeOverrides.filter((override) => !plannedKeys.has(`${override.pluginId}\0${override.platform}`)),
79
+ ...options.plan.scopeOverrides,
80
+ ],
81
+ });
82
+ }
83
+ function effectiveDependencyScopeOverrides(options) {
84
+ const nodeKeys = new Set(options.nodes.map((node) => `${node.pluginId}\0${node.platform}`));
85
+ const explicitMatches = (pluginId, platform) => options.explicit.some((override) => override.pluginId === pluginId
86
+ && (override.platform === undefined || override.platform === platform));
87
+ return [
88
+ ...options.state.dependencyScopeOverrides.filter((override) => nodeKeys.has(`${override.pluginId}\0${override.platform}`)
89
+ && !options.ignoredPersistedRootIds?.has(override.pluginId)
90
+ && !explicitMatches(override.pluginId, override.platform)),
91
+ ...options.explicit.filter((override) => options.nodes.some((node) => node.pluginId === override.pluginId
92
+ && (override.platform === undefined || node.platform === override.platform))),
93
+ ];
94
+ }
95
+ function deriveDesiredIntentState(options) {
96
+ if (options.reconcileIntentOnly)
97
+ return options.state;
98
+ let next = options.state;
99
+ if (options.command === 'install') {
100
+ for (const root of options.selectedRoots)
101
+ next = mergePluginIntentRootPlatforms(next, root);
102
+ next = updateIntentOverrides({ state: next, plan: options.plan });
103
+ }
104
+ else if (options.command === 'uninstall') {
105
+ for (const root of options.selectedRoots)
106
+ next = removePluginIntentRootPlatforms(next, root);
107
+ }
108
+ return next;
109
+ }
110
+ async function writeDesiredIntentSuperset(options) {
111
+ const location = { scope: options.scope, cwd: options.cwd, env: options.env };
112
+ let next = deriveDesiredIntentState({
113
+ command: options.command,
114
+ state: await readPluginIntentState(location),
115
+ selectedRoots: options.selectedRoots,
116
+ plan: options.plan,
117
+ reconcileIntentOnly: options.reconcileIntentOnly,
118
+ });
119
+ const convergedCodexPluginIds = options.codexPlan?.managedInventory.converged
120
+ ?? next.managed.codexPluginIds;
121
+ const superset = options.codexPlan
122
+ ? widenPluginIntentManagedCodexOwnership(next, options.codexPlan.managedInventory.ownershipSuperset)
123
+ : next;
124
+ await writePluginIntentState(location, superset);
125
+ const writes = [{ location, superset, convergedCodexPluginIds }];
126
+ if (options.command === 'install' || options.codexPlan) {
127
+ const otherScope = options.scope === 'user' ? 'project' : 'user';
128
+ const otherLocation = { scope: otherScope, cwd: options.cwd, env: options.env };
129
+ let other = await readPluginIntentState(otherLocation);
130
+ if (options.command === 'install' && !options.reconcileIntentOnly) {
131
+ for (const root of options.selectedRoots)
132
+ other = removePluginIntentRootPlatforms(other, root);
133
+ }
134
+ const otherConvergedCodexPluginIds = options.codexPlan?.managedInventory.related.converged
135
+ ?? other.managed.codexPluginIds;
136
+ const otherSuperset = options.codexPlan
137
+ ? widenPluginIntentManagedCodexOwnership(other, options.codexPlan.managedInventory.related.ownershipSuperset)
138
+ : other;
139
+ await writePluginIntentState(otherLocation, otherSuperset);
140
+ writes.push({
141
+ location: otherLocation,
142
+ superset: otherSuperset,
143
+ convergedCodexPluginIds: otherConvergedCodexPluginIds,
144
+ });
145
+ }
146
+ return writes;
147
+ }
79
148
  function createSyntheticPlugin(pluginName, env) {
80
149
  const rootDir = path.join(resolveAwHome(env), 'plugins', pluginName);
81
150
  return {
82
151
  rootDir,
83
152
  manifestPath: path.join(rootDir, '.agents-plugin', 'plugin.json'),
84
153
  manifest: {
85
- schemaVersion: 1,
154
+ schemaVersion: 2,
86
155
  name: pluginName,
87
156
  version: '0.0.0',
88
157
  description: 'Cleanup-only target for a plugin missing from the current catalog.',
89
- compatibility: { aw: { minVersion: '0.0.0' } },
90
158
  marketplace: {
91
159
  category: 'cleanup',
92
160
  strict: true,
@@ -110,17 +178,6 @@ function failedResult(command, identity, message, phase = 'preflight') {
110
178
  error: { phase, message },
111
179
  };
112
180
  }
113
- function appendOperationReleaseFailure(result, message) {
114
- result.actions.push({ name: 'operation.release', status: 'failed' });
115
- result.failures.push({ phase: 'operation.release', message });
116
- if (result.status === 'failed')
117
- return result;
118
- return {
119
- ...result,
120
- status: 'failed',
121
- error: { phase: 'operation.release', message },
122
- };
123
- }
124
181
  function toTargetResult(result) {
125
182
  return {
126
183
  target: {
@@ -138,17 +195,17 @@ function toTargetResult(result) {
138
195
  ...(result.status === 'failed' ? { message: result.error.message } : {}),
139
196
  };
140
197
  }
141
- async function assertAgentVersions(options) {
198
+ async function assertClaudeVersion(options) {
199
+ if (!options.platforms.includes('claude'))
200
+ return;
142
201
  const failures = [];
143
- for (const platform of options.platforms) {
144
- const result = await probeAgentVersion(platform, {
145
- cwd: options.cwd,
146
- env: options.env,
147
- runner: options.runtime.nativeCliRunner,
148
- });
149
- if (result.status !== 'supported')
150
- failures.push(result);
151
- }
202
+ const result = await probeAgentVersion('claude', {
203
+ cwd: options.cwd,
204
+ env: options.env,
205
+ runner: options.runtime.nativeCliRunner,
206
+ });
207
+ if (result.status !== 'supported')
208
+ failures.push(result);
152
209
  if (failures.length > 0) {
153
210
  throw new Error(failures.map((failure) => `${failure.command} 版本检查失败:${failure.details ?? failure.status};最低版本 ${failure.requiredMinVersion}。`)
154
211
  .join(';'));
@@ -177,8 +234,7 @@ async function runSingleSourceRebuildCommand(options) {
177
234
  nativeCliRunner: options.runtime?.nativeCliRunner ?? getDefaultNativeCliRunner(),
178
235
  reporter: options.runtime?.reporter ?? createNoopReporter(),
179
236
  debugLogger: options.runtime?.debugLogger ?? createNoopDebugLogger(),
180
- runtimeLeaseHeld: options.runtime?.runtimeLeaseHeld,
181
- lifecycleLeaseHeld: options.runtime?.lifecycleLeaseHeld,
237
+ ...(options.runtime?.codexManager ? { codexManager: options.runtime.codexManager } : {}),
182
238
  expectedCatalogConfigDigest: options.runtime?.expectedCatalogConfigDigest,
183
239
  };
184
240
  const env = runtime.env;
@@ -186,7 +242,23 @@ async function runSingleSourceRebuildCommand(options) {
186
242
  const catalog = options.runtime?.catalogResolution;
187
243
  if (!catalog)
188
244
  throw new Error('Internal error: a resolved catalog source is required.');
189
- const catalogPlugins = await resolvePluginsFromCatalog(catalog.rootDir, undefined);
245
+ const requestedByPlatform = await resolveRequestedNames({
246
+ command: options.command,
247
+ pluginNames: options.pluginNames,
248
+ all: options.all,
249
+ platforms: selected,
250
+ scope: options.commandOptions.scope,
251
+ cwd: options.commandOptions.cwd,
252
+ env,
253
+ nativeCliRunner: runtime.nativeCliRunner,
254
+ });
255
+ const exactPluginNames = [...new Set([...requestedByPlatform.values()].flat())];
256
+ const catalogPlugins = options.runtime?.resolvedCatalogPlugins ?? (await Promise.all(exactPluginNames.map(async (pluginName) => {
257
+ const pluginRoot = path.join(catalog.rootDir, 'plugins', pluginName);
258
+ return await fs.pathExists(pluginRoot)
259
+ ? readPluginManifest(pluginRoot, { mode: 'bundle' })
260
+ : null;
261
+ }))).filter((plugin) => plugin !== null);
190
262
  if (options.runtime?.namespacedSource) {
191
263
  for (const plugin of catalogPlugins) {
192
264
  plugin.sourceId = options.runtime.namespacedSource.sourceId;
@@ -199,16 +271,6 @@ async function runSingleSourceRebuildCommand(options) {
199
271
  }
200
272
  }
201
273
  const catalogByName = new Map(catalogPlugins.map((plugin) => [plugin.manifest.name, plugin]));
202
- const requestedByPlatform = await resolveRequestedNames({
203
- command: options.command,
204
- pluginNames: options.pluginNames,
205
- all: options.all,
206
- platforms: selected,
207
- scope: options.commandOptions.scope,
208
- cwd: options.commandOptions.cwd,
209
- env,
210
- nativeCliRunner: runtime.nativeCliRunner,
211
- });
212
274
  if (options.commandOptions.platform === 'both'
213
275
  && options.command !== 'cleanup'
214
276
  && options.command !== 'uninstall') {
@@ -220,12 +282,14 @@ async function runSingleSourceRebuildCommand(options) {
220
282
  }
221
283
  }
222
284
  const platforms = selected.filter((platform) => (requestedByPlatform.get(platform)?.length ?? 0) > 0);
223
- await assertAgentVersions({
224
- platforms,
225
- cwd: options.commandOptions.cwd,
226
- env,
227
- runtime,
228
- });
285
+ if (options.runtime?.bypassDependencyPlan) {
286
+ await assertClaudeVersion({
287
+ platforms,
288
+ cwd: options.commandOptions.cwd,
289
+ env,
290
+ runtime,
291
+ });
292
+ }
229
293
  const integration = options.runtime?.preparedTarget
230
294
  ? {
231
295
  driver: options.runtime.preparedTarget.driver,
@@ -238,7 +302,6 @@ async function runSingleSourceRebuildCommand(options) {
238
302
  const activeStore = createRebuildActiveStore({
239
303
  env,
240
304
  loadPlugin: (bundleRoot) => readPluginManifest(bundleRoot, { mode: 'bundle' }),
241
- isLifecycleLeaseHeld: () => runtime.lifecycleLeaseHeld === true,
242
305
  });
243
306
  const targets = [];
244
307
  const results = [];
@@ -286,44 +349,6 @@ async function runSingleSourceRebuildCommand(options) {
286
349
  results.push(failedResult(options.command, identity, 'codexScopePathAlias: Codex project config 与用户配置目录相同。请进入实际项目目录后重试。'));
287
350
  continue;
288
351
  }
289
- if (currentPlugin
290
- && platform === 'codex'
291
- && options.commandOptions.scope === 'project'
292
- && options.command !== 'cleanup'
293
- && options.command !== 'uninstall'
294
- && currentPlugin.manifest.apps) {
295
- results.push(failedResult(options.command, identity, `${pluginName} 声明了 apps;Codex project scope 不支持 apps。`));
296
- continue;
297
- }
298
- if (currentPlugin
299
- && platform === 'codex'
300
- && options.commandOptions.scope === 'project'
301
- && options.command !== 'cleanup'
302
- && options.command !== 'uninstall'
303
- && !hasCodexProjectManagedArtifacts(currentPlugin)) {
304
- results.push(failedResult(options.command, identity, `${pluginName} 没有 Codex project scope 可生成的 Skills、instructions、roles、MCP、hooks 或 permissions。`));
305
- continue;
306
- }
307
- let operationLock;
308
- if (!options.runtime?.operationLeaseHeld) {
309
- try {
310
- await rebuildCommandTestHooks?.beforeOperationLockAcquire?.({
311
- command: options.command,
312
- identity,
313
- });
314
- operationLock = await withRebuildActionEvent({
315
- runtime,
316
- command: options.command,
317
- identity,
318
- name: 'operation.lock',
319
- operation: () => activeStore.acquireOperation(identity),
320
- });
321
- }
322
- catch (error) {
323
- results.push(failedResult(options.command, identity, error instanceof Error ? error.message : String(error), 'operation.lock'));
324
- continue;
325
- }
326
- }
327
352
  let result;
328
353
  try {
329
354
  if (options.runtime?.preparedTargetError)
@@ -358,49 +383,21 @@ async function runSingleSourceRebuildCommand(options) {
358
383
  ...options.runtime.resolvedReviewEvidenceByPlugin?.[pluginName],
359
384
  });
360
385
  }
361
- const { acquireOperation: _acquireOperation, ...operationHeldStore } = activeStore;
362
386
  result = await executeRebuildOperation({
363
387
  command: options.command,
364
388
  target,
365
389
  driver: integration.driver,
366
- activeStore: operationHeldStore,
390
+ activeStore,
367
391
  onActionEvent: (event) => emitRebuildActionEvent(runtime, event),
368
392
  });
369
393
  }
370
394
  catch (error) {
371
395
  result = error.result ?? failedResult(options.command, identity, error instanceof Error ? error.message : String(error), 'preflight');
372
396
  }
373
- result.actions.unshift({ name: 'operation.lock', status: 'succeeded' });
374
- if (operationLock) {
375
- try {
376
- await withRebuildActionEvent({
377
- runtime,
378
- command: options.command,
379
- identity,
380
- name: 'operation.release',
381
- operation: () => releaseRebuildOperationLease(operationLock),
382
- });
383
- result.actions.push({ name: 'operation.release', status: 'succeeded' });
384
- }
385
- catch (error) {
386
- result = appendOperationReleaseFailure(result, error instanceof Error ? error.message : String(error));
387
- }
388
- }
389
397
  results.push(result);
390
398
  }
391
399
  }
392
400
  const successful = results.filter((result) => result.status === 'succeeded');
393
- const artifacts = targets.flatMap((target) => [...target.artifacts]);
394
- const nativePlugins = {};
395
- for (const result of successful) {
396
- const target = targets.find((entry) => entry.identity.pluginName === result.target.pluginName
397
- && entry.identity.platform === result.target.platform
398
- && entry.identity.scope === result.target.scope);
399
- if (target?.requiresNative && options.command !== 'cleanup' && options.command !== 'uninstall') {
400
- nativePlugins[result.target.platform] ??= [];
401
- nativePlugins[result.target.platform].push(result.target.pluginName);
402
- }
403
- }
404
401
  return {
405
402
  platforms,
406
403
  plugins: [...new Set(successful.map((result) => result.target.pluginName))],
@@ -408,8 +405,6 @@ async function runSingleSourceRebuildCommand(options) {
408
405
  rebuildResults: results,
409
406
  targetResults: results.map(toTargetResult),
410
407
  overallState: results.every((result) => result.status === 'succeeded') ? 'succeeded' : 'failed',
411
- nativePlugins,
412
- repoManagedArtifacts: artifacts,
413
408
  receiptTargets: results.map((result) => ({
414
409
  pluginName: result.target.pluginName,
415
410
  ...(result.target.sourceId ? {
@@ -431,20 +426,11 @@ function toCatalogResolution(source) {
431
426
  source: 'local',
432
427
  catalogVersion: source.manifest.catalogVersion,
433
428
  channel: source.manifest.channel,
434
- minAwVersion: source.manifest.compatibility.aw.minVersion,
435
429
  };
436
430
  }
437
431
  function mergeRebuildCommandResults(results) {
438
432
  const rebuildResults = results.flatMap((result) => result.rebuildResults);
439
433
  const platforms = [...new Set(results.flatMap((result) => result.platforms))];
440
- const nativePlugins = {};
441
- for (const result of results) {
442
- for (const platform of ['codex', 'claude']) {
443
- const names = result.nativePlugins[platform];
444
- if (names)
445
- nativePlugins[platform] = [...new Set([...(nativePlugins[platform] ?? []), ...names])];
446
- }
447
- }
448
434
  return {
449
435
  platforms,
450
436
  plugins: [...new Set(results.flatMap((result) => result.plugins))],
@@ -454,19 +440,22 @@ function mergeRebuildCommandResults(results) {
454
440
  overallState: rebuildResults.every((result) => result.status === 'succeeded')
455
441
  ? 'succeeded'
456
442
  : 'failed',
457
- nativePlugins,
458
- repoManagedArtifacts: [],
459
443
  receiptTargets: results.flatMap((result) => result.receiptTargets),
460
444
  ...(results.find((result) => result.dependencyPlan)?.dependencyPlan
461
445
  ? { dependencyPlan: results.find((result) => result.dependencyPlan).dependencyPlan }
462
446
  : {}),
447
+ ...(results.find((result) => result.codexManagerPreview)?.codexManagerPreview
448
+ ? { codexManagerPreview: results.find((result) => result.codexManagerPreview).codexManagerPreview }
449
+ : {}),
450
+ ...(results.some((result) => result.offlineCleanupPlans !== undefined)
451
+ ? { offlineCleanupPlans: results.flatMap((result) => result.offlineCleanupPlans ?? []) }
452
+ : {}),
463
453
  };
464
454
  }
465
455
  function leaseSafeRebuildResult(result) {
466
456
  const projectPath = (value) => path.basename(value);
467
457
  return {
468
458
  ...result,
469
- repoManagedArtifacts: [],
470
459
  rebuildResults: result.rebuildResults.map((entry) => ({
471
460
  ...entry,
472
461
  target: { ...entry.target, cwd: '.' },
@@ -497,12 +486,18 @@ function projectLegacyTargets(references) {
497
486
  return [`${target.pluginName}\0${target.platform}\0${target.scope}`, target];
498
487
  })).values()];
499
488
  }
500
- function exclusiveSkillRoot(options) {
501
- if (options.scope === 'user') {
502
- return path.join(options.platform === 'codex' ? getCodexHome(options.env) : getClaudeHome(options.env), 'skills');
503
- }
504
- const repoRoot = resolveRepoRootSync(options.cwd);
505
- return path.join(repoRoot, options.platform === 'codex' ? '.agents' : '.claude', 'skills');
489
+ function desiredCleanupNoopResult(targets, cwd) {
490
+ const rebuildResults = targets.map((target) => ({
491
+ status: 'succeeded', command: 'cleanup', target: { ...target, pluginVersion: 'unavailable', cwd },
492
+ actions: [], cleanupAttempted: false, removed: [], absent: [], residual: [], quarantined: [], failures: [],
493
+ }));
494
+ return {
495
+ platforms: [...new Set(targets.map((target) => target.platform))],
496
+ plugins: [...new Set(targets.map((target) => target.pluginName))],
497
+ warnings: [], rebuildResults,
498
+ targetResults: rebuildResults.map((result) => ({ target: result.target, action: 'keep', state: 'completed' })),
499
+ overallState: 'succeeded', receiptTargets: targets,
500
+ };
506
501
  }
507
502
  function resolveLiveCleanupTargets(options) {
508
503
  const visible = options.references.filter((reference) => (reference.kind === 'namespacedOwner'
@@ -520,6 +515,12 @@ function resolveLiveCleanupTargets(options) {
520
515
  const matchesByTarget = new Map();
521
516
  for (const reference of visible.filter((entry) => entry.pluginName === parsed.pluginName
522
517
  && (parsed.sourceId === undefined || entry.sourceId === parsed.sourceId))) {
518
+ const lifecycleReference = visible.find((entry) => entry.kind === 'namespacedLifecycle'
519
+ && entry.sourceId === reference.sourceId
520
+ && entry.sourceIdentity === reference.sourceIdentity
521
+ && entry.pluginName === reference.pluginName
522
+ && entry.platform === reference.platform
523
+ && entry.scope === reference.scope);
523
524
  const liveNames = new Set(options.references.flatMap((candidate) => candidate.sourceId === reference.sourceId
524
525
  && candidate.sourceIdentity === reference.sourceIdentity
525
526
  && candidate.pluginName === reference.pluginName
@@ -553,7 +554,19 @@ function resolveLiveCleanupTargets(options) {
553
554
  platform: reference.platform,
554
555
  scope: reference.scope,
555
556
  ...(marketplaceName ? { marketplaceName } : {}),
557
+ ...((liveNames.size === 1
558
+ || (marketplaceName !== undefined && unboundNativeNames.has(marketplaceName)))
559
+ ? { nativeRegistration: true }
560
+ : {}),
556
561
  ...(marketplaceIdentityUnresolved ? { marketplaceIdentityUnresolved: true } : {}),
562
+ ...(lifecycleReference?.lifecycleMetadataIntegrityDigest
563
+ ? {
564
+ lifecycleMetadataIntegrityDigest: lifecycleReference.lifecycleMetadataIntegrityDigest,
565
+ }
566
+ : {}),
567
+ ...(lifecycleReference?.pluginVersion
568
+ ? { lifecyclePluginVersion: lifecycleReference.pluginVersion }
569
+ : {}),
557
570
  };
558
571
  const key = [target.pluginId, target.sourceIdentity, target.platform, target.scope].join('\0');
559
572
  const previous = matchesByTarget.get(key);
@@ -574,60 +587,18 @@ function resolveLiveCleanupTargets(options) {
574
587
  ].join('\0'), target])).values()],
575
588
  };
576
589
  }
577
- async function discoverDamagedExclusiveCleanupTargets(options) {
578
- const parsed = parsePluginId(options.selector);
579
- if (!parsed.sourceId)
580
- return [];
581
- const targets = [];
582
- for (const platform of options.platforms) {
583
- const root = exclusiveSkillRoot({
584
- platform,
585
- scope: options.scope,
586
- cwd: options.cwd,
587
- env: options.env,
588
- });
589
- const entries = await fs.readdir(root, { withFileTypes: true }).catch(() => []);
590
- for (const entry of entries) {
591
- if (!entry.isDirectory() || entry.isSymbolicLink())
592
- continue;
593
- const markerPath = path.join(root, entry.name, '.agent-workflows-owner.json');
594
- const markerStats = await fs.lstat(markerPath).catch(() => null);
595
- if (!markerStats?.isFile() || markerStats.isSymbolicLink())
596
- continue;
597
- const raw = await fs.readJson(markerPath).catch(() => null);
598
- if (!raw
599
- || raw.schemaVersion !== 2
600
- || raw.owner !== 'agent-workflows'
601
- || raw.sourceId !== parsed.sourceId
602
- || raw.plugin !== parsed.pluginName
603
- || raw.pluginId !== options.selector
604
- || raw.platform !== platform
605
- || raw.scope !== options.scope)
606
- continue;
607
- targets.push({
608
- pluginId: options.selector,
609
- pluginName: parsed.pluginName,
610
- sourceId: parsed.sourceId,
611
- sourceIdentity: typeof raw.sourceIdentity === 'string'
612
- && /^sha256:[0-9a-f]{64}$/u.test(raw.sourceIdentity)
613
- ? raw.sourceIdentity
614
- : `sha256:${'0'.repeat(64)}`,
615
- platform,
616
- scope: raw.scope,
617
- });
618
- }
619
- }
620
- return [...new Map(targets.map((target) => [[target.platform, target.scope].join('\0'), target])).values()];
621
- }
622
590
  async function cleanupOfflineOwnedComponents(options) {
623
591
  if (options.targets.some((target) => target.marketplaceIdentityUnresolved)) {
624
592
  throw namespacedPreflightError('marketplaceIdentityUnavailable: Cannot uniquely recover the native marketplace name from live inventory or a canonical native registration path.', options.targets);
625
593
  }
626
594
  const results = [];
627
595
  const warnings = [];
596
+ const nativeCliRunner = options.nativeCliRunner ?? getDefaultNativeCliRunner();
597
+ const codexManager = options.codexManager;
628
598
  const context = {
629
599
  env: options.env,
630
- nativeCliRunner: options.nativeCliRunner ?? getDefaultNativeCliRunner(),
600
+ nativeCliRunner,
601
+ ...(codexManager ? { codexManager } : {}),
631
602
  reporter: createNoopReporter(),
632
603
  debugLogger: createNoopDebugLogger(),
633
604
  };
@@ -635,56 +606,36 @@ async function cleanupOfflineOwnedComponents(options) {
635
606
  const activeStore = createRebuildActiveStore({
636
607
  env: options.env,
637
608
  loadPlugin: (bundleRoot) => readPluginManifest(bundleRoot, { mode: 'bundle' }),
638
- isLifecycleLeaseHeld: () => context.lifecycleLeaseHeld === true,
639
609
  });
640
610
  for (const target of options.targets) {
641
- const exclusiveRecovery = {
642
- removed: [], absent: [], residual: [], quarantined: [], failures: [],
643
- };
644
- if (options.force) {
645
- const root = exclusiveSkillRoot({
646
- platform: target.platform,
647
- scope: target.scope,
611
+ const inventoryErrors = (options.inventoryErrors ?? []).filter((error) => error.relevance.global || ((error.relevance.sourceId === undefined || error.relevance.sourceId === target.sourceId)
612
+ && (error.relevance.pluginName === undefined || error.relevance.pluginName === target.pluginName)
613
+ && (error.relevance.platform === undefined || error.relevance.platform === target.platform)
614
+ && (error.relevance.scope === undefined || error.relevance.scope === target.scope)));
615
+ const evidence = options.evidence?.find((candidate) => candidate.pluginId === target.pluginId
616
+ && candidate.sourceIdentity === target.sourceIdentity
617
+ && candidate.platform === target.platform
618
+ && candidate.scope === target.scope);
619
+ const activationAction = target.platform === 'codex'
620
+ ? evidence?.plan.actions.find((action) => action.action === 'removeNativeRegistration'
621
+ && action.activationScope === target.scope)
622
+ : undefined;
623
+ let activationDisposition = 'remove';
624
+ if (activationAction && target.scope === 'user') {
625
+ const projectIntent = await readPluginIntentState({
626
+ scope: 'project',
648
627
  cwd: options.cwd,
649
628
  env: options.env,
650
629
  });
651
- const entries = await fs.readdir(root, { withFileTypes: true }).catch(() => []);
652
- for (const entry of entries) {
653
- if (!entry.isDirectory() || entry.isSymbolicLink())
654
- continue;
655
- const directory = path.join(root, entry.name);
656
- let recovered;
657
- try {
658
- recovered = await cleanupCorruptExclusivePluginDirectory({
659
- directory,
660
- allowedRoot: root,
661
- sourceId: target.sourceId,
662
- pluginName: target.pluginName,
663
- platform: target.platform,
664
- scope: target.scope,
665
- ...options.corruptExclusiveDirectoryOperations,
666
- });
667
- }
668
- catch (error) {
669
- recovered = {
670
- removed: [], absent: [], residual: [directory], quarantined: [],
671
- failures: [{
672
- label: directory,
673
- phase: 'mutation',
674
- message: error instanceof Error ? error.message : String(error),
675
- }],
676
- };
677
- }
678
- if (recovered) {
679
- exclusiveRecovery.removed.push(...recovered.removed);
680
- exclusiveRecovery.absent.push(...recovered.absent);
681
- exclusiveRecovery.residual.push(...recovered.residual);
682
- exclusiveRecovery.quarantined.push(...recovered.quarantined);
683
- exclusiveRecovery.failures.push(...recovered.failures);
684
- }
685
- }
630
+ const remainsDesired = projectIntent.roots.some((root) => root.pluginId === target.pluginId && root.platforms.includes('codex'))
631
+ || projectIntent.managed.codexPluginIds.includes(activationAction.target);
632
+ if (remainsDesired)
633
+ activationDisposition = 'disable';
686
634
  }
687
635
  const plugin = createSyntheticPlugin(target.pluginName, options.env);
636
+ if (target.lifecyclePluginVersion) {
637
+ plugin.manifest.version = target.lifecyclePluginVersion;
638
+ }
688
639
  plugin.sourceId = target.sourceId;
689
640
  plugin.sourceIdentity = target.sourceIdentity;
690
641
  plugin.pluginId = target.pluginId;
@@ -706,54 +657,46 @@ async function cleanupOfflineOwnedComponents(options) {
706
657
  source: 'local',
707
658
  catalogVersion: '0.0.0',
708
659
  channel: 'beta',
709
- minAwVersion: '0.0.0',
710
660
  },
711
- includeHistoricalLifecycleArtifacts: false,
661
+ includeHistoricalLifecycleArtifacts: target.lifecycleMetadataIntegrityDigest !== undefined,
662
+ ...(target.lifecycleMetadataIntegrityDigest
663
+ ? {
664
+ expectedLifecycleMetadataIntegrityDigest: target.lifecycleMetadataIntegrityDigest,
665
+ }
666
+ : {}),
667
+ includeNativeCleanupCandidate: target.nativeRegistration === true,
668
+ ...(activationAction
669
+ ? {
670
+ codexActivationCleanup: [{
671
+ codexPluginId: activationAction.target,
672
+ scope: activationAction.activationScope,
673
+ disposition: activationDisposition,
674
+ }],
675
+ preserveCodexSharedNativePayload: true,
676
+ }
677
+ : {}),
712
678
  });
713
- let result;
714
- const operationLock = await activeStore.acquireOperation(desired.identity);
715
- try {
716
- const { acquireOperation: _acquireOperation, ...operationHeldStore } = activeStore;
717
- result = await executeRebuildOperation({
718
- command: 'cleanup',
719
- target: desired,
720
- driver: integration.driver,
721
- activeStore: operationHeldStore,
722
- });
679
+ const preserveDamagedLifecycle = inventoryErrors.some((error) => error.kind.startsWith('lifecycle'));
680
+ const result = await executeRebuildOperation({
681
+ command: 'cleanup',
682
+ target: desired,
683
+ driver: integration.driver,
684
+ activeStore: preserveDamagedLifecycle
685
+ ? { ...activeStore, remove: async () => undefined }
686
+ : activeStore,
687
+ });
688
+ if (result.status === 'succeeded' && evidence) {
689
+ result.removed = [...new Set([
690
+ ...result.removed,
691
+ ...evidence.plan.actions.flatMap((action) => action.action === 'removeLifecycleSnapshot' ? [action.target] : []),
692
+ ])];
723
693
  }
724
- finally {
725
- await releaseRebuildOperationLease(operationLock);
726
- }
727
- const exclusiveResidual = (await Promise.all(exclusiveRecovery.residual.map(async (entry) => await fs.pathExists(entry) ? entry : null))).filter((entry) => entry !== null);
728
- const exclusiveQuarantined = (await Promise.all(exclusiveRecovery.quarantined.map(async (entry) => await fs.pathExists(entry) ? entry : null))).filter((entry) => entry !== null);
729
- const exclusiveFailures = (await Promise.all(exclusiveRecovery.failures.map(async (failure) => {
730
- const residualPath = failure.quarantinePath ?? failure.label;
731
- return await fs.pathExists(residualPath) ? failure : null;
732
- }))).filter((failure) => failure !== null);
733
- const removed = [...new Set([...result.removed, ...exclusiveRecovery.removed])];
734
- const absent = [...new Set([...result.absent, ...exclusiveRecovery.absent])]
735
- .filter((entry) => !removed.includes(entry));
736
- result = {
737
- ...result,
738
- removed,
739
- absent,
740
- residual: [...new Set([...result.residual, ...exclusiveResidual])],
741
- quarantined: [...new Set([...result.quarantined, ...exclusiveQuarantined])],
742
- };
743
- if (exclusiveFailures.length > 0) {
744
- const failures = exclusiveFailures.map((failure) => ({
745
- phase: 'cleanup.owned',
746
- label: failure.label,
747
- message: `exclusiveRecoveryFailed: ${failure.phase} ${failure.label}: ${failure.message}`,
748
- }));
749
- const message = failures[0].message;
750
- result = {
751
- ...result,
752
- status: 'failed',
753
- actions: [{ name: 'cleanup.owned', status: 'failed' }],
754
- failures: [...result.failures, ...failures],
755
- error: { phase: 'cleanup.owned', message },
756
- };
694
+ if (inventoryErrors.length > 0) {
695
+ result.residual = [...new Set([
696
+ ...result.residual,
697
+ ...inventoryErrors.map((error) => error.location),
698
+ ])];
699
+ warnings.push(...inventoryErrors.map((error) => `stateInventoryResidual: ${error.kind}: ${error.message}`));
757
700
  }
758
701
  results.push(result);
759
702
  }
@@ -767,11 +710,10 @@ async function cleanupOfflineOwnedComponents(options) {
767
710
  return leaseSafeRebuildResult({
768
711
  platforms: [...new Set(results.map((result) => result.target.platform))],
769
712
  plugins: results.filter((result) => result.status === 'succeeded').map((result) => result.target.pluginName),
770
- warnings,
713
+ warnings: [...new Set(warnings)],
771
714
  rebuildResults: results,
772
715
  targetResults: results.map(toTargetResult),
773
716
  overallState: results.every((result) => result.status === 'succeeded') ? 'succeeded' : 'failed',
774
- nativePlugins: {}, repoManagedArtifacts: [],
775
717
  receiptTargets: results.map((result) => ({
776
718
  pluginName: result.target.pluginName,
777
719
  pluginId: result.target.pluginId,
@@ -779,57 +721,188 @@ async function cleanupOfflineOwnedComponents(options) {
779
721
  platform: result.target.platform,
780
722
  scope: result.target.scope,
781
723
  })),
724
+ ...(options.evidence ? { offlineCleanupPlans: options.evidence } : {}),
782
725
  });
783
726
  }
727
+ async function previewOfflineOwnedComponents(options) {
728
+ if (options.targets.some((target) => target.marketplaceIdentityUnresolved)) {
729
+ throw namespacedPreflightError('marketplaceIdentityUnavailable: Cannot uniquely recover the native marketplace name from live inventory or a canonical native registration path.', options.targets);
730
+ }
731
+ await options.onPreviewStage?.('inventory');
732
+ const preview = mergeRebuildCommandResults([]);
733
+ if (options.evidence)
734
+ preview.offlineCleanupPlans = options.evidence;
735
+ await options.onPreviewStage?.('dependencyPlan');
736
+ return preview;
737
+ }
784
738
  async function assertOfflineCleanupDependents(options) {
785
- const inventory = await inventoryCatalogSourceReferences({
786
- cwd: options.cwd,
787
- env: options.env,
788
- includeHistoricalProjects: true,
739
+ const inventory = await options.readInventory({
740
+ cwd: options.cwd, env: options.env, includeHistoricalProjects: true,
789
741
  });
790
742
  const currentProjectKey = await resolveCanonicalRepoProjectKey(options.cwd);
743
+ const cleanupIds = new Set(options.targets.map((target) => target.pluginId));
791
744
  for (const target of options.targets) {
792
- const pluginsRoot = path.join(resolveAwHome(options.env), 'marketplaces', target.sourceId, 'plugins');
793
- const entries = await fs.readdir(pluginsRoot, { withFileTypes: true }).catch(() => []);
794
- for (const entry of entries) {
795
- if (!entry.isDirectory() || entry.name === target.pluginName)
796
- continue;
797
- const consumer = await readPluginManifest(path.join(pluginsRoot, entry.name), { mode: 'bundle' })
798
- .catch(() => null);
745
+ const consumerNames = [...new Set(inventory.references.flatMap((reference) => (reference.kind === 'namespacedOwner'
746
+ || reference.kind === 'namespacedMarketplace'
747
+ || reference.kind === 'namespacedLifecycle')
748
+ && reference.sourceId === target.sourceId
749
+ && reference.sourceIdentity === target.sourceIdentity
750
+ && reference.pluginName
751
+ && reference.pluginName !== target.pluginName
752
+ && reference.platform === target.platform
753
+ && (target.scope === 'user' || reference.scope === 'project')
754
+ ? [reference.pluginName]
755
+ : []))];
756
+ const consumerPlugins = (await Promise.all(consumerNames.map((pluginName) => readOfflinePluginManifestCandidates({
757
+ target,
758
+ sourceIdentity: target.sourceIdentity,
759
+ pluginName,
760
+ env: options.env,
761
+ inventory,
762
+ })))).flat();
763
+ for (const consumer of consumerPlugins) {
799
764
  if (!consumer?.manifest.dependencies?.includes(target.pluginName))
800
765
  continue;
801
766
  const consumerId = formatPluginId({ sourceId: target.sourceId, pluginName: consumer.manifest.name });
802
- const live = inventory.references.some((reference) => (reference.kind === 'namespacedOwner' || reference.kind === 'namespacedMarketplace')
767
+ if (cleanupIds.has(consumerId))
768
+ continue;
769
+ const live = inventory.references.some((reference) => (reference.kind === 'namespacedOwner'
770
+ || reference.kind === 'namespacedMarketplace'
771
+ || reference.kind === 'namespacedLifecycle')
803
772
  && reference.sourceId === target.sourceId
804
773
  && reference.sourceIdentity === target.sourceIdentity
805
774
  && reference.pluginName === consumer.manifest.name
806
775
  && reference.platform === target.platform
807
776
  && (target.scope === 'user' || reference.scope === 'project')
808
777
  && (target.scope === 'user' || reference.projectKey === currentProjectKey));
809
- if (live) {
778
+ if (live)
810
779
  throw namespacedPreflightError(`dependencyStillRequired: ${target.pluginId} is required by ${consumerId}.`, options.targets);
780
+ }
781
+ }
782
+ }
783
+ async function readPluginManifestIfPresent(root) {
784
+ return readPluginManifest(root, { mode: 'bundle' }).catch(() => null);
785
+ }
786
+ async function readOfflinePluginManifestCandidates(options) {
787
+ const lifecycleRoots = options.inventory.references.flatMap((reference) => reference.kind === 'namespacedLifecycle'
788
+ && reference.sourceId === options.target.sourceId
789
+ && reference.sourceIdentity === options.sourceIdentity
790
+ && reference.pluginName === options.pluginName
791
+ ? [path.join(reference.location, 'bundle')]
792
+ : []);
793
+ const marketplaceRoot = path.join(resolveAwHome(options.env), 'marketplaces', options.target.sourceId);
794
+ const marketplace = await readStrictNamespacedMarketplaceRoot({
795
+ rootDir: marketplaceRoot,
796
+ sourceId: options.target.sourceId,
797
+ }).catch(() => null);
798
+ const marketplaceRoots = marketplace?.config.sourceIdentity === options.sourceIdentity
799
+ ? [marketplaceRoot]
800
+ : [];
801
+ const roots = [
802
+ ...lifecycleRoots,
803
+ ...marketplaceRoots.map((root) => path.join(root, 'plugins', options.pluginName)),
804
+ ];
805
+ const storeRoot = path.join(resolveAwHome(options.env), 'catalog-store');
806
+ const logicalEntries = await fs.readdir(storeRoot, { withFileTypes: true }).catch(() => []);
807
+ for (const logicalEntry of logicalEntries) {
808
+ if (!logicalEntry.isDirectory() || logicalEntry.isSymbolicLink())
809
+ continue;
810
+ const logicalRoot = path.join(storeRoot, logicalEntry.name);
811
+ const snapshotEntries = await fs.readdir(logicalRoot, { withFileTypes: true }).catch(() => []);
812
+ for (const snapshotEntry of snapshotEntries) {
813
+ if (!snapshotEntry.isDirectory()
814
+ || snapshotEntry.isSymbolicLink()
815
+ || !/^[0-9a-f]{64}$/u.test(snapshotEntry.name))
816
+ continue;
817
+ let logicalIdentity;
818
+ try {
819
+ logicalIdentity = decodeURIComponent(logicalEntry.name);
820
+ }
821
+ catch {
822
+ continue;
811
823
  }
824
+ const entry = await readCatalogStoreEntry({
825
+ logicalIdentity,
826
+ snapshotDigest: `sha256:${snapshotEntry.name}`,
827
+ env: options.env,
828
+ }).catch(() => null);
829
+ if (!entry)
830
+ continue;
831
+ const storeAnchor = await readPluginManifestIfPresent(path.join(entry.snapshotRoot, 'plugins', options.target.pluginName));
832
+ if (!storeAnchor || storeAnchor.manifest.name !== options.target.pluginName)
833
+ continue;
834
+ roots.push(path.join(entry.snapshotRoot, 'plugins', options.pluginName));
812
835
  }
813
836
  }
837
+ const plugins = await Promise.all([...new Set(roots)].map(readPluginManifestIfPresent));
838
+ return plugins.filter((plugin) => plugin !== null);
839
+ }
840
+ async function cleanupTargetIsDesired(options) {
841
+ const pending = options.roots.filter((root) => root.platforms.includes(options.target.platform));
842
+ const visited = new Set();
843
+ while (pending.length > 0) {
844
+ const root = pending.pop();
845
+ if (visited.has(root.pluginId))
846
+ continue;
847
+ visited.add(root.pluginId);
848
+ if (root.pluginId === options.target.pluginId)
849
+ return true;
850
+ const parsed = parsePluginId(root.pluginId);
851
+ if (parsed.sourceId !== options.target.sourceId)
852
+ continue;
853
+ const plugins = await readOfflinePluginManifestCandidates({
854
+ target: options.target,
855
+ sourceIdentity: options.target.sourceIdentity,
856
+ pluginName: parsed.pluginName,
857
+ env: options.env,
858
+ inventory: options.inventory,
859
+ });
860
+ pending.push(...plugins.flatMap((plugin) => plugin.manifest.dependencies ?? []).map((pluginName) => ({
861
+ pluginId: formatPluginId({ sourceId: parsed.sourceId, pluginName }),
862
+ platforms: [options.target.platform],
863
+ })));
864
+ }
865
+ return false;
814
866
  }
815
867
  export async function runRebuildCommand(options) {
816
868
  if (options.runtime?.catalogResolution && options.runtime.bypassDependencyPlan) {
817
869
  return runSingleSourceRebuildCommand(options);
818
870
  }
819
871
  const runtimeEnv = options.runtime?.env ?? process.env;
872
+ const readInventory = options.runtime?.inventoryCatalogSourceReferences
873
+ ?? inventoryCatalogSourceReferences;
874
+ let ordinaryInventory;
875
+ const readOrdinaryInventory = async () => {
876
+ ordinaryInventory ??= await measureCommandPerformanceStage(options.runtime, 'inventory', () => readInventory({
877
+ cwd: options.commandOptions.cwd,
878
+ env: runtimeEnv,
879
+ ...(options.command === 'cleanup' && options.all
880
+ ? { includeOrphanedCodexNativeResidue: true }
881
+ : {}),
882
+ ...(options.command === 'cleanup' && !options.all
883
+ ? {
884
+ codexNativeResidueTargets: options.pluginNames.map((selector) => {
885
+ const parsed = parsePluginId(selector);
886
+ return {
887
+ pluginName: parsed.pluginName,
888
+ ...(parsed.sourceId ? { sourceId: parsed.sourceId } : {}),
889
+ };
890
+ }),
891
+ }
892
+ : {}),
893
+ }));
894
+ return ordinaryInventory;
895
+ };
820
896
  let requestedPluginNames = options.pluginNames;
821
897
  const cleanupForce = options.command === 'cleanup'
822
898
  && options.commandOptions.force === true;
899
+ const expectedOfflineCleanup = options.runtime?.expectedOfflineCleanupEvidence;
823
900
  let cleanupInventory;
824
901
  let cleanupLiveTargets = [];
825
- let cleanupHasDamagedExclusiveTarget = false;
826
902
  let liveTargets = [];
827
903
  let legacyLiveTargets = [];
828
904
  if (options.command === 'cleanup' && !options.all) {
829
- cleanupInventory = await inventoryCatalogSourceReferences({
830
- cwd: options.commandOptions.cwd,
831
- env: runtimeEnv,
832
- });
905
+ cleanupInventory = await readOrdinaryInventory();
833
906
  const live = resolveLiveCleanupTargets({
834
907
  selectors: requestedPluginNames,
835
908
  platforms: selectedPlatforms(options.commandOptions.platform),
@@ -838,40 +911,30 @@ export async function runRebuildCommand(options) {
838
911
  });
839
912
  requestedPluginNames = live.selectors;
840
913
  cleanupLiveTargets = live.targets;
841
- if (cleanupForce) {
842
- const damagedTargets = (await Promise.all(requestedPluginNames.map(async (selector) => parsePluginId(selector).sourceId
843
- ? discoverDamagedExclusiveCleanupTargets({
844
- selector,
845
- platforms: selectedPlatforms(options.commandOptions.platform),
846
- scope: options.commandOptions.scope,
847
- cwd: options.commandOptions.cwd,
848
- env: runtimeEnv,
914
+ if (expectedOfflineCleanup) {
915
+ const liveTarget = cleanupLiveTargets.find((target) => target.pluginId === expectedOfflineCleanup.pluginId
916
+ && target.pluginName === expectedOfflineCleanup.pluginName
917
+ && target.sourceId === expectedOfflineCleanup.sourceId
918
+ && target.sourceIdentity === expectedOfflineCleanup.sourceIdentity
919
+ && target.platform === expectedOfflineCleanup.platform
920
+ && target.scope === expectedOfflineCleanup.scope);
921
+ const actualOfflineCleanup = liveTarget
922
+ ? offlineCleanupEvidenceFromInventory({
923
+ ...expectedOfflineCleanup,
924
+ inventory: cleanupInventory,
849
925
  })
850
- : []))).flat();
851
- cleanupHasDamagedExclusiveTarget = damagedTargets.length > 0;
852
- cleanupLiveTargets = [...new Map([
853
- ...damagedTargets,
854
- ...cleanupLiveTargets,
855
- ].map((target) => [[target.pluginId, target.platform, target.scope].join('\0'), target])).values()];
926
+ : null;
927
+ if (!liveTarget
928
+ || !actualOfflineCleanup
929
+ || !offlineCleanupEvidenceMatches(expectedOfflineCleanup, actualOfflineCleanup)) {
930
+ throw namespacedPreflightError('operationReviewStale: offline cleanup ownership evidence changed before mutation.', cleanupLiveTargets);
931
+ }
932
+ cleanupLiveTargets = [liveTarget];
856
933
  }
857
934
  liveTargets = cleanupLiveTargets;
858
935
  }
859
- if (cleanupHasDamagedExclusiveTarget) {
860
- return cleanupOfflineOwnedComponents({
861
- targets: cleanupLiveTargets,
862
- cwd: options.commandOptions.cwd,
863
- env: runtimeEnv,
864
- force: true,
865
- removeObsoleteInstallationContracts: options.runtime?.removeObsoleteInstallationContracts,
866
- nativeCliRunner: options.runtime?.nativeCliRunner,
867
- corruptExclusiveDirectoryOperations: options.runtime?.corruptExclusiveDirectoryOperations,
868
- });
869
- }
870
936
  if (options.all) {
871
- const inventory = await inventoryCatalogSourceReferences({
872
- cwd: options.commandOptions.cwd,
873
- env: runtimeEnv,
874
- });
937
+ const inventory = await readOrdinaryInventory();
875
938
  if (options.command !== 'cleanup')
876
939
  assertCatalogSourceInventoryComplete(inventory);
877
940
  const requestedPlatforms = new Set(selectedPlatforms(options.commandOptions.platform));
@@ -902,6 +965,10 @@ export async function runRebuildCommand(options) {
902
965
  ].join('\0'), target];
903
966
  })).values()];
904
967
  liveTargets = projectedTargets;
968
+ if (options.command === 'cleanup') {
969
+ cleanupInventory = inventory;
970
+ cleanupLiveTargets = projectedTargets;
971
+ }
905
972
  const identities = new Map();
906
973
  for (const reference of live) {
907
974
  if (!reference.sourceIdentity) {
@@ -924,38 +991,184 @@ export async function runRebuildCommand(options) {
924
991
  }
925
992
  return mergeRebuildCommandResults([]);
926
993
  }
994
+ if (options.command === 'cleanup') {
995
+ if (cleanupLiveTargets.some((target) => target.marketplaceIdentityUnresolved)) {
996
+ throw namespacedPreflightError('marketplaceIdentityUnavailable: Cannot uniquely recover the native marketplace name from live inventory or a canonical native registration path.', cleanupLiveTargets);
997
+ }
998
+ if (cleanupLiveTargets.length === 0) {
999
+ const configuredSnapshot = await readCatalogSourcesConfigSnapshot(runtimeEnv).catch(() => null);
1000
+ const configuredSources = new Set(configuredSnapshot?.config.sources.map((source) => source.id) ?? []);
1001
+ const requestedPlatforms = selectedPlatforms(options.commandOptions.platform);
1002
+ const primarySource = configuredSnapshot?.config.sources.find((source) => source.primary)?.id;
1003
+ const unresolvedTargets = requestedPluginNames.flatMap((selector) => {
1004
+ const parsed = parsePluginId(selector);
1005
+ const sourceId = parsed.sourceId ?? primarySource;
1006
+ return sourceId ? requestedPlatforms.map((platform) => ({
1007
+ pluginId: formatPluginId({ sourceId, pluginName: parsed.pluginName }),
1008
+ pluginName: parsed.pluginName,
1009
+ sourceId,
1010
+ sourceIdentity: 'unavailable',
1011
+ platform,
1012
+ scope: options.commandOptions.scope,
1013
+ })) : [];
1014
+ });
1015
+ const inventoryError = cleanupInventory?.errors.find((error) => error.relevance.global || unresolvedTargets.some((target) => !error.relevance.global
1016
+ && (error.relevance.sourceId === undefined || error.relevance.sourceId === target.sourceId)
1017
+ && (error.relevance.pluginName === undefined || error.relevance.pluginName === target.pluginName)
1018
+ && (error.relevance.platform === undefined || error.relevance.platform === target.platform)
1019
+ && (error.relevance.scope === undefined
1020
+ || error.relevance.scope === target.scope
1021
+ || target.platform === 'codex')));
1022
+ if (inventoryError) {
1023
+ throw namespacedPreflightError(`stateInventoryFailed: ${inventoryError.message}`, unresolvedTargets);
1024
+ }
1025
+ const missingTargets = requestedPluginNames.flatMap((selector) => {
1026
+ const parsed = parsePluginId(selector);
1027
+ return parsed.sourceId && !configuredSources.has(parsed.sourceId)
1028
+ ? requestedPlatforms.map((platform) => ({
1029
+ pluginId: selector,
1030
+ pluginName: parsed.pluginName,
1031
+ sourceId: parsed.sourceId,
1032
+ sourceIdentity: 'unavailable',
1033
+ platform,
1034
+ scope: options.commandOptions.scope,
1035
+ }))
1036
+ : [];
1037
+ });
1038
+ if (missingTargets.length > 0) {
1039
+ throw namespacedPreflightError('sourceMissing: cleanup target source is unavailable.', missingTargets);
1040
+ }
1041
+ const result = mergeRebuildCommandResults([]);
1042
+ if (cleanupForce && !options.runtime?.previewDependencyPlanOnly) {
1043
+ try {
1044
+ await (options.runtime?.removeObsoleteInstallationContracts ?? fs.remove)(path.join(resolveAwHome(runtimeEnv), 'installation-contracts'));
1045
+ }
1046
+ catch (error) {
1047
+ result.warnings.push(`obsoleteContractsCleanupFailed: ${error instanceof Error ? error.message : String(error)}`);
1048
+ }
1049
+ }
1050
+ return result;
1051
+ }
1052
+ const configuredSources = await readCatalogSourcesConfigSnapshot(runtimeEnv)
1053
+ .then((snapshot) => new Set(snapshot.config.sources.map((source) => source.id)))
1054
+ .catch(() => new Set());
1055
+ if (!cleanupForce) {
1056
+ await assertOfflineCleanupDependents({
1057
+ targets: cleanupLiveTargets,
1058
+ cwd: options.commandOptions.cwd,
1059
+ env: runtimeEnv,
1060
+ readInventory,
1061
+ });
1062
+ }
1063
+ const desiredIntent = await readPluginIntentState({
1064
+ scope: options.commandOptions.scope,
1065
+ cwd: options.commandOptions.cwd,
1066
+ env: runtimeEnv,
1067
+ });
1068
+ const desiredFlags = await Promise.all(cleanupLiveTargets.map((target) => cleanupTargetIsDesired({
1069
+ target,
1070
+ roots: desiredIntent.roots,
1071
+ inventory: cleanupInventory,
1072
+ env: runtimeEnv,
1073
+ })));
1074
+ const desiredTargets = cleanupLiveTargets.filter((_, index) => desiredFlags[index]);
1075
+ const removalTargets = cleanupLiveTargets.filter((_, index) => !desiredFlags[index]);
1076
+ if (removalTargets.length === 0) {
1077
+ const result = desiredCleanupNoopResult(desiredTargets, options.commandOptions.cwd);
1078
+ if (cleanupForce && !options.runtime?.previewDependencyPlanOnly) {
1079
+ try {
1080
+ await (options.runtime?.removeObsoleteInstallationContracts ?? fs.remove)(path.join(resolveAwHome(runtimeEnv), 'installation-contracts'));
1081
+ }
1082
+ catch (error) {
1083
+ result.warnings.push(`obsoleteContractsCleanupFailed: ${error instanceof Error ? error.message : String(error)}`);
1084
+ }
1085
+ }
1086
+ return leaseSafeRebuildResult(result);
1087
+ }
1088
+ const evidence = expectedOfflineCleanup
1089
+ ? [expectedOfflineCleanup]
1090
+ : removalTargets.flatMap((target) => {
1091
+ const value = offlineCleanupEvidenceFromInventory({
1092
+ reason: configuredSources.has(target.sourceId) ? 'bundleUnavailable' : 'sourceMissing',
1093
+ pluginId: target.pluginId,
1094
+ pluginName: target.pluginName,
1095
+ sourceId: target.sourceId,
1096
+ sourceIdentity: target.sourceIdentity,
1097
+ platform: target.platform,
1098
+ scope: target.scope,
1099
+ inventory: cleanupInventory,
1100
+ });
1101
+ return value ? [value] : [];
1102
+ });
1103
+ if (options.runtime?.previewDependencyPlanOnly) {
1104
+ const previewResult = await previewOfflineOwnedComponents({
1105
+ targets: removalTargets,
1106
+ evidence,
1107
+ cwd: options.commandOptions.cwd,
1108
+ env: runtimeEnv,
1109
+ onPreviewStage: options.runtime.onPreviewStage,
1110
+ });
1111
+ return desiredTargets.length > 0
1112
+ ? mergeRebuildCommandResults([
1113
+ desiredCleanupNoopResult(desiredTargets, options.commandOptions.cwd),
1114
+ previewResult,
1115
+ ])
1116
+ : previewResult;
1117
+ }
1118
+ const cleanupResult = await cleanupOfflineOwnedComponents({
1119
+ targets: removalTargets,
1120
+ evidence,
1121
+ cwd: options.commandOptions.cwd,
1122
+ env: runtimeEnv,
1123
+ inventoryErrors: cleanupInventory.errors,
1124
+ removeObsoleteInstallationContracts: options.runtime?.removeObsoleteInstallationContracts,
1125
+ nativeCliRunner: options.runtime?.nativeCliRunner,
1126
+ codexManager: options.runtime?.codexManager,
1127
+ });
1128
+ return desiredTargets.length > 0
1129
+ ? mergeRebuildCommandResults([
1130
+ desiredCleanupNoopResult(desiredTargets, options.commandOptions.cwd),
1131
+ cleanupResult,
1132
+ ])
1133
+ : cleanupResult;
1134
+ }
927
1135
  try {
928
1136
  if (options.runtime?.previewDependencyPlanOnly) {
929
1137
  await options.runtime.onPreviewStage?.('catalog');
930
1138
  }
1139
+ if (options.runtime?.catalogBootstrapError !== undefined) {
1140
+ throw options.runtime.catalogBootstrapError;
1141
+ }
1142
+ const catalogOperation = options.runtime?.catalogOperationContext
1143
+ ?? await measureCommandPerformanceStage(options.runtime, 'catalog.load', () => resolveCatalogOperationContext({
1144
+ ...(sourceIds ? { sourceIds } : {}),
1145
+ env: runtimeEnv,
1146
+ }));
1147
+ if (options.runtime?.expectedGenerationSetDigest
1148
+ && catalogOperation?.generationSetDigest !== options.runtime.expectedGenerationSetDigest) {
1149
+ throw new Error('operationReviewStale: Active Catalog generation changed before mutation.');
1150
+ }
931
1151
  const resolvedResult = await withResolvedMultiSourceCatalog({
932
1152
  cliVersion: resolveRuntimeCliVersion(),
933
- freshnessPolicy: options.command === 'cleanup'
934
- ? 'cacheFirst'
935
- : options.runtime?.previewDependencyPlanOnly
936
- ? options.runtime.previewCatalogFreshnessPolicy ?? 'requireRemote'
937
- : 'requireRemote',
938
1153
  ...(sourceIds ? { sourceIds } : {}),
1154
+ ...(catalogOperation ? { operationContext: catalogOperation } : {}),
939
1155
  env: runtimeEnv,
940
1156
  consume: async (catalog) => {
941
1157
  const failedSources = catalog.sources.filter((source) => source.error);
942
1158
  if (failedSources.length > 0) {
943
1159
  throw namespacedPreflightError(`Cannot resolve required catalog sources: ${failedSources.map((source) => source.sourceId).join(', ')}.`, liveTargets, legacyLiveTargets);
944
1160
  }
945
- const available = (await Promise.all(catalog.sources.map(async (item) => {
1161
+ const availableEntries = catalog.sources.flatMap((item) => {
946
1162
  const source = item.resolution;
947
- const plugins = await resolvePluginsFromCatalog(source.rootDir, undefined);
948
- return plugins.map((plugin) => {
949
- const pluginId = formatPluginId({ sourceId: source.sourceId, pluginName: plugin.manifest.name });
950
- plugin.sourceId = source.sourceId;
951
- plugin.sourceIdentity = source.sourceIdentity;
952
- plugin.pluginId = pluginId;
953
- return { source, plugin, pluginId };
954
- });
955
- }))).flat();
956
- const selected = selectors.map((selector, index) => {
957
- const matches = available.filter((entry) => entry.plugin.manifest.name === selector.pluginName
958
- && (selector.sourceId === undefined || entry.source.sourceId === selector.sourceId));
1163
+ return source.manifest.entries.map((entry) => ({
1164
+ source,
1165
+ entry,
1166
+ pluginId: formatPluginId({ sourceId: source.sourceId, pluginName: entry.name }),
1167
+ }));
1168
+ });
1169
+ const selectedEntries = selectors.map((selector, index) => {
1170
+ const matches = availableEntries.filter((candidate) => candidate.entry.name === selector.pluginName
1171
+ && (selector.sourceId === undefined || candidate.source.sourceId === selector.sourceId));
959
1172
  if (matches.length === 0) {
960
1173
  throw new Error(`Plugin "${requestedPluginNames[index]}" does not exist in the required catalog source set.`);
961
1174
  }
@@ -967,6 +1180,74 @@ export async function runRebuildCommand(options) {
967
1180
  const dependencyEnabled = options.command === 'install'
968
1181
  || options.command === 'upgrade'
969
1182
  || options.command === 'reinstall';
1183
+ const entryClosure = (roots) => {
1184
+ const result = [];
1185
+ const seen = new Set();
1186
+ const visiting = new Set();
1187
+ const byId = new Map(availableEntries.map((candidate) => [candidate.pluginId, candidate]));
1188
+ const visit = (candidate) => {
1189
+ if (seen.has(candidate.pluginId))
1190
+ return;
1191
+ if (visiting.has(candidate.pluginId))
1192
+ throw new Error(`Catalog dependency cycle includes "${candidate.pluginId}".`);
1193
+ visiting.add(candidate.pluginId);
1194
+ for (const dependency of candidate.entry.dependencies) {
1195
+ const dependencyId = formatPluginId({ sourceId: candidate.source.sourceId, pluginName: dependency });
1196
+ const resolved = byId.get(dependencyId);
1197
+ if (!resolved)
1198
+ throw new Error(`Catalog plugin "${candidate.pluginId}" depends on missing plugin "${dependency}".`);
1199
+ visit(resolved);
1200
+ }
1201
+ visiting.delete(candidate.pluginId);
1202
+ seen.add(candidate.pluginId);
1203
+ result.push(candidate);
1204
+ };
1205
+ for (const root of roots)
1206
+ visit(root);
1207
+ return result;
1208
+ };
1209
+ const removalConsumerEntries = options.command === 'cleanup' || options.command === 'uninstall'
1210
+ ? availableEntries.filter((candidate) => selectedEntries.some((selectedEntry) => candidate.source.sourceId === selectedEntry.source.sourceId
1211
+ && candidate.entry.dependencies.includes(selectedEntry.entry.name)))
1212
+ : [];
1213
+ const removalDesiredEntries = options.command === 'cleanup' || options.command === 'uninstall'
1214
+ ? entryClosure(selectedEntries)
1215
+ : [];
1216
+ const requiredEntryClosure = dependencyEnabled
1217
+ ? entryClosure(selectedEntries)
1218
+ : [...new Map([
1219
+ ...selectedEntries,
1220
+ ...removalDesiredEntries,
1221
+ ...removalConsumerEntries,
1222
+ ].map((candidate) => [candidate.pluginId, candidate])).values()];
1223
+ if (dependencyEnabled) {
1224
+ for (const candidate of requiredEntryClosure) {
1225
+ const requiredCapabilities = candidate.entry.requires.aw.capabilities ?? [];
1226
+ const support = checkAwCapabilitySupport(requiredCapabilities, IMPLEMENTED_AW_CAPABILITIES);
1227
+ if (!support.supported) {
1228
+ throw new AwCapabilityUnsupportedError(candidate.pluginId, support.missing);
1229
+ }
1230
+ }
1231
+ await measureCommandPerformanceStage(options.runtime, 'catalog.validation', async () => {
1232
+ for (const item of catalog.sources) {
1233
+ const source = item.resolution;
1234
+ const names = requiredEntryClosure
1235
+ .filter((candidate) => candidate.source.sourceId === source.sourceId)
1236
+ .map((candidate) => candidate.entry.name);
1237
+ if (names.length > 0) {
1238
+ await validateCatalogSnapshotPluginEntriesComplete(source.rootDir, names);
1239
+ }
1240
+ }
1241
+ });
1242
+ }
1243
+ const available = await Promise.all(requiredEntryClosure.map(async (candidate) => {
1244
+ const plugin = await readPluginManifest(path.join(candidate.source.rootDir, 'plugins', candidate.entry.name), { mode: 'bundle' });
1245
+ plugin.sourceId = candidate.source.sourceId;
1246
+ plugin.sourceIdentity = candidate.source.sourceIdentity;
1247
+ plugin.pluginId = candidate.pluginId;
1248
+ return { source: candidate.source, plugin, pluginId: candidate.pluginId };
1249
+ }));
1250
+ const selected = selectedEntries.map((entry) => available.find((candidate) => candidate.pluginId === entry.pluginId));
970
1251
  const selectedIds = new Set(selected.map((entry) => entry.pluginId));
971
1252
  const resolveClosureEntries = (rootsBySource) => catalog.sources.flatMap((item) => {
972
1253
  const source = item.resolution;
@@ -993,7 +1274,9 @@ export async function runRebuildCommand(options) {
993
1274
  }
994
1275
  const closureEntries = dependencyEnabled
995
1276
  ? [...new Map([...closureEntriesByPlatform.values()].flat().map((entry) => [entry.pluginId, entry])).values()]
996
- : selected;
1277
+ : options.command === 'cleanup' || options.command === 'uninstall'
1278
+ ? resolveClosureEntries(selected)
1279
+ : selected;
997
1280
  const rawExecutionSelections = options.all
998
1281
  ? liveTargets.flatMap((target) => {
999
1282
  const entry = selected.find((candidate) => candidate.pluginId === target.pluginId);
@@ -1034,12 +1317,23 @@ export async function runRebuildCommand(options) {
1034
1317
  if (options.runtime?.previewDependencyPlanOnly) {
1035
1318
  await options.runtime.onPreviewStage?.('inventory');
1036
1319
  }
1037
- const stateInventory = await inventoryCatalogSourceReferences({
1038
- cwd: options.commandOptions.cwd,
1039
- env: runtimeEnv,
1040
- });
1320
+ const stateInventory = await readOrdinaryInventory();
1041
1321
  if (options.command !== 'cleanup')
1042
1322
  assertCatalogSourceInventoryComplete(stateInventory);
1323
+ const hasCodexSelection = executionSelections.some((selection) => selection.platform === 'codex');
1324
+ const intentBeforeByScope = hasCodexSelection || dependencyEnabled
1325
+ ? {
1326
+ user: await readPluginIntentState({
1327
+ scope: 'user', cwd: options.commandOptions.cwd, env: runtimeEnv,
1328
+ }),
1329
+ project: await readPluginIntentState({
1330
+ scope: 'project', cwd: options.commandOptions.cwd, env: runtimeEnv,
1331
+ }),
1332
+ }
1333
+ : {
1334
+ user: createEmptyPluginIntentState(),
1335
+ project: createEmptyPluginIntentState(),
1336
+ };
1043
1337
  const commandProjectKey = await resolveCanonicalRepoProjectKey(options.commandOptions.cwd);
1044
1338
  const codexScopePathsAlias = codexProjectConfigAliasesUserHome(options.commandOptions.cwd, runtimeEnv);
1045
1339
  const selectedLegacy = selectedLegacyLiveReferences(stateInventory.references, new Set(executionSelections.map((selection) => selection.platform)), options.commandOptions.scope);
@@ -1121,22 +1415,33 @@ export async function runRebuildCommand(options) {
1121
1415
  nativeCliRunner: options.runtime?.nativeCliRunner ?? getDefaultNativeCliRunner(),
1122
1416
  reporter: options.runtime?.reporter ?? createNoopReporter(),
1123
1417
  debugLogger: options.runtime?.debugLogger ?? createNoopDebugLogger(),
1124
- runtimeLeaseHeld: options.runtime?.runtimeLeaseHeld,
1125
- lifecycleLeaseHeld: options.runtime?.lifecycleLeaseHeld,
1126
1418
  expectedCatalogConfigDigest: catalog.configDigest,
1127
1419
  };
1128
1420
  const inspection = createRebuildIntegration({
1129
1421
  context: executionContext,
1130
1422
  scriptTimeoutMs: options.commandOptions.scriptTimeoutMs,
1131
1423
  });
1132
- const createPlan = async (preparedInventory) => {
1133
- const inventory = preparedInventory ?? await inventoryCatalogSourceReferences({
1134
- cwd: options.commandOptions.cwd,
1135
- env: runtimeEnv,
1136
- });
1424
+ const selectedRootsForPlan = (plan) => [...new Map(plan.targets.flatMap((target) => target.relation === 'requested'
1425
+ ? [[target.pluginId, {
1426
+ pluginId: target.pluginId,
1427
+ platforms: plan.targets.flatMap((candidate) => candidate.relation === 'requested'
1428
+ && candidate.pluginId === target.pluginId
1429
+ ? [candidate.platform]
1430
+ : []),
1431
+ }]]
1432
+ : [])).values()];
1433
+ const createPlan = async (inventory) => {
1137
1434
  if (options.command !== 'cleanup')
1138
1435
  assertCatalogSourceInventoryComplete(inventory);
1436
+ const codexActivationSnapshots = executionSelections.some((selection) => selection.platform === 'codex')
1437
+ ? Object.fromEntries(await Promise.all(['user', 'project'].map(async (scope) => [
1438
+ scope,
1439
+ (await readCodexConfigFile(resolveCodexPaths(options.commandOptions.cwd, scope, runtimeEnv).configTomlPath)).snapshot,
1440
+ ])))
1441
+ : undefined;
1139
1442
  const targets = [];
1443
+ const placementNodes = [];
1444
+ const blockingTargets = new Map();
1140
1445
  for (const [order, { entry, platform }] of executionSelections.entries()) {
1141
1446
  const relation = selectedIds.has(entry.pluginId) ? 'requested' : 'dependency';
1142
1447
  let resolvedScope = options.commandOptions.scope;
@@ -1172,8 +1477,8 @@ export async function runRebuildCommand(options) {
1172
1477
  const hasUnexpectedPresent = result.components.some((entry) => entry.state === 'present' && !required.has(entry.component) && !optional.has(entry.component));
1173
1478
  const hasPartial = result.components.some((entry) => entry.state === 'partial');
1174
1479
  const hasAnyPresent = result.components.some((entry) => entry.state === 'present');
1175
- const state = !hasAnyPresent
1176
- && result.components.every((entry) => entry.state === 'absent')
1480
+ const hasAnyOwned = hasAnyPresent || hasPartial;
1481
+ const state = !hasAnyOwned
1177
1482
  ? 'absent'
1178
1483
  : hasPartial || hasUnexpectedPresent || !presentRequired
1179
1484
  ? 'partial'
@@ -1189,26 +1494,90 @@ export async function runRebuildCommand(options) {
1189
1494
  ? Promise.resolve({ state: 'absent', components: [], errors: [] })
1190
1495
  : inspectScope(otherScope),
1191
1496
  ]);
1192
- let state = requestedInspection.state;
1497
+ const surface = platform === 'codex'
1498
+ ? analyzeCodexSurface(entry.plugin.manifest)
1499
+ : undefined;
1500
+ const codexPluginId = `${entry.plugin.manifest.name}@${marketplaceNameFor(entry, platform, resolvedScope)}`;
1501
+ const placementState = (inspected, scope) => {
1502
+ if (platform !== 'codex'
1503
+ || surface?.classification !== 'native'
1504
+ || options.command === 'cleanup'
1505
+ || options.command === 'uninstall')
1506
+ return inspected.state;
1507
+ const nativePresent = inspected.components.some((component) => component.component.kind === 'native-plugin' && component.state === 'present');
1508
+ return nativePresent
1509
+ && codexActivationSnapshots?.[scope].plugins[codexPluginId]?.enabled === true
1510
+ ? 'present'
1511
+ : 'absent';
1512
+ };
1513
+ const requestedPlacementState = placementState(requestedInspection, options.commandOptions.scope);
1514
+ let otherPlacementState = placementState(otherInspection, otherScope);
1515
+ if (surface?.classification === 'native'
1516
+ && requestedPlacementState === 'present'
1517
+ && otherPlacementState === 'present') {
1518
+ otherPlacementState = 'absent';
1519
+ }
1520
+ const allowedScopes = platform === 'codex'
1521
+ ? [
1522
+ ...(surface?.ok ? ['user'] : []),
1523
+ ...(surface?.ok && !codexScopePathsAlias ? ['project'] : []),
1524
+ ]
1525
+ : ['project', 'user'];
1526
+ if (allowedScopes.length === 0)
1527
+ allowedScopes.push(options.commandOptions.scope);
1528
+ placementNodes.push({
1529
+ pluginId: entry.pluginId,
1530
+ pluginName: entry.plugin.manifest.name,
1531
+ platform,
1532
+ relation,
1533
+ depth: order,
1534
+ allowedScopes,
1535
+ current: {
1536
+ project: options.commandOptions.scope === 'project'
1537
+ ? requestedPlacementState
1538
+ : otherPlacementState,
1539
+ user: options.commandOptions.scope === 'user'
1540
+ ? requestedPlacementState
1541
+ : otherPlacementState,
1542
+ },
1543
+ });
1544
+ let state = requestedPlacementState;
1193
1545
  let action = relation === 'requested'
1194
1546
  ? options.command
1195
1547
  : options.command === 'reinstall' ? 'reinstall' : 'upgrade';
1196
1548
  let errorCode;
1197
1549
  const hasForeignCollision = [...requestedInspection.components, ...otherInspection.components]
1198
- .some((component) => component.diagnostic?.includes('foreign collision'));
1550
+ .some((component) => component.collision !== undefined);
1199
1551
  if (dependencyEnabled && hasForeignCollision) {
1200
1552
  action = 'block';
1201
1553
  errorCode = 'artifactCollision';
1202
1554
  }
1555
+ else if (dependencyEnabled
1556
+ && platform === 'codex'
1557
+ && relation === 'requested'
1558
+ && surface?.scopeOverlap === 'aw-owned-conflict'
1559
+ && options.runtime?.reconcileIntentOnly !== true
1560
+ && !(options.command === 'install'
1561
+ && intentBeforeByScope[otherScope].roots.some((root) => root.pluginId === entry.pluginId && root.platforms.includes(platform)))
1562
+ && otherInspection.components.some((component) => component.state !== 'absent'
1563
+ && component.component.kind !== 'native-plugin'
1564
+ && !component.component.identity.artifactKey.startsWith('activation-')
1565
+ && !component.component.identity.artifactKey.startsWith('marketplace-'))) {
1566
+ action = 'block';
1567
+ errorCode = 'codexExtensionScopeConflict';
1568
+ }
1203
1569
  else if (dependencyEnabled && relation === 'requested'
1204
1570
  && (requestedInspection.state === 'error'
1205
- || (requestedInspection.state === 'partial' && options.command !== 'reinstall'))) {
1571
+ || (requestedInspection.state === 'partial'
1572
+ && platform !== 'codex'
1573
+ && options.command !== 'reinstall'
1574
+ && options.command !== 'cleanup'))) {
1206
1575
  action = 'block';
1207
1576
  errorCode = 'dependencyNotReady';
1208
1577
  }
1209
1578
  if (!hasForeignCollision && relation === 'dependency'
1210
- && requestedInspection.state === 'present'
1211
- && otherInspection.state === 'present') {
1579
+ && requestedPlacementState === 'present'
1580
+ && otherPlacementState === 'present') {
1212
1581
  action = 'block';
1213
1582
  state = 'partial';
1214
1583
  errorCode = 'dependencyScopeConflict';
@@ -1221,24 +1590,26 @@ export async function runRebuildCommand(options) {
1221
1590
  }
1222
1591
  else if (!hasForeignCollision && relation === 'dependency'
1223
1592
  && options.commandOptions.scope === 'project'
1224
- && (otherInspection.state === 'partial' || otherInspection.state === 'error')) {
1593
+ && (otherPlacementState === 'partial' || otherPlacementState === 'error')) {
1225
1594
  resolvedScope = 'user';
1226
- state = otherInspection.state;
1227
- if (otherInspection.state === 'error') {
1595
+ state = otherPlacementState;
1596
+ if (otherPlacementState === 'error') {
1228
1597
  action = 'block';
1229
1598
  errorCode = 'dependencyNotReady';
1230
1599
  }
1231
1600
  }
1232
1601
  else if (!hasForeignCollision && relation === 'dependency'
1233
1602
  && options.commandOptions.scope === 'user'
1234
- && otherInspection.state !== 'absent') {
1603
+ && otherPlacementState !== 'absent') {
1235
1604
  action = 'block';
1236
1605
  errorCode = 'dependencyScopeTooNarrow';
1237
1606
  }
1238
1607
  const resolvedInspection = resolvedScope === options.commandOptions.scope
1239
1608
  ? requestedInspection
1240
1609
  : otherInspection;
1241
- state = resolvedInspection.state;
1610
+ state = resolvedScope === options.commandOptions.scope
1611
+ ? requestedPlacementState
1612
+ : otherPlacementState;
1242
1613
  if (!hasForeignCollision && !errorCode
1243
1614
  && relation === 'dependency' && options.command === 'install') {
1244
1615
  action = state === 'present' ? 'keep' : state === 'partial' ? 'reinstall' : 'install';
@@ -1249,13 +1620,6 @@ export async function runRebuildCommand(options) {
1249
1620
  state = resolvedInspection.state;
1250
1621
  errorCode ??= 'dependencyNotReady';
1251
1622
  }
1252
- if (!hasForeignCollision && relation === 'dependency'
1253
- && platform === 'codex'
1254
- && resolvedScope === 'project'
1255
- && (entry.plugin.manifest.apps || !hasCodexProjectManagedArtifacts(entry.plugin))) {
1256
- action = 'block';
1257
- errorCode = 'dependencyTargetUnsupported';
1258
- }
1259
1623
  if (entry.plugin.manifest.config?.schema
1260
1624
  && (action === 'install' || action === 'upgrade' || action === 'reinstall')) {
1261
1625
  const effective = await resolveEffectivePluginConfig({
@@ -1278,6 +1642,12 @@ export async function runRebuildCommand(options) {
1278
1642
  action = 'block';
1279
1643
  errorCode = 'codexScopePathAlias';
1280
1644
  }
1645
+ if (dependencyEnabled && platform === 'codex' && !surface?.ok) {
1646
+ action = 'block';
1647
+ errorCode = surface?.errorCode ?? 'codexNativeKindUnsupported';
1648
+ }
1649
+ if (errorCode)
1650
+ blockingTargets.set(`${entry.pluginId}\0${platform}`, errorCode);
1281
1651
  targets.push({
1282
1652
  pluginId: entry.pluginId,
1283
1653
  pluginName: entry.plugin.manifest.name,
@@ -1295,6 +1665,46 @@ export async function runRebuildCommand(options) {
1295
1665
  ...(errorCode ? { errorCode } : {}),
1296
1666
  });
1297
1667
  }
1668
+ if (dependencyEnabled) {
1669
+ const placement = createDependencyPlacementPlan({
1670
+ requestedPluginIds: selected.map((entry) => entry.pluginId),
1671
+ nodes: placementNodes,
1672
+ roots: placementNodes.flatMap((node) => selectedIds.has(node.pluginId)
1673
+ ? [{ pluginId: node.pluginId, platform: node.platform, scope: options.commandOptions.scope }]
1674
+ : []),
1675
+ dependencyEdges: closureEntries.flatMap((entry) => (entry.plugin.manifest.dependencies ?? []).flatMap((dependency) => {
1676
+ const dependencyId = formatPluginId({
1677
+ sourceId: entry.source.sourceId,
1678
+ pluginName: dependency,
1679
+ });
1680
+ return ['codex', 'claude'].flatMap((platform) => placementNodes.some((node) => node.pluginId === entry.pluginId && node.platform === platform)
1681
+ && placementNodes.some((node) => node.pluginId === dependencyId && node.platform === platform)
1682
+ ? [{ from: entry.pluginId, to: dependencyId, platform }]
1683
+ : []);
1684
+ })),
1685
+ scopeOverrides: effectiveDependencyScopeOverrides({
1686
+ state: intentBeforeByScope[options.commandOptions.scope],
1687
+ explicit: options.commandOptions
1688
+ .dependencyScopeOverrides ?? [],
1689
+ nodes: placementNodes,
1690
+ ignoredPersistedRootIds: selectedIds,
1691
+ }),
1692
+ operation: options.command,
1693
+ });
1694
+ return createDependencyOperationPlan({
1695
+ requestedPluginIds: placement.requestedPluginIds,
1696
+ resolvedPluginIds: placement.resolvedPluginIds,
1697
+ dependencyEdges: placement.dependencyEdges,
1698
+ scopeOverrides: placement.scopeOverrides,
1699
+ claimRoots: placement.claimRoots,
1700
+ targets: placement.targets.map((target) => {
1701
+ const errorCode = blockingTargets.get(`${target.pluginId}\0${target.platform}`);
1702
+ return errorCode
1703
+ ? { ...target, action: 'block', errorCode }
1704
+ : target;
1705
+ }),
1706
+ });
1707
+ }
1298
1708
  return createDependencyOperationPlan({
1299
1709
  requestedPluginIds: selected.map((entry) => entry.pluginId),
1300
1710
  resolvedPluginIds: closureEntries.map((entry) => entry.pluginId),
@@ -1305,11 +1715,302 @@ export async function runRebuildCommand(options) {
1305
1715
  targets,
1306
1716
  });
1307
1717
  };
1718
+ const createCodexPlans = async (plan, activationSupport = 'observed') => {
1719
+ const codexTargets = plan.targets.filter((target) => target.platform === 'codex');
1720
+ const selectedRoots = selectedRootsForPlan(plan);
1721
+ const currentScope = options.commandOptions.scope;
1722
+ const otherScope = currentScope === 'user' ? 'project' : 'user';
1723
+ const reconciledCodexTargets = codexTargets;
1724
+ if (reconciledCodexTargets.length === 0)
1725
+ return [];
1726
+ const beforeByScope = intentBeforeByScope;
1727
+ const afterByScope = {
1728
+ user: beforeByScope.user,
1729
+ project: beforeByScope.project,
1730
+ };
1731
+ afterByScope[currentScope] = deriveDesiredIntentState({
1732
+ command: options.command,
1733
+ state: beforeByScope[currentScope],
1734
+ selectedRoots,
1735
+ plan,
1736
+ reconcileIntentOnly: options.runtime?.reconcileIntentOnly,
1737
+ });
1738
+ if (options.command === 'install' && options.runtime?.reconcileIntentOnly !== true) {
1739
+ let other = afterByScope[otherScope];
1740
+ for (const root of selectedRoots)
1741
+ other = removePluginIntentRootPlatforms(other, root);
1742
+ afterByScope[otherScope] = other;
1743
+ }
1744
+ const desiredScopesForEntry = (entry) => {
1745
+ const scopes = new Set();
1746
+ for (const intentScope of ['user', 'project']) {
1747
+ const intent = afterByScope[intentScope];
1748
+ for (const root of intent.roots.filter((candidate) => candidate.platforms.includes('codex'))) {
1749
+ const rootEntry = available.find((candidate) => candidate.pluginId === root.pluginId);
1750
+ if (!rootEntry || rootEntry.source.sourceId !== entry.source.sourceId)
1751
+ continue;
1752
+ const sourcePlugins = available
1753
+ .filter((candidate) => candidate.source.sourceId === entry.source.sourceId)
1754
+ .map((candidate) => candidate.plugin);
1755
+ let closure;
1756
+ try {
1757
+ closure = resolvePluginMetaDependencyClosure(sourcePlugins, [rootEntry.plugin.manifest.name]);
1758
+ }
1759
+ catch {
1760
+ closure = [rootEntry.plugin];
1761
+ }
1762
+ if (!closure.some((plugin) => plugin.manifest.name === entry.plugin.manifest.name)) {
1763
+ continue;
1764
+ }
1765
+ const currentPlannedScope = intentScope === currentScope
1766
+ && selectedRoots.some((selectedRoot) => selectedRoot.pluginId === root.pluginId)
1767
+ ? plan.targets.find((target) => target.pluginId === entry.pluginId && target.platform === 'codex')?.resolvedScope
1768
+ : undefined;
1769
+ const override = intent.dependencyScopeOverrides.find((candidate) => candidate.pluginId === entry.pluginId && candidate.platform === 'codex');
1770
+ const reusedUserDependency = intentScope === 'project'
1771
+ && root.pluginId !== entry.pluginId
1772
+ && scopes.has('user');
1773
+ scopes.add(currentPlannedScope
1774
+ ?? override?.scope
1775
+ ?? (reusedUserDependency ? 'user' : intentScope));
1776
+ }
1777
+ }
1778
+ return [...scopes].sort();
1779
+ };
1780
+ const intentScopesForEntry = (entry) => {
1781
+ const scopes = new Set();
1782
+ for (const intentScope of ['user', 'project']) {
1783
+ for (const root of afterByScope[intentScope].roots.filter((candidate) => candidate.platforms.includes('codex'))) {
1784
+ const rootEntry = available.find((candidate) => candidate.pluginId === root.pluginId);
1785
+ if (!rootEntry || rootEntry.source.sourceId !== entry.source.sourceId)
1786
+ continue;
1787
+ const sourcePlugins = available
1788
+ .filter((candidate) => candidate.source.sourceId === entry.source.sourceId)
1789
+ .map((candidate) => candidate.plugin);
1790
+ let closure;
1791
+ try {
1792
+ closure = resolvePluginMetaDependencyClosure(sourcePlugins, [rootEntry.plugin.manifest.name]);
1793
+ }
1794
+ catch {
1795
+ closure = [rootEntry.plugin];
1796
+ }
1797
+ if (closure.some((plugin) => plugin.manifest.name === entry.plugin.manifest.name)) {
1798
+ scopes.add(intentScope);
1799
+ }
1800
+ }
1801
+ }
1802
+ return [...scopes].sort();
1803
+ };
1804
+ const inspectBothScopes = options.command !== 'cleanup' && options.command !== 'uninstall';
1805
+ const readActivationConfig = async (scope) => {
1806
+ return readCodexConfigFile(resolveCodexPaths(options.commandOptions.cwd, scope, runtimeEnv).configTomlPath);
1807
+ };
1808
+ const [userConfig, projectConfig] = await Promise.all([
1809
+ readActivationConfig('user'),
1810
+ readActivationConfig('project'),
1811
+ ]);
1812
+ const catalogClosure = [];
1813
+ const liveEvidence = [];
1814
+ for (const planned of reconciledCodexTargets) {
1815
+ const entry = available.find((candidate) => candidate.pluginId === planned.pluginId);
1816
+ const surface = analyzeCodexSurface(entry.plugin.manifest);
1817
+ const marketplaceName = marketplaceNameFor(entry, 'codex', planned.resolvedScope);
1818
+ const codexPluginId = `${entry.plugin.manifest.name}@${marketplaceName}`;
1819
+ const contentDigest = await calculateCodexSharedNativePayloadDigest(entry.plugin.rootDir);
1820
+ const desiredScopes = desiredScopesForEntry(entry);
1821
+ catalogClosure.push({
1822
+ pluginId: entry.pluginId,
1823
+ pluginName: entry.plugin.manifest.name,
1824
+ codexPluginId,
1825
+ version: entry.plugin.manifest.version,
1826
+ payloadDigest: contentDigest,
1827
+ relation: planned.relation,
1828
+ resolvedScope: planned.resolvedScope,
1829
+ desiredScopes,
1830
+ intentScopes: intentScopesForEntry(entry),
1831
+ surface,
1832
+ });
1833
+ const managedBefore = beforeByScope.user.managed.codexPluginIds.includes(codexPluginId)
1834
+ || beforeByScope.project.managed.codexPluginIds.includes(codexPluginId);
1835
+ const sharedInspection = surface.nativeKinds.length > 0
1836
+ ? await inspectCodexSharedNativeLive({
1837
+ pluginName: entry.plugin.manifest.name,
1838
+ desiredVersion: entry.plugin.manifest.version,
1839
+ expectedPayloadDigest: contentDigest,
1840
+ cwd: options.commandOptions.cwd,
1841
+ env: runtimeEnv,
1842
+ managedByCurrentIntent: managedBefore,
1843
+ marketplaceName,
1844
+ marketplaceRoot: path.join(resolveAwHome(runtimeEnv), 'marketplaces', entry.source.sourceId),
1845
+ })
1846
+ : undefined;
1847
+ const sharedNative = sharedInspection?.sharedNative
1848
+ ?? { state: 'absent', nativeAction: 'install' };
1849
+ const projectionEntries = [];
1850
+ const extensions = [];
1851
+ for (const scope of (inspectBothScopes
1852
+ ? ['user', 'project']
1853
+ : [currentScope])) {
1854
+ const desired = await inspection.buildTarget({
1855
+ sourceId: entry.source.sourceId,
1856
+ sourceIdentity: entry.source.sourceIdentity,
1857
+ pluginId: entry.pluginId,
1858
+ marketplaceName,
1859
+ marketplaceRoot: path.join(resolveAwHome(runtimeEnv), 'marketplaces', entry.source.sourceId),
1860
+ plugin: entry.plugin,
1861
+ platform: 'codex',
1862
+ scope,
1863
+ cwd: options.commandOptions.cwd,
1864
+ command: options.command,
1865
+ catalog: toCatalogResolution(entry.source),
1866
+ includeHistoricalLifecycleArtifacts: false,
1867
+ readOnlyInspectionPlan: true,
1868
+ });
1869
+ const inspected = await inspectOwnedComponents({
1870
+ components: desired.ownedComponents,
1871
+ nativeProbe: inspection.driver.probeNative,
1872
+ verifyHealth: true,
1873
+ });
1874
+ const projected = inspected.components.filter((component) => component.component.kind !== 'native-plugin'
1875
+ && !component.component.identity.artifactKey.startsWith('activation-')
1876
+ && !component.component.identity.artifactKey.startsWith('marketplace-'));
1877
+ projectionEntries.push(...projected.map((component) => ({
1878
+ scope,
1879
+ artifactKey: component.component.identity.artifactKey,
1880
+ state: component.state,
1881
+ collision: component.collision?.reason,
1882
+ })));
1883
+ const projectionState = projected.some((component) => component.collision)
1884
+ ? planned.action === 'block' ? 'absent' : 'foreign'
1885
+ : projected.some((component) => component.state === 'partial'
1886
+ || component.state === 'error')
1887
+ ? 'drifted'
1888
+ : projected.some((component) => component.state === 'present')
1889
+ ? 'present'
1890
+ : 'absent';
1891
+ for (const component of surface.components.filter((candidate) => candidate.owner === 'aw')) {
1892
+ extensions.push({ componentKey: component.key, scope, state: projectionState });
1893
+ }
1894
+ }
1895
+ liveEvidence.push({
1896
+ codexPluginId,
1897
+ sharedNative,
1898
+ ...(sharedInspection?.actualPayloadDigest
1899
+ ? { sharedNativePayloadDigest: sharedInspection.actualPayloadDigest }
1900
+ : {}),
1901
+ ...(sharedInspection?.payloadPath
1902
+ ? { sharedNativePayloadPath: sharedInspection.payloadPath }
1903
+ : {}),
1904
+ ...(sharedNative.state === 'corrupt' ? { repairableSharedNative: true } : {}),
1905
+ activations: {
1906
+ user: userConfig?.snapshot.plugins[codexPluginId]?.enabled,
1907
+ project: projectConfig?.snapshot.plugins[codexPluginId]?.enabled,
1908
+ },
1909
+ extensions,
1910
+ projectionDigest: JSON.stringify(projectionEntries.sort((left, right) => `${left.scope}\0${left.artifactKey}`.localeCompare(`${right.scope}\0${right.artifactKey}`, 'en'))),
1911
+ });
1912
+ }
1913
+ return [createCodexReconcilePlan({
1914
+ scope: currentScope,
1915
+ cwd: options.commandOptions.cwd,
1916
+ operation: options.runtime?.reconcileIntentOnly ? 'sync' : options.command,
1917
+ intentBefore: beforeByScope[currentScope],
1918
+ intentAfter: afterByScope[currentScope],
1919
+ relatedIntentBefore: beforeByScope[otherScope],
1920
+ relatedIntentAfter: afterByScope[otherScope],
1921
+ catalogClosure,
1922
+ liveEvidence,
1923
+ activationSupport,
1924
+ })];
1925
+ };
1926
+ const operationNeedsNativeCli = async (platform, plan) => {
1927
+ for (const { entry, platform: selectedPlatform } of executionSelections) {
1928
+ if (selectedPlatform !== platform)
1929
+ continue;
1930
+ const planned = plan?.targets.find((target) => target.pluginId === entry.pluginId && target.platform === selectedPlatform);
1931
+ if (dependencyEnabled && (!planned
1932
+ || planned.action === 'keep'
1933
+ || planned.action === 'block'))
1934
+ continue;
1935
+ const scope = planned?.resolvedScope ?? options.commandOptions.scope;
1936
+ const marketplaceName = marketplaceNameFor(entry, selectedPlatform, scope);
1937
+ entry.plugin.marketplaceName = marketplaceName;
1938
+ const marketplaceRoot = path.join(resolveAwHome(runtimeEnv), 'marketplaces', entry.source.sourceId);
1939
+ if (!dependencyEnabled && selectedPlatform === 'codex') {
1940
+ continue;
1941
+ }
1942
+ const target = await inspection.buildTarget({
1943
+ sourceId: entry.source.sourceId,
1944
+ sourceIdentity: entry.source.sourceIdentity,
1945
+ pluginId: entry.pluginId,
1946
+ marketplaceName,
1947
+ marketplaceRoot,
1948
+ plugin: entry.plugin,
1949
+ platform: selectedPlatform,
1950
+ scope,
1951
+ cwd: options.commandOptions.cwd,
1952
+ command: dependencyEnabled
1953
+ ? planned.action
1954
+ : options.command,
1955
+ catalog: toCatalogResolution(entry.source),
1956
+ includeHistoricalLifecycleArtifacts: false,
1957
+ readOnlyInspectionPlan: true,
1958
+ });
1959
+ if (dependencyEnabled && (target.writeNativeRequired ?? target.requiresNative))
1960
+ return true;
1961
+ const nativeComponents = target.ownedComponents.filter((component) => component.kind === 'native-plugin'
1962
+ || (component.kind === 'shared-config'
1963
+ && component.identity.artifactKey.startsWith('marketplace-')));
1964
+ if (nativeComponents.length === 0)
1965
+ continue;
1966
+ const nativeInspection = await inspectOwnedComponents({
1967
+ components: nativeComponents,
1968
+ nativeProbe: inspection.driver.probeNative,
1969
+ });
1970
+ if (nativeInspection.components.some((component) => component.state === 'present' || component.state === 'partial')) {
1971
+ return true;
1972
+ }
1973
+ }
1974
+ return false;
1975
+ };
1308
1976
  if (options.runtime?.previewDependencyPlanOnly) {
1309
1977
  await options.runtime.onPreviewStage?.('dependencyPlan');
1310
- const initialPlan = await createPlan(stateInventory);
1978
+ const initialPlan = dependencyEnabled
1979
+ ? await measureCommandPerformanceStage(options.runtime, 'dependency.plan', () => createPlan(stateInventory))
1980
+ : undefined;
1981
+ const provisionalCodexPlans = initialPlan ? await createCodexPlans(initialPlan) : [];
1982
+ const previewNeedsNativeMaterialization = provisionalCodexPlans.some((plan) => plan.sharedNativeActions.some((action) => action.action === 'install'
1983
+ || action.action === 'replace'
1984
+ || action.action === 'repair'));
1985
+ const previewActivationOnlyProject = !previewNeedsNativeMaterialization
1986
+ && provisionalCodexPlans.some((plan) => plan.requirement.requiresProjectPluginContract
1987
+ && plan.postcheck.plugins.length > 0);
1988
+ const previewConsumer = previewActivationOnlyProject
1989
+ ? await (options.runtime?.codexOperationConsumerResolver ?? resolveCodexOperationConsumer)({
1990
+ cwd: options.commandOptions.cwd,
1991
+ env: runtimeEnv,
1992
+ runner: options.runtime?.nativeCliRunner ?? getDefaultNativeCliRunner(),
1993
+ operationRequirement: getCodexOperationRequirement('project'),
1994
+ requiresNativeMaterialization: false,
1995
+ })
1996
+ : undefined;
1997
+ const initialCodexPlans = initialPlan && previewConsumer?.support === 'assumed'
1998
+ ? await createCodexPlans(initialPlan, 'assumed')
1999
+ : provisionalCodexPlans;
1311
2000
  const preview = mergeRebuildCommandResults([]);
1312
- preview.dependencyPlan = initialPlan;
2001
+ if (initialPlan)
2002
+ preview.dependencyPlan = initialPlan;
2003
+ if (initialCodexPlans.length > 0)
2004
+ preview.codexReconcilePlans = initialCodexPlans;
2005
+ if (initialCodexPlans.some((plan) => plan.sharedNativeActions.some((action) => action.action === 'install'
2006
+ || action.action === 'replace'
2007
+ || action.action === 'repair'))) {
2008
+ preview.codexManagerPreview = await (options.runtime.codexManagerPreviewer ?? previewCodexManager)({
2009
+ cwd: options.commandOptions.cwd,
2010
+ env: runtimeEnv,
2011
+ runner: options.runtime?.nativeCliRunner,
2012
+ });
2013
+ }
1313
2014
  return preview;
1314
2015
  }
1315
2016
  for (const { entry, platform } of executionSelections.filter((selection) => selectedIds.has(selection.entry.pluginId))) {
@@ -1326,6 +2027,9 @@ export async function runRebuildCommand(options) {
1326
2027
  cwd: options.commandOptions.cwd,
1327
2028
  },
1328
2029
  configDigest: catalog.configDigest,
2030
+ ...(catalogOperation
2031
+ ? { generationSetDigest: catalogOperation.generationSetDigest }
2032
+ : {}),
1329
2033
  sourceId: entry.source.sourceId,
1330
2034
  sourceIdentity: entry.source.sourceIdentity,
1331
2035
  resolvedCommit: entry.source.resolvedCommit,
@@ -1334,7 +2038,62 @@ export async function runRebuildCommand(options) {
1334
2038
  contentDigest: entry.source.manifest.entries.find((candidate) => candidate.name === entry.plugin.manifest.name)?.contentDigest,
1335
2039
  });
1336
2040
  }
1337
- const lockedPlan = await createPlan();
2041
+ const lockedPlan = await measureCommandPerformanceStage(options.runtime, 'dependency.plan', () => createPlan(stateInventory));
2042
+ const provisionalLockedCodexPlans = await createCodexPlans(lockedPlan);
2043
+ const codexManagerRequired = provisionalLockedCodexPlans.some((plan) => plan.sharedNativeActions.some((action) => action.action === 'install'
2044
+ || action.action === 'replace'
2045
+ || action.action === 'repair'));
2046
+ const activationOnlyProjectOperation = !codexManagerRequired
2047
+ && provisionalLockedCodexPlans.some((plan) => plan.requirement.requiresProjectPluginContract
2048
+ && plan.postcheck.plugins.length > 0);
2049
+ const activationConsumer = activationOnlyProjectOperation
2050
+ ? await (options.runtime?.codexOperationConsumerResolver ?? resolveCodexOperationConsumer)({
2051
+ cwd: options.commandOptions.cwd,
2052
+ env: runtimeEnv,
2053
+ runner: executionContext.nativeCliRunner,
2054
+ operationRequirement: getCodexOperationRequirement('project'),
2055
+ requiresNativeMaterialization: false,
2056
+ })
2057
+ : undefined;
2058
+ const lockedCodexPlans = activationConsumer?.support === 'assumed'
2059
+ ? await createCodexPlans(lockedPlan, 'assumed')
2060
+ : provisionalLockedCodexPlans;
2061
+ if (activationConsumer?.support === 'observed') {
2062
+ executionContext.codexManager = activationConsumer.manager;
2063
+ }
2064
+ if (options.runtime?.expectedOperationDependencyPlan
2065
+ && JSON.stringify(lockedPlan) !== JSON.stringify(options.runtime.expectedOperationDependencyPlan)) {
2066
+ throw namespacedPreflightError('operationReviewStale: dependency plan changed before mutation.', preflightTargets);
2067
+ }
2068
+ if (options.runtime?.expectedCodexReconcilePlans
2069
+ && JSON.stringify(lockedCodexPlans) !== JSON.stringify(options.runtime.expectedCodexReconcilePlans)) {
2070
+ throw namespacedPreflightError('operationReviewStale: Codex reconciliation evidence or plan changed before mutation.', preflightTargets);
2071
+ }
2072
+ if (options.runtime?.directCli
2073
+ && options.commandOptions.yes !== true
2074
+ && (options.command === 'install'
2075
+ || options.command === 'upgrade'
2076
+ || options.command === 'reinstall')) {
2077
+ const sharedNativeImpact = lockedCodexPlans.some((plan) => plan.review.requiresConfirmation);
2078
+ if (sharedNativeImpact) {
2079
+ throw namespacedPreflightError('sharedNativeImpactAuthorizationRequired: Replacing or force-reinstalling shared Codex native state requires --yes.', preflightTargets);
2080
+ }
2081
+ }
2082
+ let desiredIntent;
2083
+ if (options.runtime?.awHomeMutationToken
2084
+ && !lockedPlan.targets.some((target) => target.action === 'block')) {
2085
+ const selectedRoots = selectedRootsForPlan(lockedPlan);
2086
+ desiredIntent = await writeDesiredIntentSuperset({
2087
+ command: options.command,
2088
+ cwd: options.commandOptions.cwd,
2089
+ scope: options.commandOptions.scope,
2090
+ selectedRoots,
2091
+ plan: lockedPlan,
2092
+ codexPlan: lockedCodexPlans[0],
2093
+ reconcileIntentOnly: options.runtime?.reconcileIntentOnly,
2094
+ env: runtimeEnv,
2095
+ });
2096
+ }
1338
2097
  for (const pluginName of new Set(lockedPlan.targets.flatMap((target) => target.platform === 'claude'
1339
2098
  && (target.action === 'install'
1340
2099
  || target.action === 'upgrade'
@@ -1347,29 +2106,76 @@ export async function runRebuildCommand(options) {
1347
2106
  env: runtimeEnv,
1348
2107
  });
1349
2108
  }
1350
- await assertAgentVersions({
1351
- platforms: [...new Set(executionSelections.flatMap(({ entry, platform }) => supportsPlatform(entry.plugin.manifest, platform) ? [platform] : []))],
2109
+ const claudeCliRequired = await operationNeedsNativeCli('claude', lockedPlan);
2110
+ const codexUserActivationDesired = new Set(lockedCodexPlans.flatMap((plan) => plan.activationActions.user.flatMap((action) => action.desired ? [action.codexPluginId] : [])));
2111
+ executionContext.codexUserActivationDesired = codexUserActivationDesired;
2112
+ if (codexManagerRequired) {
2113
+ const codexRequirementScope = lockedCodexPlans.some((plan) => plan.requirement.requiresProjectPluginContract)
2114
+ ? 'project'
2115
+ : 'user';
2116
+ if (options.runtime?.codexManagerResolver && codexManagerRequired) {
2117
+ executionContext.codexManager = await options.runtime.codexManagerResolver({
2118
+ cwd: options.commandOptions.cwd,
2119
+ env: runtimeEnv,
2120
+ runner: executionContext.nativeCliRunner,
2121
+ authorizer: options.runtime?.managedCodexAuthorizer,
2122
+ operationRequirement: getCodexOperationRequirement(codexRequirementScope),
2123
+ });
2124
+ }
2125
+ else {
2126
+ const consumer = await (options.runtime?.codexOperationConsumerResolver ?? resolveCodexOperationConsumer)({
2127
+ cwd: options.commandOptions.cwd,
2128
+ env: runtimeEnv,
2129
+ runner: executionContext.nativeCliRunner,
2130
+ authorizer: options.runtime?.managedCodexAuthorizer,
2131
+ operationRequirement: getCodexOperationRequirement(codexRequirementScope),
2132
+ requiresNativeMaterialization: codexManagerRequired,
2133
+ });
2134
+ if (consumer.support !== 'observed') {
2135
+ throw new Error('Codex native materialization requires an observed CLI consumer.');
2136
+ }
2137
+ executionContext.codexManager = consumer.manager;
2138
+ }
2139
+ }
2140
+ await assertClaudeVersion({
2141
+ platforms: claudeCliRequired ? ['claude'] : [],
1352
2142
  cwd: options.commandOptions.cwd,
1353
2143
  env: runtimeEnv,
1354
- runtime: {
1355
- env: runtimeEnv,
1356
- nativeCliRunner: options.runtime?.nativeCliRunner ?? getDefaultNativeCliRunner(),
1357
- reporter: options.runtime?.reporter ?? createNoopReporter(),
1358
- debugLogger: options.runtime?.debugLogger ?? createNoopDebugLogger(),
1359
- },
2144
+ runtime: executionContext,
2145
+ });
2146
+ const executionIntegration = createRebuildIntegration({
2147
+ context: executionContext,
2148
+ scriptTimeoutMs: options.commandOptions.scriptTimeoutMs,
1360
2149
  });
1361
2150
  const preparedTargets = new Map();
1362
2151
  const preparedTargetErrors = new Map();
1363
2152
  const lockedCollisionSelections = [];
1364
2153
  for (const { entry, platform } of executionSelections) {
1365
2154
  const planned = lockedPlan.targets.find((candidate) => candidate.pluginId === entry.pluginId && candidate.platform === platform);
2155
+ const desiredCleanup = platform === 'codex'
2156
+ && options.command === 'cleanup'
2157
+ && lockedCodexPlans.some((plan) => plan.desiredTargets.some((target) => target.pluginId === entry.pluginId && target.scopes.includes(planned.resolvedScope)));
2158
+ const reconcileObsolete = platform === 'codex'
2159
+ && options.runtime?.reconcileIntentOnly === true
2160
+ && lockedCodexPlans.some((plan) => plan.desiredTargets.some((target) => target.pluginId === entry.pluginId && !target.scopes.includes(planned.resolvedScope)));
2161
+ if (reconcileObsolete)
2162
+ continue;
1366
2163
  if (planned.action !== 'install'
1367
2164
  && planned.action !== 'upgrade'
1368
- && planned.action !== 'reinstall')
2165
+ && planned.action !== 'reinstall'
2166
+ && planned.action !== 'move'
2167
+ && !desiredCleanup)
1369
2168
  continue;
2169
+ const buildCommand = desiredCleanup
2170
+ ? 'install'
2171
+ : planned.action === 'move'
2172
+ ? 'install'
2173
+ : planned.relation === 'dependency'
2174
+ ? planned.action
2175
+ : options.command;
1370
2176
  const marketplaceName = marketplaceNameFor(entry, platform, planned.resolvedScope);
1371
2177
  try {
1372
- const target = await inspection.buildTarget({
2178
+ const target = await measureCommandPerformanceStage(options.runtime, 'target.build', () => executionIntegration.buildTarget({
1373
2179
  sourceId: entry.source.sourceId,
1374
2180
  sourceIdentity: entry.source.sourceIdentity,
1375
2181
  pluginId: entry.pluginId,
@@ -1379,21 +2185,27 @@ export async function runRebuildCommand(options) {
1379
2185
  platform,
1380
2186
  scope: planned.resolvedScope,
1381
2187
  cwd: options.commandOptions.cwd,
1382
- command: planned.relation === 'dependency' ? planned.action : options.command,
2188
+ command: buildCommand,
1383
2189
  catalog: toCatalogResolution(entry.source),
1384
2190
  includeHistoricalLifecycleArtifacts: true,
1385
- });
2191
+ }));
2192
+ if (platform === 'codex') {
2193
+ const plannedNative = lockedCodexPlans.flatMap((plan) => plan.sharedNativeActions).find((action) => action.pluginId === entry.pluginId);
2194
+ if (plannedNative) {
2195
+ target.writeNativeRequired = plannedNative.action === 'install'
2196
+ || plannedNative.action === 'replace'
2197
+ || plannedNative.action === 'repair';
2198
+ }
2199
+ }
1386
2200
  preparedTargets.set(`${entry.pluginId}\0${platform}`, {
1387
2201
  target,
1388
- driver: inspection.driver,
2202
+ driver: executionIntegration.driver,
1389
2203
  context: executionContext,
1390
2204
  });
1391
- if (selectedIds.has(entry.pluginId)) {
1392
- await rebuildCommandTestHooks?.afterTargetBuilt?.({
1393
- command: options.command,
1394
- identity: target.identity,
1395
- });
1396
- }
2205
+ await rebuildCommandTestHooks?.afterTargetBuilt?.({
2206
+ command: options.command,
2207
+ identity: target.identity,
2208
+ });
1397
2209
  lockedCollisionSelections.push({
1398
2210
  pluginId: entry.pluginId,
1399
2211
  targetKey: `${platform}/${target.identity.scope}`,
@@ -1416,8 +2228,10 @@ export async function runRebuildCommand(options) {
1416
2228
  throw namespacedPreflightError(`artifactCollision: ${lockedCollisions.map((collision) => `${collision.targetKey} (${collision.pluginIds.join(', ')})`).join('; ')}`, preflightTargets);
1417
2229
  }
1418
2230
  const removalRequiredConsumers = new Map();
1419
- if ((options.command === 'cleanup' && !cleanupForce) || options.command === 'uninstall') {
1420
- const removalStateInventory = await inventoryCatalogSourceReferences({
2231
+ if ((options.command === 'cleanup' && !cleanupForce)
2232
+ || options.command === 'uninstall'
2233
+ || options.runtime?.reconcileIntentOnly === true) {
2234
+ const removalStateInventory = await readInventory({
1421
2235
  cwd: options.commandOptions.cwd,
1422
2236
  env: runtimeEnv,
1423
2237
  includeHistoricalProjects: true,
@@ -1560,6 +2374,32 @@ export async function runRebuildCommand(options) {
1560
2374
  scope: planned.resolvedScope,
1561
2375
  cwd: options.commandOptions.cwd,
1562
2376
  };
2377
+ const codexReconcilePlan = platform === 'codex'
2378
+ ? lockedCodexPlans.find((plan) => plan.desiredTargets.some((target) => target.pluginId === entry.pluginId))
2379
+ : undefined;
2380
+ const desiredInCurrentScope = codexReconcilePlan?.desiredTargets.some((target) => target.pluginId === entry.pluginId && target.scopes.includes(planned.resolvedScope)) === true;
2381
+ const reconcileObsolete = platform === 'codex'
2382
+ && options.runtime?.reconcileIntentOnly === true
2383
+ && !desiredInCurrentScope;
2384
+ const desiredCleanupMutation = codexReconcilePlan !== undefined && (codexReconcilePlan.sharedNativeActions.some((action) => action.pluginId === entry.pluginId
2385
+ && (action.action === 'install'
2386
+ || action.action === 'replace'
2387
+ || action.action === 'repair'))
2388
+ || [...codexReconcilePlan.activationActions.user,
2389
+ ...codexReconcilePlan.activationActions.project].some((action) => action.pluginId === entry.pluginId && action.action !== 'none')
2390
+ || [...codexReconcilePlan.extensionWrites,
2391
+ ...codexReconcilePlan.extensionRemovals].some((action) => action.pluginId === entry.pluginId));
2392
+ const plannedNativeAction = codexReconcilePlan?.sharedNativeActions.find((action) => action.pluginId === entry.pluginId);
2393
+ const hasCodexExtensionMutation = codexReconcilePlan !== undefined
2394
+ && [...codexReconcilePlan.extensionWrites, ...codexReconcilePlan.extensionRemovals]
2395
+ .some((action) => action.pluginId === entry.pluginId);
2396
+ const reconcileActivationOnly = platform === 'codex'
2397
+ && plannedNativeAction !== undefined
2398
+ && (plannedNativeAction.action === 'reuse'
2399
+ || plannedNativeAction.action === 'claim'
2400
+ || plannedNativeAction.action === 'none')
2401
+ && !hasCodexExtensionMutation
2402
+ && entry.plugin.manifest.scripts?.lifecycle?.uninstall === undefined;
1563
2403
  if (failedDependency || failedConsumer || planned.action === 'block') {
1564
2404
  const code = failedConsumer
1565
2405
  ? 'dependencyStillRequired'
@@ -1583,7 +2423,7 @@ export async function runRebuildCommand(options) {
1583
2423
  errorCode: code,
1584
2424
  message,
1585
2425
  }],
1586
- overallState: 'failed', nativePlugins: {}, repoManagedArtifacts: [],
2426
+ overallState: 'failed',
1587
2427
  receiptTargets: [{
1588
2428
  pluginName: identity.pluginName, pluginId: identity.pluginId,
1589
2429
  sourceId: identity.sourceId, platform, scope: planned.resolvedScope,
@@ -1592,7 +2432,68 @@ export async function runRebuildCommand(options) {
1592
2432
  dependencyStates.set(`${entry.pluginId}\0${platform}`, failedDependency || failedConsumer ? 'skipped' : 'failed');
1593
2433
  continue;
1594
2434
  }
1595
- if (planned.action === 'keep') {
2435
+ if (reconcileActivationOnly) {
2436
+ const preparedError = preparedTargetErrors.get(`${entry.pluginId}\0${platform}`);
2437
+ if (preparedError) {
2438
+ const message = preparedError instanceof Error
2439
+ ? preparedError.message
2440
+ : String(preparedError);
2441
+ const result = failedResult(options.command, identity, message);
2442
+ results.push({
2443
+ platforms: [platform], plugins: [], warnings: [], rebuildResults: [result],
2444
+ targetResults: [{
2445
+ target: identity,
2446
+ action: planned.action,
2447
+ state: 'failed',
2448
+ errorCode: message.split(':', 1)[0] ?? 'targetPreparationFailed',
2449
+ message,
2450
+ }],
2451
+ overallState: 'failed',
2452
+ receiptTargets: [{
2453
+ pluginName: identity.pluginName, pluginId: identity.pluginId,
2454
+ sourceId: identity.sourceId, platform, scope: planned.resolvedScope,
2455
+ }],
2456
+ });
2457
+ dependencyStates.set(`${entry.pluginId}\0${platform}`, 'failed');
2458
+ continue;
2459
+ }
2460
+ const result = {
2461
+ status: 'succeeded', command: options.command, target: identity, actions: [],
2462
+ cleanupAttempted: false, removed: [], absent: [], residual: [], quarantined: [], failures: [],
2463
+ };
2464
+ results.push({
2465
+ platforms: [platform], plugins: [identity.pluginName], warnings: [], rebuildResults: [result],
2466
+ targetResults: [{ target: identity, action: planned.action, state: 'completed' }],
2467
+ overallState: 'succeeded',
2468
+ receiptTargets: [{
2469
+ pluginName: identity.pluginName, pluginId: identity.pluginId,
2470
+ sourceId: identity.sourceId, platform, scope: planned.resolvedScope,
2471
+ }],
2472
+ });
2473
+ dependencyStates.set(`${entry.pluginId}\0${platform}`, 'completed');
2474
+ continue;
2475
+ }
2476
+ if (platform === 'codex'
2477
+ && desiredInCurrentScope
2478
+ && (options.command === 'uninstall'
2479
+ || (options.command === 'cleanup' && !desiredCleanupMutation))) {
2480
+ const result = {
2481
+ status: 'succeeded', command: options.command, target: identity, actions: [],
2482
+ cleanupAttempted: false, removed: [], absent: [], residual: [], quarantined: [], failures: [],
2483
+ };
2484
+ results.push({
2485
+ platforms: [platform], plugins: [identity.pluginName], warnings: [], rebuildResults: [result],
2486
+ targetResults: [{ target: identity, action: 'keep', state: 'completed' }],
2487
+ overallState: 'succeeded',
2488
+ receiptTargets: [{
2489
+ pluginName: identity.pluginName, pluginId: identity.pluginId,
2490
+ sourceId: identity.sourceId, platform, scope: planned.resolvedScope,
2491
+ }],
2492
+ });
2493
+ dependencyStates.set(`${entry.pluginId}\0${platform}`, 'kept');
2494
+ continue;
2495
+ }
2496
+ if (planned.action === 'keep' && !reconcileObsolete) {
1596
2497
  const result = {
1597
2498
  status: 'succeeded', command: options.command, target: identity, actions: [],
1598
2499
  cleanupAttempted: false, removed: [], absent: [], residual: [], quarantined: [], failures: [],
@@ -1600,7 +2501,7 @@ export async function runRebuildCommand(options) {
1600
2501
  results.push({
1601
2502
  platforms: [platform], plugins: [identity.pluginName], warnings: [], rebuildResults: [result],
1602
2503
  targetResults: [{ target: identity, action: 'keep', state: 'completed' }],
1603
- overallState: 'succeeded', nativePlugins: {}, repoManagedArtifacts: [],
2504
+ overallState: 'succeeded',
1604
2505
  receiptTargets: [{
1605
2506
  pluginName: identity.pluginName, pluginId: identity.pluginId,
1606
2507
  sourceId: identity.sourceId, platform, scope: planned.resolvedScope,
@@ -1617,20 +2518,34 @@ export async function runRebuildCommand(options) {
1617
2518
  marketplaceRoot: path.join(resolveAwHome(runtimeEnv), 'marketplaces', entry.source.sourceId),
1618
2519
  };
1619
2520
  entry.plugin.marketplaceName = marketplaceName;
2521
+ const reconcileDesiredCleanup = platform === 'codex'
2522
+ && options.command === 'cleanup'
2523
+ && desiredInCurrentScope;
1620
2524
  const executionResult = await runSingleSourceRebuildCommand({
1621
2525
  ...options,
1622
- command: planned.relation === 'dependency'
1623
- ? planned.action
1624
- : options.command,
2526
+ command: reconcileObsolete
2527
+ ? 'cleanup'
2528
+ : reconcileDesiredCleanup
2529
+ ? 'install'
2530
+ : planned.action === 'move'
2531
+ ? 'install'
2532
+ : planned.relation === 'dependency'
2533
+ ? planned.action
2534
+ : options.command,
1625
2535
  pluginNames: [entry.plugin.manifest.name],
1626
2536
  all: false,
1627
2537
  commandOptions: { ...options.commandOptions, platform, scope: planned.resolvedScope },
1628
2538
  runtime: {
1629
2539
  ...options.runtime,
1630
2540
  env: runtimeEnv,
2541
+ ...(executionContext.codexManager
2542
+ ? { codexManager: executionContext.codexManager }
2543
+ : {}),
1631
2544
  catalogResolution: toCatalogResolution(entry.source),
2545
+ resolvedCatalogPlugins: available
2546
+ .filter((candidate) => candidate.source.sourceId === entry.source.sourceId)
2547
+ .map((candidate) => candidate.plugin),
1632
2548
  namespacedSource,
1633
- operationLeaseHeld: true,
1634
2549
  dependencyReviewPreflightHandled: true,
1635
2550
  targetBuiltHookHandled: true,
1636
2551
  preparedTarget: preparedTargets.get(`${entry.pluginId}\0${platform}`),
@@ -1639,6 +2554,9 @@ export async function runRebuildCommand(options) {
1639
2554
  resolvedReviewEvidenceByPlugin: {
1640
2555
  [entry.plugin.manifest.name]: {
1641
2556
  configDigest: catalog.configDigest,
2557
+ ...(catalogOperation
2558
+ ? { generationSetDigest: catalogOperation.generationSetDigest }
2559
+ : {}),
1642
2560
  sourceId: entry.source.sourceId,
1643
2561
  sourceIdentity: entry.source.sourceIdentity,
1644
2562
  resolvedCommit: entry.source.resolvedCommit,
@@ -1649,15 +2567,119 @@ export async function runRebuildCommand(options) {
1649
2567
  },
1650
2568
  },
1651
2569
  });
2570
+ if (reconcileDesiredCleanup) {
2571
+ for (const result of executionResult.rebuildResults)
2572
+ result.command = 'cleanup';
2573
+ for (const result of executionResult.targetResults)
2574
+ result.action = 'cleanup';
2575
+ }
1652
2576
  results.push(executionResult);
1653
2577
  dependencyStates.set(`${entry.pluginId}\0${platform}`, executionResult.overallState === 'succeeded' ? 'completed' : 'failed');
1654
2578
  }
2579
+ if (lockedCodexPlans.length > 0
2580
+ && mergeRebuildCommandResults(results).overallState === 'succeeded') {
2581
+ try {
2582
+ await executeCodexReconcileActivations({
2583
+ plans: lockedCodexPlans,
2584
+ cwd: options.commandOptions.cwd,
2585
+ env: runtimeEnv,
2586
+ manager: executionContext.codexManager,
2587
+ runner: executionContext.nativeCliRunner,
2588
+ });
2589
+ }
2590
+ catch (error) {
2591
+ const selection = executionSelections.find((candidate) => candidate.platform === 'codex');
2592
+ if (!selection)
2593
+ throw error;
2594
+ const planned = lockedPlan.targets.find((target) => target.pluginId === selection.entry.pluginId && target.platform === 'codex');
2595
+ const identity = {
2596
+ sourceId: selection.entry.source.sourceId,
2597
+ sourceIdentity: selection.entry.source.sourceIdentity,
2598
+ pluginId: selection.entry.pluginId,
2599
+ pluginName: selection.entry.plugin.manifest.name,
2600
+ pluginVersion: selection.entry.plugin.manifest.version,
2601
+ platform: 'codex',
2602
+ scope: planned.resolvedScope,
2603
+ cwd: options.commandOptions.cwd,
2604
+ };
2605
+ const message = error instanceof Error ? error.message : String(error);
2606
+ const failed = failedResult(options.command, identity, message, 'postcheck.inspect');
2607
+ results.push({
2608
+ platforms: ['codex'],
2609
+ plugins: [],
2610
+ warnings: [],
2611
+ rebuildResults: [failed],
2612
+ targetResults: [{
2613
+ target: identity,
2614
+ action: options.command,
2615
+ state: 'failed',
2616
+ errorCode: message.split(':', 1)[0] ?? 'codexActivationPostcheckFailed',
2617
+ message,
2618
+ }],
2619
+ overallState: 'failed',
2620
+ receiptTargets: [{
2621
+ pluginName: identity.pluginName,
2622
+ pluginId: identity.pluginId,
2623
+ sourceId: identity.sourceId,
2624
+ platform: 'codex',
2625
+ scope: identity.scope,
2626
+ }],
2627
+ });
2628
+ }
2629
+ }
2630
+ if (options.command === 'install'
2631
+ && mergeRebuildCommandResults(results).overallState === 'succeeded') {
2632
+ for (const { entry, platform } of executionSelections) {
2633
+ const planned = lockedPlan.targets.find((target) => target.pluginId === entry.pluginId && target.platform === platform);
2634
+ if (planned.action !== 'move'
2635
+ || dependencyStates.get(`${entry.pluginId}\0${platform}`) !== 'completed')
2636
+ continue;
2637
+ const previousScope = planned.resolvedScope === 'user' ? 'project' : 'user';
2638
+ const marketplaceName = marketplaceNameFor(entry, platform, previousScope);
2639
+ entry.plugin.marketplaceName = marketplaceName;
2640
+ const migration = await runSingleSourceRebuildCommand({
2641
+ command: 'cleanup',
2642
+ pluginNames: [entry.plugin.manifest.name],
2643
+ commandOptions: { ...options.commandOptions, platform, scope: previousScope },
2644
+ runtime: {
2645
+ ...options.runtime,
2646
+ env: runtimeEnv,
2647
+ ...(executionContext.codexManager
2648
+ ? { codexManager: executionContext.codexManager }
2649
+ : {}),
2650
+ catalogResolution: toCatalogResolution(entry.source),
2651
+ resolvedCatalogPlugins: available
2652
+ .filter((candidate) => candidate.source.sourceId === entry.source.sourceId)
2653
+ .map((candidate) => candidate.plugin),
2654
+ namespacedSource: {
2655
+ sourceId: entry.source.sourceId,
2656
+ sourceIdentity: entry.source.sourceIdentity,
2657
+ marketplaceName,
2658
+ marketplaceRoot: path.join(resolveAwHome(runtimeEnv), 'marketplaces', entry.source.sourceId),
2659
+ },
2660
+ dependencyReviewPreflightHandled: true,
2661
+ bypassDependencyPlan: true,
2662
+ },
2663
+ });
2664
+ results.push(migration);
2665
+ if (migration.overallState !== 'succeeded') {
2666
+ dependencyStates.set(`${entry.pluginId}\0${platform}`, 'failed');
2667
+ }
2668
+ }
2669
+ }
1655
2670
  const merged = mergeRebuildCommandResults(results);
1656
2671
  merged.dependencyPlan = lockedPlan;
2672
+ if (lockedCodexPlans.length > 0)
2673
+ merged.codexReconcilePlans = lockedCodexPlans;
2674
+ if (desiredIntent && merged.overallState === 'succeeded') {
2675
+ for (const intent of desiredIntent) {
2676
+ await writePluginIntentState(intent.location, convergePluginIntentManagedCodexOwnership(intent.superset, intent.convergedCodexPluginIds));
2677
+ }
2678
+ }
1657
2679
  return leaseSafeRebuildResult(merged);
1658
2680
  },
1659
2681
  });
1660
- if (cleanupForce) {
2682
+ if (cleanupForce && !options.runtime?.previewDependencyPlanOnly) {
1661
2683
  try {
1662
2684
  await (options.runtime?.removeObsoleteInstallationContracts ?? fs.remove)(path.join(resolveAwHome(runtimeEnv), 'installation-contracts'));
1663
2685
  }
@@ -1672,37 +2694,16 @@ export async function runRebuildCommand(options) {
1672
2694
  const projected = error;
1673
2695
  error = namespacedPreflightError(error instanceof Error ? error.message : String(error), projected.namespacedTargets ?? liveTargets, projected.legacyTargets ?? legacyLiveTargets);
1674
2696
  }
1675
- if (options.command !== 'cleanup' && options.command !== 'uninstall')
2697
+ if (options.command !== 'uninstall')
1676
2698
  throw error;
1677
2699
  const message = error instanceof Error ? error.message : String(error);
1678
- const catalogUnavailable = message.startsWith('Cannot resolve required catalog sources:')
1679
- || message.includes('does not exist in the required catalog source set')
1680
- || (message.includes('Catalog source') && message.includes('does not exist'));
1681
- if (options.command === 'cleanup'
1682
- && (catalogUnavailable || message.includes('sourceChanged'))
1683
- && cleanupInventory
1684
- && cleanupLiveTargets.length > 0) {
1685
- if (!cleanupForce) {
1686
- await assertOfflineCleanupDependents({
1687
- targets: cleanupLiveTargets,
1688
- cwd: options.commandOptions.cwd,
1689
- env: runtimeEnv,
1690
- });
1691
- }
1692
- return cleanupOfflineOwnedComponents({
1693
- targets: cleanupLiveTargets,
1694
- cwd: options.commandOptions.cwd,
1695
- env: runtimeEnv,
1696
- force: cleanupForce,
1697
- removeObsoleteInstallationContracts: options.runtime?.removeObsoleteInstallationContracts,
1698
- nativeCliRunner: options.runtime?.nativeCliRunner,
1699
- corruptExclusiveDirectoryOperations: options.runtime?.corruptExclusiveDirectoryOperations,
1700
- });
1701
- }
2700
+ const catalogUnavailable = isOfflineCleanupCatalogGenerationError(error)
2701
+ || message.startsWith('Cannot resolve required catalog sources:')
2702
+ || message.includes('does not exist in the required catalog source set');
1702
2703
  if (options.command === 'uninstall' && catalogUnavailable) {
1703
2704
  throw namespacedPreflightError(`catalogSourceUnavailable: ${message} Restore the source to run lifecycle uninstall, or use cleanup; use cleanup --force only for exclusively owned damaged output.`, liveTargets, legacyLiveTargets);
1704
2705
  }
1705
- if ((message.includes('Catalog source') && message.includes('does not exist'))
2706
+ if (catalogGenerationErrorCode(error) === 'catalogGenerationSourceMissing'
1706
2707
  || message.includes('does not exist in the required catalog source set')) {
1707
2708
  throw namespacedPreflightError(`sourceMissing: ${message} Run cleanup for owner recovery, or cleanup --force for an exclusively owned damaged target.`, liveTargets, legacyLiveTargets);
1708
2709
  }