@lesliechan721/aw 0.1.0-beta.3 → 0.1.0-beta.31

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