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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. package/dist/authoring/scripts/add-skill.mjs +4 -4
  2. package/dist/authoring/scripts/add-skill.mjs.map +1 -1
  3. package/dist/authoring/scripts/init-plugin.mjs +8 -37
  4. package/dist/authoring/scripts/init-plugin.mjs.map +1 -1
  5. package/dist/authoring/scripts/lib/plugin-version-check.mjs +1 -1
  6. package/dist/authoring/scripts/lib/skill-scaffold.d.mts +77 -90
  7. package/dist/authoring/scripts/lib/skill-scaffold.mjs +99 -120
  8. package/dist/authoring/scripts/lib/skill-scaffold.mjs.map +1 -1
  9. package/dist/authoring/scripts/plugin-status.mjs +8 -11
  10. package/dist/authoring/scripts/plugin-status.mjs.map +1 -1
  11. package/dist/authoring/scripts/remove-plugin.mjs +2 -1
  12. package/dist/authoring/scripts/remove-plugin.mjs.map +1 -1
  13. package/dist/authoring/scripts/validate-plugin.mjs +64 -758
  14. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
  15. package/dist/scripts/aw-skill-eval-materializer.d.ts +24 -0
  16. package/dist/scripts/{plugin-eval-materializer.js → aw-skill-eval-materializer.js} +34 -145
  17. package/dist/scripts/aw-skill-eval-materializer.js.map +1 -0
  18. package/dist/scripts/bundle-plugins.d.ts +1 -0
  19. package/dist/scripts/bundle-plugins.js +2 -1
  20. package/dist/scripts/bundle-plugins.js.map +1 -1
  21. package/dist/scripts/plugin-staging.d.ts +6 -8
  22. package/dist/scripts/plugin-staging.js +15 -28
  23. package/dist/scripts/plugin-staging.js.map +1 -1
  24. package/dist/scripts/test-plugin-scripts.js +2 -33
  25. package/dist/scripts/test-plugin-scripts.js.map +1 -1
  26. package/dist/src/adapters/helpers.js +2 -2
  27. package/dist/src/adapters/helpers.js.map +1 -1
  28. package/dist/src/authoring/workspace-commands.d.ts +17 -1
  29. package/dist/src/authoring/workspace-commands.js +21 -4
  30. package/dist/src/authoring/workspace-commands.js.map +1 -1
  31. package/dist/src/commands/aw-capabilities.d.ts +78 -0
  32. package/dist/src/commands/aw-capabilities.js +254 -0
  33. package/dist/src/commands/aw-capabilities.js.map +1 -0
  34. package/dist/src/commands/catalog-dev-source.d.ts +118 -0
  35. package/dist/src/commands/catalog-dev-source.js +417 -0
  36. package/dist/src/commands/catalog-dev-source.js.map +1 -0
  37. package/dist/src/commands/catalog-generation.d.ts +13 -0
  38. package/dist/src/commands/catalog-generation.js +13 -0
  39. package/dist/src/commands/catalog-generation.js.map +1 -0
  40. package/dist/src/commands/catalog-publication-inspection.d.ts +29 -28
  41. package/dist/src/commands/catalog-publication-inspection.js +138 -50
  42. package/dist/src/commands/catalog-publication-inspection.js.map +1 -1
  43. package/dist/src/commands/catalog-publication-journal.d.ts +49 -0
  44. package/dist/src/commands/catalog-publication-journal.js +147 -0
  45. package/dist/src/commands/catalog-publication-journal.js.map +1 -0
  46. package/dist/src/commands/catalog-publication-target.d.ts +18 -0
  47. package/dist/src/commands/catalog-publication-target.js +95 -0
  48. package/dist/src/commands/catalog-publication-target.js.map +1 -0
  49. package/dist/src/commands/catalog-publish.d.ts +58 -15
  50. package/dist/src/commands/catalog-publish.js +590 -210
  51. package/dist/src/commands/catalog-publish.js.map +1 -1
  52. package/dist/src/commands/catalog-source.d.ts +12 -15
  53. package/dist/src/commands/catalog-source.js +61 -86
  54. package/dist/src/commands/catalog-source.js.map +1 -1
  55. package/dist/src/commands/catalog-status.d.ts +18 -22
  56. package/dist/src/commands/catalog-status.js +81 -115
  57. package/dist/src/commands/catalog-status.js.map +1 -1
  58. package/dist/src/commands/catalog-workspace-resolution.d.ts +13 -0
  59. package/dist/src/commands/catalog-workspace-resolution.js +42 -0
  60. package/dist/src/commands/catalog-workspace-resolution.js.map +1 -0
  61. package/dist/src/commands/dependency-operation-plan.d.ts +88 -9
  62. package/dist/src/commands/dependency-operation-plan.js +331 -6
  63. package/dist/src/commands/dependency-operation-plan.js.map +1 -1
  64. package/dist/src/commands/init.d.ts +5 -11
  65. package/dist/src/commands/init.js +30 -51
  66. package/dist/src/commands/init.js.map +1 -1
  67. package/dist/src/commands/offline-cleanup-evidence.d.ts +43 -0
  68. package/dist/src/commands/offline-cleanup-evidence.js +116 -0
  69. package/dist/src/commands/offline-cleanup-evidence.js.map +1 -0
  70. package/dist/src/commands/options.d.ts +7 -1
  71. package/dist/src/commands/options.js +65 -6
  72. package/dist/src/commands/options.js.map +1 -1
  73. package/dist/src/commands/plugin-capabilities.js +5 -1
  74. package/dist/src/commands/plugin-capabilities.js.map +1 -1
  75. package/dist/src/commands/rebuild-command.d.ts +27 -13
  76. package/dist/src/commands/rebuild-command.js +1472 -471
  77. package/dist/src/commands/rebuild-command.js.map +1 -1
  78. package/dist/src/commands/rebuild-integration.d.ts +63 -8
  79. package/dist/src/commands/rebuild-integration.js +761 -439
  80. package/dist/src/commands/rebuild-integration.js.map +1 -1
  81. package/dist/src/commands/rebuild-operations.d.ts +2 -8
  82. package/dist/src/commands/rebuild-operations.js +48 -132
  83. package/dist/src/commands/rebuild-operations.js.map +1 -1
  84. package/dist/src/commands/runtime.js +21 -34
  85. package/dist/src/commands/runtime.js.map +1 -1
  86. package/dist/src/commands/skill-eval.d.ts +33 -0
  87. package/dist/src/commands/skill-eval.js +363 -0
  88. package/dist/src/commands/skill-eval.js.map +1 -0
  89. package/dist/src/commands/status-v6.d.ts +202 -0
  90. package/dist/src/commands/status-v6.js +1938 -0
  91. package/dist/src/commands/status-v6.js.map +1 -0
  92. package/dist/src/commands/sync-command.d.ts +21 -0
  93. package/dist/src/commands/sync-command.js +206 -0
  94. package/dist/src/commands/sync-command.js.map +1 -0
  95. package/dist/src/commands/wizard.d.ts +2 -2
  96. package/dist/src/commands/wizard.js +2 -2
  97. package/dist/src/commands/wizard.js.map +1 -1
  98. package/dist/src/commands/write-commands.js +33 -19
  99. package/dist/src/commands/write-commands.js.map +1 -1
  100. package/dist/src/constants.d.ts +7 -1
  101. package/dist/src/constants.js +7 -1
  102. package/dist/src/constants.js.map +1 -1
  103. package/dist/src/eval/manifest-v1.d.ts +66 -0
  104. package/dist/src/eval/manifest-v1.js +326 -0
  105. package/dist/src/eval/manifest-v1.js.map +1 -0
  106. package/dist/src/generators/local-plugin.d.ts +3 -0
  107. package/dist/src/generators/local-plugin.js +167 -45
  108. package/dist/src/generators/local-plugin.js.map +1 -1
  109. package/dist/src/index.d.ts +6 -1
  110. package/dist/src/index.js +863 -126
  111. package/dist/src/index.js.map +1 -1
  112. package/dist/src/output/command-receipt.d.ts +58 -8
  113. package/dist/src/output/command-receipt.js +141 -11
  114. package/dist/src/output/command-receipt.js.map +1 -1
  115. package/dist/src/output/model.d.ts +3 -44
  116. package/dist/src/output/model.js +1 -144
  117. package/dist/src/output/model.js.map +1 -1
  118. package/dist/src/output/rebuild-progress.js +0 -3
  119. package/dist/src/output/rebuild-progress.js.map +1 -1
  120. package/dist/src/portable/commands.d.ts +6 -2
  121. package/dist/src/portable/commands.js +57 -6
  122. package/dist/src/portable/commands.js.map +1 -1
  123. package/dist/src/portable/materializer.d.ts +4 -4
  124. package/dist/src/portable/materializer.js +61 -12
  125. package/dist/src/portable/materializer.js.map +1 -1
  126. package/dist/src/tui/catalog-generation.d.ts +35 -0
  127. package/dist/src/tui/catalog-generation.js +85 -0
  128. package/dist/src/tui/catalog-generation.js.map +1 -0
  129. package/dist/src/tui/components.js +1 -2
  130. package/dist/src/tui/components.js.map +1 -1
  131. package/dist/src/tui/console-app-v4.d.ts +1 -1
  132. package/dist/src/tui/console-app-v4.js +647 -196
  133. package/dist/src/tui/console-app-v4.js.map +1 -1
  134. package/dist/src/tui/controller-runtime.d.ts +6 -2
  135. package/dist/src/tui/controller-runtime.js +646 -137
  136. package/dist/src/tui/controller-runtime.js.map +1 -1
  137. package/dist/src/tui/controller.d.ts +77 -15
  138. package/dist/src/tui/controller.js +647 -306
  139. package/dist/src/tui/controller.js.map +1 -1
  140. package/dist/src/tui/diagnostics.d.ts +1 -2
  141. package/dist/src/tui/diagnostics.js.map +1 -1
  142. package/dist/src/tui/index.d.ts +1 -0
  143. package/dist/src/tui/index.js +1 -0
  144. package/dist/src/tui/index.js.map +1 -1
  145. package/dist/src/tui/input-display.d.ts +8 -0
  146. package/dist/src/tui/input-display.js +36 -0
  147. package/dist/src/tui/input-display.js.map +1 -0
  148. package/dist/src/tui/model.d.ts +102 -14
  149. package/dist/src/tui/model.js +630 -139
  150. package/dist/src/tui/model.js.map +1 -1
  151. package/dist/src/tui/router.d.ts +35 -0
  152. package/dist/src/tui/router.js.map +1 -1
  153. package/dist/src/tui/source-ref-presets.d.ts +3 -0
  154. package/dist/src/tui/source-ref-presets.js +11 -0
  155. package/dist/src/tui/source-ref-presets.js.map +1 -0
  156. package/dist/src/tui/views.js +31 -3
  157. package/dist/src/tui/views.js.map +1 -1
  158. package/dist/src/types.d.ts +104 -13
  159. package/dist/src/types.js.map +1 -1
  160. package/dist/src/utils/agent-version.d.ts +7 -1
  161. package/dist/src/utils/agent-version.js +41 -8
  162. package/dist/src/utils/agent-version.js.map +1 -1
  163. package/dist/src/utils/aw-home-mutation-lock.d.ts +1 -0
  164. package/dist/src/utils/aw-home-mutation-lock.js +32 -4
  165. package/dist/src/utils/aw-home-mutation-lock.js.map +1 -1
  166. package/dist/src/utils/bundle-integrity.d.ts +0 -1
  167. package/dist/src/utils/bundle-integrity.js +0 -25
  168. package/dist/src/utils/bundle-integrity.js.map +1 -1
  169. package/dist/src/utils/bundle-layout.js +8 -6
  170. package/dist/src/utils/bundle-layout.js.map +1 -1
  171. package/dist/src/utils/catalog-digests.d.ts +7 -0
  172. package/dist/src/utils/catalog-digests.js +40 -2
  173. package/dist/src/utils/catalog-digests.js.map +1 -1
  174. package/dist/src/utils/catalog-generation-control-snapshot.d.ts +11 -0
  175. package/dist/src/utils/catalog-generation-control-snapshot.js +45 -0
  176. package/dist/src/utils/catalog-generation-control-snapshot.js.map +1 -0
  177. package/dist/src/utils/catalog-generation-error.d.ts +13 -0
  178. package/dist/src/utils/catalog-generation-error.js +28 -0
  179. package/dist/src/utils/catalog-generation-error.js.map +1 -0
  180. package/dist/src/utils/catalog-generation-resolver.d.ts +20 -0
  181. package/dist/src/utils/catalog-generation-resolver.js +143 -0
  182. package/dist/src/utils/catalog-generation-resolver.js.map +1 -0
  183. package/dist/src/utils/catalog-generation-service.d.ts +43 -0
  184. package/dist/src/utils/catalog-generation-service.js +306 -0
  185. package/dist/src/utils/catalog-generation-service.js.map +1 -0
  186. package/dist/src/utils/catalog-generation-state.d.ts +17 -0
  187. package/dist/src/utils/catalog-generation-state.js +168 -0
  188. package/dist/src/utils/catalog-generation-state.js.map +1 -0
  189. package/dist/src/utils/catalog-generation-store.d.ts +34 -0
  190. package/dist/src/utils/catalog-generation-store.js +251 -0
  191. package/dist/src/utils/catalog-generation-store.js.map +1 -0
  192. package/dist/src/utils/catalog-manifest-v6.d.ts +5 -0
  193. package/dist/src/utils/{catalog-manifest-v4.js → catalog-manifest-v6.js} +35 -43
  194. package/dist/src/utils/catalog-manifest-v6.js.map +1 -0
  195. package/dist/src/utils/catalog-operation-context.d.ts +20 -0
  196. package/dist/src/utils/catalog-operation-context.js +141 -0
  197. package/dist/src/utils/catalog-operation-context.js.map +1 -0
  198. package/dist/src/utils/catalog-public-error.d.ts +7 -3
  199. package/dist/src/utils/catalog-public-error.js +21 -8
  200. package/dist/src/utils/catalog-public-error.js.map +1 -1
  201. package/dist/src/utils/catalog-runtime-projection.d.ts +3 -0
  202. package/dist/src/utils/catalog-runtime-projection.js +5 -40
  203. package/dist/src/utils/catalog-runtime-projection.js.map +1 -1
  204. package/dist/src/utils/catalog-snapshot.d.ts +5 -3
  205. package/dist/src/utils/catalog-snapshot.js +11 -3
  206. package/dist/src/utils/catalog-snapshot.js.map +1 -1
  207. package/dist/src/utils/catalog-source-identity.d.ts +1 -0
  208. package/dist/src/utils/catalog-source-identity.js +21 -2
  209. package/dist/src/utils/catalog-source-identity.js.map +1 -1
  210. package/dist/src/utils/catalog-source-references.d.ts +7 -0
  211. package/dist/src/utils/catalog-source-references.js +44 -55
  212. package/dist/src/utils/catalog-source-references.js.map +1 -1
  213. package/dist/src/utils/catalog-sources.d.ts +1 -0
  214. package/dist/src/utils/catalog-sources.js +55 -20
  215. package/dist/src/utils/catalog-sources.js.map +1 -1
  216. package/dist/src/utils/codex-config.d.ts +2 -0
  217. package/dist/src/utils/codex-config.js +26 -5
  218. package/dist/src/utils/codex-config.js.map +1 -1
  219. package/dist/src/utils/codex-manager.d.ts +81 -0
  220. package/dist/src/utils/codex-manager.js +810 -0
  221. package/dist/src/utils/codex-manager.js.map +1 -0
  222. package/dist/src/utils/codex-reconciler.d.ts +131 -0
  223. package/dist/src/utils/codex-reconciler.js +327 -0
  224. package/dist/src/utils/codex-reconciler.js.map +1 -0
  225. package/dist/src/utils/codex-shared-native.d.ts +56 -0
  226. package/dist/src/utils/codex-shared-native.js +87 -0
  227. package/dist/src/utils/codex-shared-native.js.map +1 -0
  228. package/dist/src/utils/codex-skill-staging.d.ts +2 -0
  229. package/dist/src/utils/codex-skill-staging.js.map +1 -1
  230. package/dist/src/utils/codex-surface-analysis.d.ts +44 -0
  231. package/dist/src/utils/codex-surface-analysis.js +247 -0
  232. package/dist/src/utils/codex-surface-analysis.js.map +1 -0
  233. package/dist/src/utils/dynamic-skill-tree.d.ts +4 -0
  234. package/dist/src/utils/dynamic-skill-tree.js +35 -5
  235. package/dist/src/utils/dynamic-skill-tree.js.map +1 -1
  236. package/dist/src/utils/git-catalog-provider.js +66 -140
  237. package/dist/src/utils/git-catalog-provider.js.map +1 -1
  238. package/dist/src/utils/lifecycle-active-store.d.ts +0 -28
  239. package/dist/src/utils/lifecycle-active-store.js +35 -247
  240. package/dist/src/utils/lifecycle-active-store.js.map +1 -1
  241. package/dist/src/utils/manifest.d.ts +1 -3
  242. package/dist/src/utils/manifest.js +1 -3
  243. package/dist/src/utils/manifest.js.map +1 -1
  244. package/dist/src/utils/multi-source-catalog.d.ts +3 -9
  245. package/dist/src/utils/multi-source-catalog.js +53 -110
  246. package/dist/src/utils/multi-source-catalog.js.map +1 -1
  247. package/dist/src/utils/native-cli.d.ts +49 -6
  248. package/dist/src/utils/native-cli.js +116 -33
  249. package/dist/src/utils/native-cli.js.map +1 -1
  250. package/dist/src/utils/orphan-marketplace-cleanup.js +1 -3
  251. package/dist/src/utils/orphan-marketplace-cleanup.js.map +1 -1
  252. package/dist/src/utils/ownership.d.ts +10 -11
  253. package/dist/src/utils/ownership.js +55 -112
  254. package/dist/src/utils/ownership.js.map +1 -1
  255. package/dist/src/utils/path-catalog-provider.d.ts +1 -1
  256. package/dist/src/utils/path-catalog-provider.js +52 -65
  257. package/dist/src/utils/path-catalog-provider.js.map +1 -1
  258. package/dist/src/utils/paths.d.ts +0 -1
  259. package/dist/src/utils/paths.js +0 -1
  260. package/dist/src/utils/paths.js.map +1 -1
  261. package/dist/src/utils/plugin-config-write.js +18 -40
  262. package/dist/src/utils/plugin-config-write.js.map +1 -1
  263. package/dist/src/utils/plugin-config.d.ts +1 -12
  264. package/dist/src/utils/plugin-config.js +107 -226
  265. package/dist/src/utils/plugin-config.js.map +1 -1
  266. package/dist/src/utils/plugin-intent-state.d.ts +39 -0
  267. package/dist/src/utils/plugin-intent-state.js +325 -0
  268. package/dist/src/utils/plugin-intent-state.js.map +1 -0
  269. package/dist/src/utils/plugin-scripts.d.ts +1 -1
  270. package/dist/src/utils/plugin-scripts.js +1 -1
  271. package/dist/src/utils/plugin-scripts.js.map +1 -1
  272. package/dist/src/utils/state-paths.js +7 -2
  273. package/dist/src/utils/state-paths.js.map +1 -1
  274. package/dist/src/utils/target-eligibility.d.ts +8 -2
  275. package/dist/src/utils/target-eligibility.js +9 -5
  276. package/dist/src/utils/target-eligibility.js.map +1 -1
  277. package/package.json +6 -4
  278. package/dist/authoring/scripts/check-plugin-versions.d.mts +0 -2
  279. package/dist/authoring/scripts/check-plugin-versions.mjs +0 -5
  280. package/dist/authoring/scripts/check-plugin-versions.mjs.map +0 -1
  281. package/dist/scripts/aw-skill-eval-engine.d.ts +0 -13
  282. package/dist/scripts/aw-skill-eval-engine.js +0 -31
  283. package/dist/scripts/aw-skill-eval-engine.js.map +0 -1
  284. package/dist/scripts/codex-eval-app-server.d.ts +0 -89
  285. package/dist/scripts/codex-eval-app-server.js +0 -1228
  286. package/dist/scripts/codex-eval-app-server.js.map +0 -1
  287. package/dist/scripts/codex-eval-isolation.d.ts +0 -61
  288. package/dist/scripts/codex-eval-isolation.js +0 -318
  289. package/dist/scripts/codex-eval-isolation.js.map +0 -1
  290. package/dist/scripts/codex-eval-process-management.d.ts +0 -8
  291. package/dist/scripts/codex-eval-process-management.js +0 -69
  292. package/dist/scripts/codex-eval-process-management.js.map +0 -1
  293. package/dist/scripts/codex-eval-process-tree.d.ts +0 -38
  294. package/dist/scripts/codex-eval-process-tree.js +0 -803
  295. package/dist/scripts/codex-eval-process-tree.js.map +0 -1
  296. package/dist/scripts/codex-eval-runtime-config.d.ts +0 -62
  297. package/dist/scripts/codex-eval-runtime-config.js +0 -843
  298. package/dist/scripts/codex-eval-runtime-config.js.map +0 -1
  299. package/dist/scripts/codex-trigger-eval.d.ts +0 -84
  300. package/dist/scripts/codex-trigger-eval.js +0 -968
  301. package/dist/scripts/codex-trigger-eval.js.map +0 -1
  302. package/dist/scripts/eval-plugin-skills-audited.d.ts +0 -6
  303. package/dist/scripts/eval-plugin-skills-audited.js +0 -261
  304. package/dist/scripts/eval-plugin-skills-audited.js.map +0 -1
  305. package/dist/scripts/eval-plugin-skills.d.ts +0 -45
  306. package/dist/scripts/eval-plugin-skills.js +0 -1586
  307. package/dist/scripts/eval-plugin-skills.js.map +0 -1
  308. package/dist/scripts/eval-run-layout.d.ts +0 -17
  309. package/dist/scripts/eval-run-layout.js +0 -26
  310. package/dist/scripts/eval-run-layout.js.map +0 -1
  311. package/dist/scripts/plugin-eval-contract.d.ts +0 -185
  312. package/dist/scripts/plugin-eval-contract.js +0 -695
  313. package/dist/scripts/plugin-eval-contract.js.map +0 -1
  314. package/dist/scripts/plugin-eval-materializer.d.ts +0 -13
  315. package/dist/scripts/plugin-eval-materializer.js.map +0 -1
  316. package/dist/scripts/skill-reporting.d.ts +0 -212
  317. package/dist/scripts/skill-reporting.js +0 -922
  318. package/dist/scripts/skill-reporting.js.map +0 -1
  319. package/dist/src/commands/status-v4.d.ts +0 -123
  320. package/dist/src/commands/status-v4.js +0 -905
  321. package/dist/src/commands/status-v4.js.map +0 -1
  322. package/dist/src/commands/status.d.ts +0 -93
  323. package/dist/src/commands/status.js +0 -442
  324. package/dist/src/commands/status.js.map +0 -1
  325. package/dist/src/utils/active-marketplace-runtime-lock.d.ts +0 -12
  326. package/dist/src/utils/active-marketplace-runtime-lock.js +0 -133
  327. package/dist/src/utils/active-marketplace-runtime-lock.js.map +0 -1
  328. package/dist/src/utils/catalog-manifest-v3.d.ts +0 -2
  329. package/dist/src/utils/catalog-manifest-v3.js +0 -3
  330. package/dist/src/utils/catalog-manifest-v3.js.map +0 -1
  331. package/dist/src/utils/catalog-manifest-v4.d.ts +0 -5
  332. package/dist/src/utils/catalog-manifest-v4.js.map +0 -1
  333. package/dist/src/utils/catalog-source-cache.d.ts +0 -62
  334. package/dist/src/utils/catalog-source-cache.js +0 -296
  335. package/dist/src/utils/catalog-source-cache.js.map +0 -1
  336. package/dist/src/utils/codex-project-surface.d.ts +0 -3
  337. package/dist/src/utils/codex-project-surface.js +0 -49
  338. package/dist/src/utils/codex-project-surface.js.map +0 -1
  339. package/dist/src/utils/plugin-release.d.ts +0 -2
  340. package/dist/src/utils/plugin-release.js +0 -2
  341. package/dist/src/utils/plugin-release.js.map +0 -1
@@ -1,34 +1,153 @@
1
1
  import crypto from 'node:crypto';
2
+ import os from 'node:os';
2
3
  import path from 'node:path';
3
4
  import fs from 'fs-extra';
4
5
  import { generateAgentArtifacts } from '../generators/agents.js';
5
- import { extractCodexHookGroups } from '../generators/hooks.js';
6
6
  import { generateInstructionArtifacts, renderCodexManagedInstructionBlock, } from '../generators/instructions.js';
7
7
  import { generateCodexPermissionArtifacts, mergeClaudePermissions, } from '../generators/permissions.js';
8
8
  import { buildGeneratedName } from '../generators/shared.js';
9
- import { applyConditionalCodexConfigTransactionFile, applyCodexConfigTransactionFile, composeManagedDeveloperInstructionsBlock, readCodexConfigFile, readRenderedCodexMcpServersFile, } from '../utils/codex-config.js';
9
+ import { applyConditionalCodexConfigTransactionFile, applyCodexConfigTransactionFile, composeManagedDeveloperInstructionsBlock, readCodexConfigFile, } from '../utils/codex-config.js';
10
+ import { canonicalizePathThroughExistingAncestor } from '../utils/state-paths.js';
10
11
  import { createCodexSkillExpectedTree, createCodexSkillStagingTreeContentDigest, } from '../utils/codex-skill-staging.js';
11
12
  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';
13
+ import { parseCodexSharedHooksFile, serializeTrackedHookGroupIdentity, } from '../utils/hooks-contract.js';
14
+ import { buildCodexMarketplaceAddInvocation, buildCodexMarketplaceListInvocation, buildCodexMarketplaceRemoveInvocation, buildCodexPluginAddInvocation, buildCodexPluginListInvocation, buildCodexProjectPluginListInvocations, buildCodexPluginRemoveInvocation, postcheckCodexProjectPluginActivation, runNativeCliChecked, } from '../utils/native-cli.js';
15
+ import { OWNED_TEXT_HEADER_PREFIX, createSharedOwnerToken, parseSharedOwnerToken, parseOwnedTextHeader, readSkillOwnerMarker, inspectOwnedComponents, writeOwnedTextFile, } from '../utils/ownership.js';
15
16
  import { resolveArtifactRenderConfig, runArtifactRenderer } from '../utils/artifact-renderer.js';
16
17
  import { validateArtifactPlan } from '../utils/artifact-plan.js';
17
- import { readPluginManifest } from '../utils/manifest.js';
18
+ import { createAwPluginRequirementsReceipt, readPluginManifest, } 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
21
  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';
22
+ import { atomicRemoveConfigFile, atomicReplaceConfigFile, withConfigFileMutation, } from '../utils/plugin-config.js';
23
23
  import { readCatalogSourcesConfigSnapshot } from '../utils/catalog-sources.js';
24
24
  import { getNamespacedMarketplaceName, projectMarketplaceManifestForPlatform, replayNamespacedCodexSkillOverlays, } from '../utils/local-marketplace.js';
25
25
  import { cleanupOrphanNamespacedMarketplaceRoot, commitMarketplaceGenerationBackup, } from '../utils/orphan-marketplace-cleanup.js';
26
26
  import { readClaudeSettings, writeClaudeSettings } from '../adapters/helpers.js';
27
- import { requiresCodexNativePlugin } from '../utils/codex-project-surface.js';
27
+ import { analyzeCodexSurface } from '../utils/codex-surface-analysis.js';
28
+ import { inspectCodexSharedNative, } from '../utils/codex-shared-native.js';
29
+ import { calculateCodexSharedNativePayloadDigest } from '../utils/catalog-digests.js';
28
30
  import { assertCanonicalProjectManagedRoots } from '../utils/project-boundary.js';
29
31
  import { findCodexRoleNameConflict, resolveCodexRoleNamespace, } from '../utils/codex-roles.js';
30
32
  const MARKETPLACE_NAME = 'aw';
31
- const MCP_OWNER_ENV = 'AW_AGENT_WORKFLOWS_OWNER';
33
+ const LEGACY_MCP_OWNER_ENV = 'AW_AGENT_WORKFLOWS_OWNER';
34
+ function requirementsReceipt(plugin) {
35
+ const receipt = createAwPluginRequirementsReceipt({ name: plugin.manifest.name, version: plugin.manifest.version }, plugin.manifest.requires?.aw.capabilities ?? []);
36
+ return Buffer.from(`${JSON.stringify(receipt, null, 2)}\n`, 'utf8');
37
+ }
38
+ function requireCodexManager(manager) {
39
+ if (!manager) {
40
+ throw new Error('codexManagerRequired: Codex native operation has no resolved manager.');
41
+ }
42
+ return manager;
43
+ }
44
+ export async function executeCodexReconcileActivations(options) {
45
+ const byScope = {
46
+ user: { upserts: {}, removals: new Set() },
47
+ project: { upserts: {}, removals: new Set() },
48
+ };
49
+ for (const plan of options.plans) {
50
+ for (const scope of ['user', 'project']) {
51
+ for (const action of plan.activationActions[scope]) {
52
+ if (action.action === 'enable' || action.action === 'disable') {
53
+ byScope[scope].upserts[action.codexPluginId] = action.desired;
54
+ byScope[scope].removals.delete(action.codexPluginId);
55
+ }
56
+ else if (action.action === 'remove') {
57
+ byScope[scope].removals.add(action.codexPluginId);
58
+ }
59
+ }
60
+ }
61
+ for (const removal of plan.staleActivationRemovals) {
62
+ if (!Object.hasOwn(byScope[removal.scope].upserts, removal.codexPluginId)) {
63
+ byScope[removal.scope].removals.add(removal.codexPluginId);
64
+ }
65
+ }
66
+ }
67
+ for (const scope of ['user', 'project']) {
68
+ const enables = Object.fromEntries(Object.entries(byScope[scope].upserts)
69
+ .filter(([, desired]) => desired));
70
+ if (Object.keys(enables).length === 0)
71
+ continue;
72
+ const configPath = resolveCodexPaths(options.cwd, scope, options.env).configTomlPath;
73
+ await applyCodexConfigTransactionFile(configPath, { upsertPluginActivations: enables });
74
+ const enabledConfig = await readCodexConfigFile(configPath);
75
+ for (const pluginId of Object.keys(enables)) {
76
+ if (enabledConfig.snapshot.plugins[pluginId]?.enabled !== true) {
77
+ throw new Error(`codexActivationPostcheckFailed: ${pluginId} ${scope} activation was not enabled before obsolete activation removal.`);
78
+ }
79
+ }
80
+ }
81
+ for (const scope of ['user', 'project']) {
82
+ const transaction = byScope[scope];
83
+ const disables = Object.fromEntries(Object.entries(transaction.upserts)
84
+ .filter(([, desired]) => !desired));
85
+ if (Object.keys(disables).length === 0 && transaction.removals.size === 0)
86
+ continue;
87
+ await applyCodexConfigTransactionFile(resolveCodexPaths(options.cwd, scope, options.env).configTomlPath, {
88
+ ...(transaction.removals.size > 0
89
+ ? { removePluginActivations: [...transaction.removals].sort() }
90
+ : {}),
91
+ ...(Object.keys(disables).length > 0
92
+ ? { upsertPluginActivations: disables }
93
+ : {}),
94
+ });
95
+ }
96
+ const [userConfig, projectConfig] = await Promise.all([
97
+ readCodexConfigFile(resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath),
98
+ readCodexConfigFile(resolveCodexPaths(options.cwd, 'project', options.env).configTomlPath),
99
+ ]);
100
+ for (const plan of options.plans) {
101
+ for (const scope of ['user', 'project']) {
102
+ const snapshot = scope === 'user' ? userConfig.snapshot : projectConfig.snapshot;
103
+ for (const action of plan.activationActions[scope]) {
104
+ const actual = snapshot.plugins[action.codexPluginId]?.enabled;
105
+ const expected = action.desired
106
+ ? true
107
+ : action.action === 'disable' || actual === false ? false : undefined;
108
+ if (actual !== expected) {
109
+ throw new Error(`codexActivationPostcheckFailed: ${action.codexPluginId} ${scope} activation is ${String(actual)}; expected ${String(expected)}.`);
110
+ }
111
+ }
112
+ for (const removal of plan.staleActivationRemovals.filter((entry) => entry.scope === scope)) {
113
+ if (snapshot.plugins[removal.codexPluginId]?.enabled !== undefined) {
114
+ throw new Error(`codexActivationPostcheckFailed: stale ${removal.codexPluginId} ${scope} activation remains.`);
115
+ }
116
+ }
117
+ }
118
+ }
119
+ const observedProjectChecks = options.plans.flatMap((plan) => plan.postcheck.support === 'observed'
120
+ ? plan.postcheck.plugins.filter((plugin) => plugin.neutralCwdRequired)
121
+ : []);
122
+ if (observedProjectChecks.length === 0)
123
+ return;
124
+ const manager = requireCodexManager(options.manager);
125
+ const neutralCwd = await fs.mkdtemp(path.join(os.tmpdir(), 'aw-codex-postcheck-'));
126
+ try {
127
+ const invocations = buildCodexProjectPluginListInvocations(manager, {
128
+ exactCwd: options.cwd,
129
+ neutralCwd,
130
+ env: options.env,
131
+ });
132
+ const [exactResult, neutralResult] = await Promise.all([
133
+ runNativeCliChecked(invocations.exact, options.runner),
134
+ runNativeCliChecked(invocations.neutral, options.runner),
135
+ ]);
136
+ for (const plugin of observedProjectChecks) {
137
+ postcheckCodexProjectPluginActivation({
138
+ pluginId: plugin.codexPluginId,
139
+ userEnabled: plugin.expectedUserEnabled,
140
+ projectEnabled: plugin.expectedProjectEnabled,
141
+ exactResult,
142
+ neutralResult,
143
+ });
144
+ }
145
+ }
146
+ finally {
147
+ await fs.remove(neutralCwd);
148
+ }
149
+ }
150
+ const CODEX_REMOVE_RECOVERY = 'Codex residue was retained. Retry cleanup or uninstall, upgrade AW, or install a compatible official Codex CLI.';
32
151
  function ownerIdentity(plugin, artifactKey, platform, scope) {
33
152
  return { owner: 'agent-workflows', plugin, artifactKey, platform, scope };
34
153
  }
@@ -60,7 +179,7 @@ function pathComponent(options) {
60
179
  return {
61
180
  ...options,
62
181
  validateHealth: async () => {
63
- await validateSkillDirectory(options.path, options.identity.platform === 'codex' ? 'codex.skill' : 'claude.skill', { ownerMarkerPolicy: 'ignore' });
182
+ await validateSkillDirectory(options.path, options.identity.platform === 'codex' ? 'codex.skill' : 'claude.skill', { ownerMarkerPolicy: 'ignore', requirementsReceiptPolicy: 'allow' });
64
183
  },
65
184
  };
66
185
  }
@@ -192,84 +311,85 @@ function createCodexInstructionComponent(options) {
192
311
  },
193
312
  };
194
313
  }
195
- function codexMcpOwner(actual) {
196
- const envValue = actual?.value?.env;
197
- return envValue
198
- && typeof envValue === 'object'
199
- && !Array.isArray(envValue)
200
- && typeof envValue[MCP_OWNER_ENV] === 'string'
201
- ? envValue[MCP_OWNER_ENV]
202
- : null;
314
+ function createCodexActivationComponent(options) {
315
+ return {
316
+ kind: 'shared-config',
317
+ identity: options.identity,
318
+ label: `${options.configPath}#plugins.${options.codexPluginId}.enabled`,
319
+ inspect: async () => {
320
+ const plugin = (await readCodexConfigFile(options.configPath))
321
+ .snapshot.plugins[options.codexPluginId];
322
+ return plugin?.enabled === options.desired ? 'present' : 'absent';
323
+ },
324
+ remove: async () => {
325
+ await applyCodexConfigTransactionFile(options.configPath, {
326
+ removePluginActivations: [options.codexPluginId],
327
+ });
328
+ },
329
+ };
203
330
  }
204
- function createCodexMcpComponent(options) {
205
- const ownerKey = createSharedOwnerToken(options.identity);
331
+ function createCodexActivationCleanupComponent(options) {
206
332
  return {
207
333
  kind: 'shared-config',
208
334
  identity: options.identity,
209
- label: `${options.configPath}#mcp_servers.${options.serverKey}`,
335
+ label: `${options.configPath}#plugins.${options.codexPluginId}.enabled`,
210
336
  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';
337
+ const actual = (await readCodexConfigFile(options.configPath))
338
+ .snapshot.plugins[options.codexPluginId]?.enabled;
339
+ return options.disposition === 'remove'
340
+ ? actual === undefined ? 'absent' : 'present'
341
+ : actual === true ? 'present' : 'absent';
216
342
  },
217
343
  remove: async () => {
218
344
  await applyConditionalCodexConfigTransactionFile(options.configPath, (snapshot) => {
219
- const actual = snapshot.mcpServers[options.serverKey];
220
- return codexMcpOwner(actual) === ownerKey
221
- ? { removeMcpServers: [options.serverKey] }
345
+ const actual = snapshot.plugins[options.codexPluginId]?.enabled;
346
+ if (options.disposition === 'remove') {
347
+ return actual === undefined
348
+ ? null
349
+ : { removePluginActivations: [options.codexPluginId] };
350
+ }
351
+ return actual === true
352
+ ? { upsertPluginActivations: { [options.codexPluginId]: false } }
222
353
  : null;
223
354
  });
224
355
  },
225
356
  };
226
357
  }
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
- }
358
+ function legacyCodexMcpOwner(actual) {
359
+ const envValue = actual?.value?.env;
360
+ return envValue
361
+ && typeof envValue === 'object'
362
+ && !Array.isArray(envValue)
363
+ && typeof envValue[LEGACY_MCP_OWNER_ENV] === 'string'
364
+ ? envValue[LEGACY_MCP_OWNER_ENV]
365
+ : null;
241
366
  }
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
- };
367
+ function createLegacyCodexMcpCleanupComponent(options) {
368
+ const ownerKey = createSharedOwnerToken(options.identity);
252
369
  return {
253
370
  kind: 'shared-config',
254
371
  identity: options.identity,
255
- label: `${options.configPath}#agents.${options.roleKey}`,
256
- inspect,
372
+ label: `${options.configPath}#mcp_servers.${options.serverKey}`,
373
+ inspect: async () => {
374
+ const actual = (await readCodexConfigFile(options.configPath))
375
+ .snapshot.mcpServers[options.serverKey];
376
+ if (!actual)
377
+ return 'absent';
378
+ return legacyCodexMcpOwner(actual) === ownerKey ? 'present' : 'foreign';
379
+ },
257
380
  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
- });
381
+ await applyConditionalCodexConfigTransactionFile(options.configPath, (snapshot) => legacyCodexMcpOwner(snapshot.mcpServers[options.serverKey]) === ownerKey
382
+ ? { removeMcpServers: [options.serverKey] }
383
+ : null);
264
384
  },
265
385
  };
266
386
  }
267
- async function readCodexHooks(hooksPath) {
387
+ async function readLegacyCodexHooks(hooksPath) {
268
388
  if (!await fs.pathExists(hooksPath))
269
389
  return parseCodexSharedHooksFile({ hooks: {} });
270
390
  return parseCodexSharedHooksFile(await fs.readJson(hooksPath));
271
391
  }
272
- function renderCodexHookGroups(groups) {
392
+ function renderLegacyCodexHookGroups(groups) {
273
393
  const hooks = {};
274
394
  for (const group of groups) {
275
395
  hooks[group.event] ??= [];
@@ -277,16 +397,15 @@ function renderCodexHookGroups(groups) {
277
397
  }
278
398
  return `${JSON.stringify({ hooks }, null, 2)}\n`;
279
399
  }
280
- export async function applyCodexHookGroupsTransactionFile(hooksPath, update, testHooks) {
281
- return withConfigFileLock({
400
+ async function applyLegacyCodexHookCleanupTransactionFile(hooksPath, update) {
401
+ return withConfigFileMutation({
282
402
  filePath: hooksPath,
283
403
  allowedRoot: path.dirname(hooksPath),
284
404
  action: async () => {
285
- const parsed = await readCodexHooks(hooksPath);
405
+ const parsed = await readLegacyCodexHooks(hooksPath);
286
406
  const groups = update(parsed);
287
407
  if (!groups)
288
408
  return false;
289
- await testHooks?.beforeCommit?.();
290
409
  if (groups.length === 0) {
291
410
  await atomicRemoveConfigFile({
292
411
  filePath: hooksPath,
@@ -297,40 +416,27 @@ export async function applyCodexHookGroupsTransactionFile(hooksPath, update, tes
297
416
  await atomicReplaceConfigFile({
298
417
  filePath: hooksPath,
299
418
  allowedRoot: path.dirname(hooksPath),
300
- content: renderCodexHookGroups(groups),
419
+ content: renderLegacyCodexHookGroups(groups),
301
420
  });
302
421
  }
303
422
  return true;
304
423
  },
305
424
  });
306
425
  }
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) {
426
+ function createLegacyCodexHookCleanupComponent(options) {
321
427
  const serialized = serializeTrackedHookGroupIdentity(options.identity);
322
428
  return {
323
429
  kind: 'shared-config',
324
430
  identity: ownerIdentity(options.pluginName, internalArtifactKey('hook', serialized), 'codex', options.scope),
325
431
  label: `${options.hooksPath}#${serialized}`,
326
432
  inspect: async () => {
327
- const parsed = await readCodexHooks(options.hooksPath);
433
+ const parsed = await readLegacyCodexHooks(options.hooksPath);
328
434
  return parsed.managedIdentities.some((entry) => serializeTrackedHookGroupIdentity(entry) === serialized)
329
435
  ? 'present'
330
436
  : 'absent';
331
437
  },
332
438
  remove: async () => {
333
- await applyCodexHookGroupsTransactionFile(options.hooksPath, (parsed) => {
439
+ await applyLegacyCodexHookCleanupTransactionFile(options.hooksPath, (parsed) => {
334
440
  let removed = false;
335
441
  const remaining = parsed.groups.flatMap((entry) => {
336
442
  if (!removed
@@ -346,19 +452,44 @@ function createCodexHookComponent(options) {
346
452
  },
347
453
  };
348
454
  }
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
- } : {}),
455
+ async function codexRoleRegistrationOwned(options) {
456
+ if (!options.registration?.configFile)
457
+ return false;
458
+ const expected = path.normalize(path.relative(path.dirname(options.configPath), options.rolePath));
459
+ const actual = path.normalize(path.relative(path.dirname(options.configPath), path.resolve(path.dirname(options.configPath), options.registration.configFile)));
460
+ if (actual !== expected)
461
+ return false;
462
+ const raw = await fs.readFile(options.rolePath, 'utf8').catch(() => '');
463
+ try {
464
+ return Boolean(parseOwnedTextHeader(raw, options.fileIdentity));
465
+ }
466
+ catch {
467
+ return false;
468
+ }
469
+ }
470
+ function createCodexRoleRegistrationComponent(options) {
471
+ const inspect = async () => {
472
+ const config = await readCodexConfigFile(options.configPath);
473
+ const registration = config.snapshot.agents[options.roleKey];
474
+ if (!registration)
475
+ return 'absent';
476
+ return await codexRoleRegistrationOwned({ ...options, registration })
477
+ ? 'present'
478
+ : 'foreign';
358
479
  };
359
480
  return {
360
- ...options.identity,
361
- command: `${OWNED_TEXT_HEADER_PREFIX}${JSON.stringify(JSON.parse(createSkillOwnerMarkerContent(identity)))}\n${options.identity.command}`,
481
+ kind: 'shared-config',
482
+ identity: options.identity,
483
+ label: `${options.configPath}#agents.${options.roleKey}`,
484
+ inspect,
485
+ remove: async () => {
486
+ await applyConditionalCodexConfigTransactionFile(options.configPath, async (snapshot) => {
487
+ const registration = snapshot.agents[options.roleKey];
488
+ return await codexRoleRegistrationOwned({ ...options, registration })
489
+ ? { removeAgentRegistrations: [options.roleKey] }
490
+ : null;
491
+ });
492
+ },
362
493
  };
363
494
  }
364
495
  function sameOwnerIdentity(left, right) {
@@ -371,17 +502,6 @@ function sameOwnerIdentity(left, right) {
371
502
  && left.platform === right.platform
372
503
  && left.scope === right.scope;
373
504
  }
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
505
  function sharedOwnerPluginName(value) {
386
506
  return parseSharedOwnerToken(value)?.plugin ?? value;
387
507
  }
@@ -405,7 +525,6 @@ async function scanCodexSharedComponents(options) {
405
525
  remove: async () => undefined,
406
526
  });
407
527
  const containsOwnedEvidence = (raw) => raw.includes(options.pluginName) && (raw.includes('[[aw-managed:start plugin=')
408
- || raw.includes(MCP_OWNER_ENV)
409
528
  || raw.includes(OWNED_TEXT_HEADER_PREFIX)
410
529
  || raw.includes('agent-workflows'));
411
530
  if (options.scanConfig) {
@@ -427,24 +546,6 @@ async function scanCodexSharedComponents(options) {
427
546
  configPath: options.paths.configTomlPath,
428
547
  }));
429
548
  }
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
549
  for (const [roleKey, registration] of Object.entries(config?.snapshot.agents ?? {})) {
449
550
  if (!registration.configFile)
450
551
  continue;
@@ -469,28 +570,6 @@ async function scanCodexSharedComponents(options) {
469
570
  }));
470
571
  }
471
572
  }
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
573
  return output;
495
574
  }
496
575
  async function scanClaudeSharedComponents(options) {
@@ -536,6 +615,8 @@ async function scanOwnedTextFiles(options) {
536
615
  }
537
616
  if (parsed?.identity.plugin !== options.pluginName
538
617
  || (options.sourceId !== undefined && parsed.identity.sourceId !== options.sourceId)
618
+ || (options.sourceIdentity !== undefined
619
+ && parsed.identity.sourceIdentity !== options.sourceIdentity)
539
620
  || parsed.identity.platform !== options.platform
540
621
  || parsed.identity.scope !== options.scope)
541
622
  continue;
@@ -566,6 +647,7 @@ async function scanOwnedSkills(options) {
566
647
  }).catch(() => null);
567
648
  if (marker?.plugin === options.pluginName
568
649
  && (options.sourceId === undefined || marker.sourceId === options.sourceId)
650
+ && (options.sourceIdentity === undefined || marker.sourceIdentity === options.sourceIdentity)
569
651
  && marker.platform === options.platform
570
652
  && marker.scope === options.scope) {
571
653
  output.push(pathComponent({
@@ -602,14 +684,19 @@ async function preflightCodexWriteSafety(options) {
602
684
  const pluginName = options.plugin.manifest.name;
603
685
  const userConfigPath = resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath;
604
686
  const projectConfigPath = resolveCodexPaths(options.cwd, 'project', options.env).configTomlPath;
687
+ const nativeSurface = analyzeCodexSurface(options.plugin.manifest).nativeKinds.length > 0;
688
+ const needsUserConfig = options.scope === 'user'
689
+ || nativeSurface;
605
690
  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)}`);
691
+ if (needsUserConfig) {
692
+ try {
693
+ userConfig = await readCodexConfigFile(userConfigPath);
694
+ }
695
+ catch (error) {
696
+ throw new Error(`codexProjectUserConfigUnreadable: 无法解析用户级 Codex 配置 ${userConfigPath}:${error instanceof Error ? error.message : String(error)}`);
697
+ }
611
698
  }
612
- if (options.scope === 'project') {
699
+ if (options.scope === 'project' && userConfig) {
613
700
  const expectedPluginId = `${pluginName}@${options.plugin.marketplaceName ?? MARKETPLACE_NAME}`;
614
701
  for (const [key, plugin] of Object.entries(userConfig.snapshot.plugins)) {
615
702
  if (key !== expectedPluginId
@@ -624,7 +711,7 @@ async function preflightCodexWriteSafety(options) {
624
711
  && target.target === 'codex.agentRole'
625
712
  && target.roleKey)
626
713
  .map((target) => target.roleKey));
627
- const needsProjectConfig = options.scope === 'project' && Boolean(options.plugin.manifest.mcpServers
714
+ const needsProjectConfig = options.scope === 'project' && Boolean(nativeSurface
628
715
  || resolvePlatformEntries(options.plugin.manifest.extensions?.instructions, 'codex').length > 0
629
716
  || (options.plugin.manifest.artifacts?.targets ?? []).some((target) => target.platform === 'codex'
630
717
  && target.scopes.includes('project')
@@ -641,7 +728,7 @@ async function preflightCodexWriteSafety(options) {
641
728
  if (roleKeys.size === 0)
642
729
  return;
643
730
  for (const configPath of options.scope === 'project'
644
- ? [userConfigPath, projectConfigPath]
731
+ ? [projectConfigPath]
645
732
  : [userConfigPath]) {
646
733
  const configScope = configPath === userConfigPath ? 'user' : 'project';
647
734
  const ownedRolePaths = [];
@@ -692,33 +779,6 @@ function renderCodexRole(options) {
692
779
  '',
693
780
  ].join('\n');
694
781
  }
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
782
  async function resolveDynamicArtifacts(options) {
723
783
  const declarations = (options.plugin.manifest.artifacts?.targets ?? []).filter((target) => target.platform === options.platform && target.scopes.includes(options.scope));
724
784
  if (declarations.length === 0)
@@ -832,9 +892,7 @@ async function buildBundledCodexSkillWrites(options) {
832
892
  const dynamicKeys = new Set((options.plugin.manifest.artifacts?.targets ?? [])
833
893
  .filter((target) => target.target === 'codex.skill' && target.skillKey)
834
894
  .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('/'));
895
+ const targetRoot = await canonicalizePathThroughExistingAncestor(path.join(options.marketplaceRoot ?? resolveAwHome(options.env), 'plugins', options.plugin.manifest.name, ...relativeRoot.split('/')));
838
896
  const writes = [];
839
897
  for (const entry of await fs.readdir(sourceRoot, { withFileTypes: true })) {
840
898
  if (entry.isSymbolicLink()) {
@@ -853,11 +911,12 @@ async function buildBundledCodexSkillWrites(options) {
853
911
  content: { files: {} },
854
912
  },
855
913
  sourceDirs: [skillSource],
914
+ sourceRequirementsReceiptPolicy: 'allow',
856
915
  });
857
916
  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);
917
+ const canRecoverPristine = await canonicalizePathThroughExistingAncestor(skillSource)
918
+ !== path.resolve(skillPath);
919
+ const identity = ownerIdentity(options.plugin.manifest.name, entry.name, 'codex', 'user');
861
920
  const component = pathComponent({
862
921
  kind: 'skill-directory',
863
922
  path: skillPath,
@@ -903,6 +962,8 @@ async function buildClaudeWrites(options) {
903
962
  artifact,
904
963
  sourceDirs: dynamic.sourceDirs(artifact),
905
964
  requireReadme: true,
965
+ requirementsReceipt: requirementsReceipt(options.plugin),
966
+ sourceRequirementsReceiptPolicy: 'allow',
906
967
  });
907
968
  writes.push(dynamicSkillWrite({
908
969
  ...options,
@@ -955,22 +1016,23 @@ async function buildCodexWrites(options) {
955
1016
  const dynamicRoles = dynamic.artifacts.filter((entry) => entry.target === 'codex.agentRole');
956
1017
  const dynamicSkills = dynamic.artifacts.filter((entry) => entry.target === 'codex.skill');
957
1018
  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);
1019
+ if (options.scope === 'user') {
1020
+ writes.push(...await buildBundledCodexSkillWrites({
1021
+ plugin: options.plugin,
1022
+ env: options.env,
1023
+ marketplaceRoot: options.marketplaceRoot,
1024
+ }));
1025
+ }
966
1026
  const skillRoot = options.scope === 'user'
967
- ? path.join(options.marketplaceRoot ?? resolveAwHome(options.env), 'plugins', options.plugin.manifest.name, ...relativeSkillsRoot.split('/'))
1027
+ ? path.join(getCodexHome(options.env), 'skills')
968
1028
  : path.join(repoRoot, '.agents', 'skills');
969
1029
  for (const artifact of dynamicSkills) {
970
1030
  const tree = await createCodexSkillExpectedTree({
971
1031
  artifact,
972
1032
  sourceDirs: dynamic.sourceDirs(artifact),
973
1033
  requireReadme: true,
1034
+ requirementsReceipt: requirementsReceipt(options.plugin),
1035
+ sourceRequirementsReceiptPolicy: 'allow',
974
1036
  });
975
1037
  writes.push(dynamicSkillWrite({
976
1038
  ...options,
@@ -984,7 +1046,6 @@ async function buildCodexWrites(options) {
984
1046
  const staticInstructions = dynamicInstructions.length > 0
985
1047
  ? []
986
1048
  : resolvePlatformEntries(options.plugin.manifest.extensions?.instructions, 'codex');
987
- const mcpServers = await resolveCodexMcpServers(options.plugin, options.scope);
988
1049
  const hasInstructionOutput = dynamicInstructions.length > 0 || staticInstructions.length > 0;
989
1050
  if (hasInstructionOutput) {
990
1051
  const component = createCodexInstructionComponent({
@@ -1034,34 +1095,6 @@ async function buildCodexWrites(options) {
1034
1095
  });
1035
1096
  }
1036
1097
  }
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
1098
  for (const artifact of dynamicRoles) {
1066
1099
  const roleKey = artifact.roleKey;
1067
1100
  const rolePath = path.join(paths.configDir, 'agents', `${buildGeneratedName(options.plugin.manifest.name, roleKey)}.toml`);
@@ -1161,42 +1194,40 @@ async function buildCodexWrites(options) {
1161
1194
  kind: 'agent',
1162
1195
  }));
1163
1196
  }
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
- }
1197
+ const codexPluginId = `${options.plugin.manifest.name}@${options.marketplaceName ?? options.plugin.marketplaceName ?? MARKETPLACE_NAME}`;
1198
+ const nativeSurface = analyzeCodexSurface(options.plugin.manifest).nativeKinds.length > 0;
1199
+ const userActivationDesired = options.scope === 'user'
1200
+ || options.userActivationDesired?.has(codexPluginId) === true;
1201
+ for (const activation of !nativeSurface ? [] : options.scope === 'project'
1202
+ ? [
1203
+ {
1204
+ scope: 'user',
1205
+ desired: userActivationDesired,
1206
+ configPath: resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath,
1207
+ },
1208
+ { scope: 'project', desired: true, configPath: paths.configTomlPath },
1209
+ ]
1210
+ : [{ scope: 'user', desired: true, configPath: paths.configTomlPath }]) {
1211
+ const component = createCodexActivationComponent({
1212
+ identity: pluginOwnerIdentity(options.plugin, internalArtifactKey('activation', activation.scope), 'codex', options.scope),
1213
+ configPath: activation.configPath,
1214
+ codexPluginId,
1215
+ desired: activation.desired,
1216
+ });
1217
+ writes.push({
1218
+ artifact: {
1219
+ path: activation.configPath,
1220
+ kind: 'instruction',
1221
+ platform: 'codex',
1222
+ artifactKey: internalArtifactKey('activation', activation.scope),
1223
+ },
1224
+ component,
1225
+ write: async () => {
1226
+ await applyCodexConfigTransactionFile(activation.configPath, {
1227
+ upsertPluginActivations: { [codexPluginId]: activation.desired },
1228
+ });
1229
+ },
1230
+ });
1200
1231
  }
1201
1232
  return writes;
1202
1233
  }
@@ -1260,13 +1291,13 @@ async function buildInspectionWrites(options) {
1260
1291
  }
1261
1292
  const paths = resolveCodexPaths(options.cwd, options.scope, options.env);
1262
1293
  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
- }));
1294
+ if (options.scope === 'user') {
1295
+ writes.push(...await buildBundledCodexSkillWrites({
1296
+ plugin: options.plugin,
1297
+ env: options.env,
1298
+ marketplaceRoot: options.marketplaceRoot,
1299
+ }));
1300
+ }
1270
1301
  const instructions = resolvePlatformEntries(options.plugin.manifest.extensions?.instructions, 'codex');
1271
1302
  const targets = (options.plugin.manifest.artifacts?.targets ?? []).filter((target) => target.platform === 'codex' && target.scopes.includes(options.scope));
1272
1303
  const instructionTargets = targets.filter((target) => target.target === 'codex.developerInstructionsBlock');
@@ -1286,28 +1317,8 @@ async function buildInspectionWrites(options) {
1286
1317
  write: async () => undefined,
1287
1318
  });
1288
1319
  }
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
- });
1307
- }
1308
- const relativeSkillsRoot = await resolveCodexSkillsRelativeRoot(options.plugin);
1309
1320
  const dynamicSkillRoot = options.scope === 'user'
1310
- ? path.join(options.marketplaceRoot ?? resolveAwHome(options.env), 'plugins', options.plugin.manifest.name, ...relativeSkillsRoot.split('/'))
1321
+ ? path.join(getCodexHome(options.env), 'skills')
1311
1322
  : path.join(repoRoot, '.agents', 'skills');
1312
1323
  for (const target of targets) {
1313
1324
  const state = target.states.includes('present') ? 'present' : 'disabled';
@@ -1392,32 +1403,35 @@ async function buildInspectionWrites(options) {
1392
1403
  kind: 'agent',
1393
1404
  }));
1394
1405
  }
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
- }
1406
+ const nativeSurface = analyzeCodexSurface(options.plugin.manifest).nativeKinds.length > 0;
1407
+ const codexPluginId = `${options.plugin.manifest.name}@${options.marketplaceName ?? options.plugin.marketplaceName ?? MARKETPLACE_NAME}`;
1408
+ const userActivationDesired = options.scope === 'user'
1409
+ || options.userActivationDesired?.has(codexPluginId) === true;
1410
+ for (const activation of !nativeSurface ? [] : options.scope === 'project'
1411
+ ? [
1412
+ {
1413
+ scope: 'user',
1414
+ desired: userActivationDesired,
1415
+ configPath: resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath,
1416
+ },
1417
+ { scope: 'project', desired: true, configPath: paths.configTomlPath },
1418
+ ]
1419
+ : [{ scope: 'user', desired: true, configPath: paths.configTomlPath }]) {
1420
+ writes.push({
1421
+ artifact: {
1422
+ path: activation.configPath,
1423
+ kind: 'instruction',
1424
+ platform: 'codex',
1425
+ artifactKey: internalArtifactKey('activation', activation.scope),
1426
+ },
1427
+ component: createCodexActivationComponent({
1428
+ identity: pluginOwnerIdentity(options.plugin, internalArtifactKey('activation', activation.scope), 'codex', options.scope),
1429
+ configPath: activation.configPath,
1430
+ codexPluginId,
1431
+ desired: activation.desired,
1432
+ }),
1433
+ write: async () => undefined,
1434
+ });
1421
1435
  }
1422
1436
  return writes;
1423
1437
  }
@@ -1439,24 +1453,35 @@ async function buildHistoricalComponents(options) {
1439
1453
  continue;
1440
1454
  if (artifact.state === 'disabled')
1441
1455
  continue;
1442
- if (artifact.kind === 'hook' && options.platform === 'codex') {
1456
+ if (options.platform === 'codex'
1457
+ && artifact.artifactKey !== undefined
1458
+ && new Set([
1459
+ internalArtifactKey('activation', 'user'),
1460
+ internalArtifactKey('activation', 'project'),
1461
+ ]).has(artifact.artifactKey)) {
1462
+ continue;
1463
+ }
1464
+ if (options.command === 'cleanup'
1465
+ && artifact.kind === 'hook'
1466
+ && options.platform === 'codex') {
1443
1467
  for (const identity of artifact.hookGroups ?? []) {
1444
- components.push(createCodexHookComponent({
1468
+ components.push(createLegacyCodexHookCleanupComponent({
1445
1469
  pluginName: options.pluginName,
1446
1470
  scope: options.scope,
1447
- hooksPath: resolveCodexPaths(options.cwd, options.scope, options.env).hooksPath,
1471
+ hooksPath: path.join(resolveCodexPaths(options.cwd, options.scope, options.env).configDir, 'hooks.json'),
1448
1472
  identity,
1449
1473
  }));
1450
1474
  }
1451
1475
  continue;
1452
1476
  }
1453
- if (artifact.kind === 'mcpServer' && options.platform === 'codex' && artifact.artifactKey) {
1454
- components.push(createCodexMcpComponent({
1477
+ if (options.command === 'cleanup'
1478
+ && artifact.kind === 'mcpServer'
1479
+ && options.platform === 'codex'
1480
+ && artifact.artifactKey) {
1481
+ components.push(createLegacyCodexMcpCleanupComponent({
1455
1482
  identity: historicalIdentity(internalArtifactKey('mcp', artifact.artifactKey)),
1456
- scope: options.scope,
1457
1483
  configPath: resolveCodexPaths(options.cwd, options.scope, options.env).configTomlPath,
1458
1484
  serverKey: artifact.artifactKey,
1459
- owned: false,
1460
1485
  }));
1461
1486
  continue;
1462
1487
  }
@@ -1520,7 +1545,7 @@ async function readdirCodexResidueIfPresent(directory) {
1520
1545
  }
1521
1546
  return fs.readdir(directory, { withFileTypes: true });
1522
1547
  }
1523
- async function inspectCodexNativeResidue(options) {
1548
+ export async function inspectCodexNativeResidue(options) {
1524
1549
  const marketplaceName = options.marketplaceName ?? MARKETPLACE_NAME;
1525
1550
  const marketplaceRoot = options.marketplaceRoot ?? resolveAwHome(options.env);
1526
1551
  const pluginId = `${options.pluginName}@${marketplaceName}`;
@@ -1608,6 +1633,203 @@ async function inspectCodexNativeResidue(options) {
1608
1633
  installedVersions: [], cacheVersions: [], enabled: false,
1609
1634
  };
1610
1635
  }
1636
+ function isPathWithin(root, candidate) {
1637
+ const relative = path.relative(path.resolve(root), path.resolve(candidate));
1638
+ return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
1639
+ }
1640
+ async function codexPayloadPathSafe(payloadRoot) {
1641
+ const rootStats = await lstatCodexResidueIfPresent(payloadRoot);
1642
+ if (!rootStats?.isDirectory() || rootStats.isSymbolicLink())
1643
+ return false;
1644
+ const pending = [payloadRoot];
1645
+ while (pending.length > 0) {
1646
+ const current = pending.pop();
1647
+ for (const entry of await fs.readdir(current, { withFileTypes: true })) {
1648
+ const entryPath = path.join(current, entry.name);
1649
+ if (entry.isSymbolicLink()) {
1650
+ const target = path.resolve(path.dirname(entryPath), await fs.readlink(entryPath));
1651
+ if (!isPathWithin(payloadRoot, target))
1652
+ return false;
1653
+ }
1654
+ else if (entry.isDirectory()) {
1655
+ pending.push(entryPath);
1656
+ }
1657
+ else if (!entry.isFile()) {
1658
+ return false;
1659
+ }
1660
+ }
1661
+ }
1662
+ return true;
1663
+ }
1664
+ /**
1665
+ * Read-only filesystem projection for the payload-aware shared-native classifier.
1666
+ * It does not invoke Codex or create native/config state.
1667
+ */
1668
+ export async function inspectCodexSharedNativeLive(options) {
1669
+ const marketplaceName = options.marketplaceName ?? MARKETPLACE_NAME;
1670
+ const marketplaceRoot = options.marketplaceRoot ?? resolveAwHome(options.env);
1671
+ const pluginId = `${options.pluginName}@${marketplaceName}`;
1672
+ const config = await readCodexConfigFile(resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath);
1673
+ const cacheRoot = path.join(getCodexHome(options.env), 'plugins', 'cache', marketplaceName, options.pluginName);
1674
+ const installedManifestPath = path.join(getCodexHome(options.env), 'plugins', pluginId, 'manifest.json');
1675
+ const cacheRootStats = await lstatCodexResidueIfPresent(cacheRoot);
1676
+ let unsafeCachePath = Boolean(cacheRootStats
1677
+ && (!cacheRootStats.isDirectory() || cacheRootStats.isSymbolicLink()));
1678
+ const candidates = [];
1679
+ if (cacheRootStats?.isDirectory() && !cacheRootStats.isSymbolicLink()) {
1680
+ for (const entry of await fs.readdir(cacheRoot, { withFileTypes: true })) {
1681
+ const payloadPath = path.join(cacheRoot, entry.name);
1682
+ if (!entry.isDirectory() || entry.isSymbolicLink()) {
1683
+ unsafeCachePath = true;
1684
+ continue;
1685
+ }
1686
+ const manifestPaths = [
1687
+ path.join(payloadPath, 'plugin.json'),
1688
+ path.join(payloadPath, '.codex-plugin', 'plugin.json'),
1689
+ ];
1690
+ const existingManifestPaths = [];
1691
+ for (const manifestPath of manifestPaths) {
1692
+ const stats = await lstatCodexResidueIfPresent(manifestPath);
1693
+ if (stats)
1694
+ existingManifestPaths.push(manifestPath);
1695
+ }
1696
+ if (existingManifestPaths.length !== 1) {
1697
+ candidates.push({
1698
+ version: entry.name,
1699
+ manifest: existingManifestPaths.length === 0 ? { state: 'absent' } : { state: 'unreadable' },
1700
+ payloadPath,
1701
+ });
1702
+ continue;
1703
+ }
1704
+ const manifestPath = existingManifestPaths[0];
1705
+ const manifestStats = await lstatCodexResidueIfPresent(manifestPath);
1706
+ if (!manifestStats?.isFile() || manifestStats.isSymbolicLink()) {
1707
+ candidates.push({ version: entry.name, manifest: { state: 'unreadable' }, payloadPath });
1708
+ continue;
1709
+ }
1710
+ let manifest;
1711
+ try {
1712
+ const value = await fs.readJson(manifestPath);
1713
+ if (!value || typeof value !== 'object' || Array.isArray(value))
1714
+ throw new Error('invalid manifest');
1715
+ manifest = value;
1716
+ }
1717
+ catch {
1718
+ candidates.push({ version: entry.name, manifest: { state: 'unreadable' }, payloadPath });
1719
+ continue;
1720
+ }
1721
+ const pathSafe = await codexPayloadPathSafe(payloadPath);
1722
+ let payloadDigest = options.expectedPayloadDigest;
1723
+ if (pathSafe) {
1724
+ try {
1725
+ payloadDigest = await calculateCodexSharedNativePayloadDigest(payloadPath);
1726
+ }
1727
+ catch {
1728
+ candidates.push({ version: entry.name, manifest: { state: 'unreadable' }, payloadPath });
1729
+ continue;
1730
+ }
1731
+ }
1732
+ candidates.push({
1733
+ version: typeof manifest.version === 'string' ? manifest.version : entry.name,
1734
+ payloadPath,
1735
+ manifest: {
1736
+ state: 'present',
1737
+ identity: typeof manifest.id === 'string'
1738
+ ? manifest.id
1739
+ : typeof manifest.name === 'string' ? manifest.name : '',
1740
+ version: typeof manifest.version === 'string' ? manifest.version : '',
1741
+ payloadDigest,
1742
+ payloadPath,
1743
+ pathSafe,
1744
+ },
1745
+ });
1746
+ }
1747
+ }
1748
+ let installedVersion;
1749
+ const installedStats = await lstatCodexResidueIfPresent(installedManifestPath);
1750
+ if (installedStats?.isFile() && !installedStats.isSymbolicLink()) {
1751
+ try {
1752
+ const installed = await fs.readJson(installedManifestPath);
1753
+ if (typeof installed.version === 'string')
1754
+ installedVersion = installed.version;
1755
+ }
1756
+ catch {
1757
+ installedVersion = undefined;
1758
+ }
1759
+ }
1760
+ const selected = candidates.find((candidate) => candidate.version === installedVersion)
1761
+ ?? candidates.find((candidate) => candidate.version === options.desiredVersion)
1762
+ ?? (candidates.length === 1 ? candidates[0] : undefined);
1763
+ const pluginListEntries = candidates.map((candidate) => ({
1764
+ pluginId,
1765
+ enabled: config.snapshot.plugins[pluginId]?.enabled === true,
1766
+ ...(candidate.version ? { version: candidate.version } : {}),
1767
+ path: candidate.payloadPath,
1768
+ }));
1769
+ if (installedStats && pluginListEntries.length === 0) {
1770
+ pluginListEntries.push({
1771
+ pluginId,
1772
+ enabled: config.snapshot.plugins[pluginId]?.enabled === true,
1773
+ ...(installedVersion ? { version: installedVersion } : {}),
1774
+ path: path.dirname(installedManifestPath),
1775
+ });
1776
+ }
1777
+ if (unsafeCachePath && pluginListEntries.length === 0) {
1778
+ pluginListEntries.push({
1779
+ pluginId,
1780
+ enabled: config.snapshot.plugins[pluginId]?.enabled === true,
1781
+ version: options.desiredVersion,
1782
+ path: cacheRoot,
1783
+ });
1784
+ }
1785
+ const manifest = unsafeCachePath
1786
+ ? {
1787
+ state: 'present',
1788
+ identity: pluginId,
1789
+ version: options.desiredVersion,
1790
+ payloadDigest: options.expectedPayloadDigest,
1791
+ payloadPath: cacheRoot,
1792
+ pathSafe: false,
1793
+ }
1794
+ : selected?.manifest ?? { state: 'absent' };
1795
+ const sharedNative = inspectCodexSharedNative({
1796
+ pluginName: options.pluginName,
1797
+ pluginId,
1798
+ marketplaceName,
1799
+ canonicalMarketplaceRoot: marketplaceRoot,
1800
+ desiredVersion: options.desiredVersion,
1801
+ expectedPayloadDigest: options.expectedPayloadDigest,
1802
+ marketplaceRegistration: config.snapshot.marketplaces[marketplaceName],
1803
+ marketplacePathSafe: true,
1804
+ pluginListEntries,
1805
+ manifest,
1806
+ managedByCurrentIntent: options.managedByCurrentIntent && manifest.state === 'present',
1807
+ });
1808
+ return {
1809
+ sharedNative,
1810
+ ...(manifest.state === 'present' ? {
1811
+ actualPayloadDigest: manifest.payloadDigest,
1812
+ payloadPath: manifest.payloadPath,
1813
+ } : {}),
1814
+ };
1815
+ }
1816
+ export async function codexNativeRemovalRequired(options) {
1817
+ const residue = await inspectCodexNativeResidue(options);
1818
+ if (residue.state === 'present')
1819
+ return true;
1820
+ if (residue.state === 'foreign')
1821
+ return false;
1822
+ const marketplaceName = options.marketplaceName ?? MARKETPLACE_NAME;
1823
+ const marketplaceRoot = options.marketplaceRoot ?? resolveAwHome(options.env);
1824
+ const config = await readCodexConfigFile(resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath);
1825
+ const marketplace = config.snapshot.marketplaces[marketplaceName];
1826
+ const canonical = marketplace?.sourceType === 'local'
1827
+ && !marketplace.sourcePointsToManifest
1828
+ && path.normalize(marketplace.source ?? '') === path.normalize(marketplaceRoot);
1829
+ if (!canonical)
1830
+ return false;
1831
+ return (await discoverCodexNativeResidueNames(options)).length === 0;
1832
+ }
1611
1833
  export async function discoverCodexNativeResidueNames(options) {
1612
1834
  const marketplaceName = options.marketplaceName ?? MARKETPLACE_NAME;
1613
1835
  const marketplaceRoot = options.marketplaceRoot ?? resolveAwHome(options.env);
@@ -1809,8 +2031,13 @@ function createCodexMarketplaceComponent(options) {
1809
2031
  || currentResidue.state === 'present'
1810
2032
  || await hasRemainingPlugins())
1811
2033
  return;
1812
- await runNativeCliChecked(buildCodexMarketplaceRemoveInvocation(options.marketplaceName, options.cwd, options.env), options.runner);
1813
- await runNativeCliChecked(buildCodexMarketplaceListInvocation(options.cwd, options.env), options.runner);
2034
+ try {
2035
+ await runNativeCliChecked(buildCodexMarketplaceRemoveInvocation(requireCodexManager(options.codexManager), options.marketplaceName, options.cwd, options.env), options.runner);
2036
+ await runNativeCliChecked(buildCodexMarketplaceListInvocation(requireCodexManager(options.codexManager), options.cwd, options.env), options.runner);
2037
+ }
2038
+ catch {
2039
+ throw new Error(`codexNativeRemoveFailed: ${CODEX_REMOVE_RECOVERY}`);
2040
+ }
1814
2041
  },
1815
2042
  };
1816
2043
  }
@@ -2414,9 +2641,11 @@ async function mirrorCatalog(catalog, env, target) {
2414
2641
  }
2415
2642
  function createNativeDriver(options) {
2416
2643
  const runner = options.context.nativeCliRunner;
2417
- const materializeLeaseHeld = new WeakSet();
2644
+ const codexManager = options.context.codexManager;
2418
2645
  const mirrorTransactions = new WeakMap();
2419
2646
  const finalizeNamespacedCleanup = async (target) => {
2647
+ if (target.identity.platform === 'codex')
2648
+ return;
2420
2649
  const { sourceId, sourceIdentity, marketplaceRoot, marketplaceName } = target.identity;
2421
2650
  if (!sourceId || !sourceIdentity || !marketplaceRoot)
2422
2651
  return;
@@ -2429,53 +2658,13 @@ function createNativeDriver(options) {
2429
2658
  });
2430
2659
  };
2431
2660
  return {
2432
- withOwnershipLease: (target, operation) => (!options.context.runtimeLeaseHeld
2433
- && target.identity.platform === 'codex'
2434
- && target.identity.scope === 'user'
2435
- && !materializeLeaseHeld.has(target)
2436
- ? withActiveMarketplaceRuntimeLease(options.context.env, operation)
2437
- : operation()),
2438
- withMaterializeLease: (target, operation) => {
2439
- if (!target.identity.sourceId)
2440
- return operation();
2441
- if (options.context.runtimeLeaseHeld && !options.context.lifecycleLeaseHeld) {
2442
- throw new Error('lockOrderViolation: namespaced materialization cannot acquire lifecycle after runtime.');
2443
- }
2444
- const runOperation = async () => {
2445
- const previousRuntimeHeld = options.context.runtimeLeaseHeld;
2446
- options.context.runtimeLeaseHeld = true;
2447
- materializeLeaseHeld.add(target);
2448
- try {
2449
- if (options.context.expectedCatalogConfigDigest) {
2450
- const current = await readCatalogSourcesConfigSnapshot(options.context.env);
2451
- if (current.digest !== options.context.expectedCatalogConfigDigest) {
2452
- throw new Error('catalogSourceConflict: catalog source config changed before owner publication.');
2453
- }
2454
- }
2455
- return await operation();
2456
- }
2457
- finally {
2458
- materializeLeaseHeld.delete(target);
2459
- options.context.runtimeLeaseHeld = previousRuntimeHeld;
2460
- }
2461
- };
2462
- const withRuntime = () => options.context.runtimeLeaseHeld
2463
- ? runOperation()
2464
- : withActiveMarketplaceRuntimeLease(options.context.env, runOperation);
2465
- if (options.context.lifecycleLeaseHeld)
2466
- return withRuntime();
2467
- return withActiveMarketplaceLifecycleLease(options.context.env, async () => {
2468
- const previousLifecycleHeld = options.context.lifecycleLeaseHeld;
2469
- options.context.lifecycleLeaseHeld = true;
2470
- try {
2471
- return await withRuntime();
2472
- }
2473
- finally {
2474
- options.context.lifecycleLeaseHeld = previousLifecycleHeld;
2475
- }
2476
- });
2477
- },
2478
2661
  preflightMaterialize: async (target) => {
2662
+ if (target.identity.sourceId && options.context.expectedCatalogConfigDigest) {
2663
+ const current = await readCatalogSourcesConfigSnapshot(options.context.env);
2664
+ if (current.digest !== options.context.expectedCatalogConfigDigest) {
2665
+ throw new Error('catalogSourceConflict: catalog source config changed before owner publication.');
2666
+ }
2667
+ }
2479
2668
  if (target.identity.platform === 'claude') {
2480
2669
  await assertNoClaudeUnboundNativeNameCollision({
2481
2670
  pluginName: target.identity.pluginName,
@@ -2530,7 +2719,7 @@ function createNativeDriver(options) {
2530
2719
  });
2531
2720
  return state.pluginState;
2532
2721
  }
2533
- if (target.identity.platform === 'codex' && target.identity.scope === 'user') {
2722
+ if (target.identity.platform === 'codex') {
2534
2723
  const residue = await inspectCodexNativeResidue({
2535
2724
  pluginName: target.identity.pluginName,
2536
2725
  cwd: target.identity.cwd,
@@ -2562,7 +2751,7 @@ function createNativeDriver(options) {
2562
2751
  return state.pluginState;
2563
2752
  return state.pluginEnabled ? 'present' : 'partial';
2564
2753
  }
2565
- if (target.identity.platform === 'codex' && target.identity.scope === 'user') {
2754
+ if (target.identity.platform === 'codex') {
2566
2755
  const residue = await inspectCodexNativeResidue({
2567
2756
  pluginName: target.identity.pluginName,
2568
2757
  cwd: target.identity.cwd,
@@ -2572,7 +2761,34 @@ function createNativeDriver(options) {
2572
2761
  });
2573
2762
  if (residue.state !== 'present')
2574
2763
  return residue.state;
2575
- return residue.enabled ? 'present' : 'partial';
2764
+ if (target.identity.scope !== 'project' || !codexManager)
2765
+ return 'present';
2766
+ const userConfig = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath);
2767
+ const projectConfig = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'project', options.context.env).configTomlPath);
2768
+ const pluginId = `${target.identity.pluginName}@${target.identity.marketplaceName ?? MARKETPLACE_NAME}`;
2769
+ const neutralCwd = await fs.mkdtemp(path.join(os.tmpdir(), 'aw-codex-postcheck-'));
2770
+ try {
2771
+ const invocations = buildCodexProjectPluginListInvocations(codexManager, {
2772
+ exactCwd: target.identity.cwd,
2773
+ neutralCwd,
2774
+ env: options.context.env,
2775
+ });
2776
+ const [exactResult, neutralResult] = await Promise.all([
2777
+ runNativeCliChecked(invocations.exact, runner),
2778
+ runNativeCliChecked(invocations.neutral, runner),
2779
+ ]);
2780
+ postcheckCodexProjectPluginActivation({
2781
+ pluginId,
2782
+ userEnabled: userConfig.snapshot.plugins[pluginId]?.enabled === true,
2783
+ projectEnabled: projectConfig.snapshot.plugins[pluginId]?.enabled === true,
2784
+ exactResult,
2785
+ neutralResult,
2786
+ });
2787
+ }
2788
+ finally {
2789
+ await fs.remove(neutralCwd);
2790
+ }
2791
+ return 'present';
2576
2792
  }
2577
2793
  return 'absent';
2578
2794
  },
@@ -2596,7 +2812,12 @@ function createNativeDriver(options) {
2596
2812
  }
2597
2813
  if (before.state === 'absent')
2598
2814
  return;
2599
- await runNativeCliChecked(buildCodexPluginRemoveInvocation(component.pluginId, target.identity.cwd, options.context.env), runner);
2815
+ try {
2816
+ await runNativeCliChecked(buildCodexPluginRemoveInvocation(requireCodexManager(codexManager), component.pluginId, target.identity.cwd, options.context.env), runner);
2817
+ }
2818
+ catch {
2819
+ throw new Error(`codexNativeRemoveFailed: ${CODEX_REMOVE_RECOVERY}`);
2820
+ }
2600
2821
  const residue = await inspectCodexNativeResidue({
2601
2822
  pluginName: target.identity.pluginName,
2602
2823
  cwd: target.identity.cwd,
@@ -2605,7 +2826,7 @@ function createNativeDriver(options) {
2605
2826
  marketplaceRoot: target.identity.marketplaceRoot,
2606
2827
  });
2607
2828
  if (residue.state !== 'absent') {
2608
- throw new Error(`Codex native remove 后仍有残留:${residue.labels.join(',')}`);
2829
+ throw new Error(`codexNativeRemoveIncomplete: ${CODEX_REMOVE_RECOVERY}`);
2609
2830
  }
2610
2831
  }
2611
2832
  else {
@@ -2640,6 +2861,21 @@ function createNativeDriver(options) {
2640
2861
  throw new Error('Missing rebuild integration plan.');
2641
2862
  const marketplaceName = target.identity.marketplaceName ?? MARKETPLACE_NAME;
2642
2863
  const expectedMarketplaceRoot = target.identity.marketplaceRoot ?? resolveAwHome(options.context.env);
2864
+ if (target.identity.platform === 'codex') {
2865
+ const before = await inspectCodexNativeResidue({
2866
+ pluginName: target.identity.pluginName,
2867
+ cwd: target.identity.cwd,
2868
+ env: options.context.env,
2869
+ marketplaceName,
2870
+ marketplaceRoot: expectedMarketplaceRoot,
2871
+ });
2872
+ if (before.state === 'foreign') {
2873
+ throw new Error(`codexSharedNativeForeign: ${before.labels.join(', ')}`);
2874
+ }
2875
+ if (before.state === 'present') {
2876
+ await runNativeCliChecked(buildCodexPluginRemoveInvocation(requireCodexManager(codexManager), `${target.identity.pluginName}@${marketplaceName}`, target.identity.cwd, options.context.env), runner);
2877
+ }
2878
+ }
2643
2879
  const writeGeneration = async () => {
2644
2880
  if (mirrorTransactions.has(target)) {
2645
2881
  throw new Error('Marketplace mirror transaction is already active for this target.');
@@ -2672,27 +2908,31 @@ function createNativeDriver(options) {
2672
2908
  const marketplaceRoot = mirrored.rootDir;
2673
2909
  const pluginId = `${target.identity.pluginName}@${marketplaceName}`;
2674
2910
  if (target.identity.platform === 'codex') {
2911
+ const nativePluginRoot = await canonicalizePathThroughExistingAncestor(path.join(marketplaceRoot, 'plugins', target.identity.pluginName));
2675
2912
  const requiredSkillWrites = plan.writes.filter((entry) => !entry.optional
2676
2913
  && entry.component?.kind === 'skill-directory'
2677
2914
  && entry.artifact.kind === 'skill'
2678
2915
  && entry.artifact.state !== 'disabled'
2679
- && target.identity.scope === 'user');
2916
+ && target.identity.scope === 'user'
2917
+ && !path.relative(nativePluginRoot, entry.component.path).startsWith('..'));
2680
2918
  for (const write of requiredSkillWrites) {
2681
2919
  await write.write();
2682
2920
  }
2683
2921
  const skillsRelativeRoot = await ensureCodexActiveSkillsManifest({
2684
2922
  plugin: target.plugin,
2685
2923
  marketplaceRoot,
2686
- hasRequiredSkills: requiredSkillWrites.length > 0,
2924
+ hasRequiredSkills: target.identity.scope === 'project'
2925
+ ? target.plugin.manifest.skills !== undefined
2926
+ : requiredSkillWrites.length > 0,
2687
2927
  });
2688
2928
  const config = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath);
2689
2929
  const marketplace = config.snapshot.marketplaces[marketplaceName];
2690
2930
  if (path.normalize(marketplace?.source ?? '') !== path.normalize(marketplaceRoot)) {
2691
- await runNativeCliChecked(buildCodexMarketplaceAddInvocation(marketplaceRoot, target.identity.cwd, options.context.env), runner);
2931
+ await runNativeCliChecked(buildCodexMarketplaceAddInvocation(requireCodexManager(codexManager), marketplaceRoot, target.identity.cwd, options.context.env), runner);
2692
2932
  }
2693
- await runNativeCliChecked(buildCodexMarketplaceListInvocation(target.identity.cwd, options.context.env), runner);
2694
- await runNativeCliChecked(buildCodexPluginAddInvocation(pluginId, target.identity.cwd, options.context.env), runner);
2695
- await runNativeCliChecked(buildCodexPluginListInvocation(target.identity.cwd, options.context.env), runner);
2933
+ await runNativeCliChecked(buildCodexMarketplaceListInvocation(requireCodexManager(codexManager), target.identity.cwd, options.context.env), runner);
2934
+ await runNativeCliChecked(buildCodexPluginAddInvocation(requireCodexManager(codexManager), pluginId, target.identity.cwd, options.context.env), runner);
2935
+ await runNativeCliChecked(buildCodexPluginListInvocation(requireCodexManager(codexManager), target.identity.cwd, options.context.env), runner);
2696
2936
  await assertCodexNativeInstallState({
2697
2937
  pluginName: target.identity.pluginName,
2698
2938
  pluginVersion: target.identity.pluginVersion,
@@ -2741,11 +2981,7 @@ function createNativeDriver(options) {
2741
2981
  }
2742
2982
  }
2743
2983
  };
2744
- await (target.identity.sourceId
2745
- && !options.context.runtimeLeaseHeld
2746
- && !materializeLeaseHeld.has(target)
2747
- ? withActiveMarketplaceRuntimeLease(options.context.env, writeGeneration)
2748
- : writeGeneration());
2984
+ await writeGeneration();
2749
2985
  },
2750
2986
  writeArtifacts: async (target) => {
2751
2987
  const plan = options.plans.get(target);
@@ -2756,11 +2992,7 @@ function createNativeDriver(options) {
2756
2992
  await write.write();
2757
2993
  return plan.writes.map((write) => write.artifact);
2758
2994
  };
2759
- return !options.context.runtimeLeaseHeld
2760
- && !materializeLeaseHeld.has(target)
2761
- && target.identity.platform === 'codex' && target.identity.scope === 'user'
2762
- ? withActiveMarketplaceRuntimeLease(options.context.env, writeAll)
2763
- : writeAll();
2995
+ return writeAll();
2764
2996
  },
2765
2997
  runLifecycle: async (invocation) => {
2766
2998
  await runLifecycleScript({
@@ -2799,13 +3031,10 @@ export function createRebuildIntegration(options) {
2799
3031
  cwd: input.cwd,
2800
3032
  rootNames: input.platform === 'codex' ? ['.agents', '.codex'] : ['.claude'],
2801
3033
  managedPaths: input.platform === 'codex'
2802
- ? ['.codex/config.toml', '.codex/hooks.json']
3034
+ ? ['.codex/config.toml']
2803
3035
  : ['.claude/settings.json'],
2804
3036
  });
2805
3037
  input = { ...input, cwd: canonicalRepoRoot };
2806
- if (materializing && input.platform === 'codex' && input.plugin.manifest.apps) {
2807
- throw new Error(`Codex project scope 不支持 apps:${input.plugin.manifest.name}`);
2808
- }
2809
3038
  }
2810
3039
  if (materializing && input.platform === 'codex') {
2811
3040
  await preflightCodexWriteSafety({
@@ -2832,9 +3061,15 @@ export function createRebuildIntegration(options) {
2832
3061
  env: options.context.env,
2833
3062
  loadPlugin,
2834
3063
  });
3064
+ if (input.expectedLifecycleMetadataIntegrityDigest !== undefined
3065
+ && active?.metadata.metadataIntegrityDigest
3066
+ !== input.expectedLifecycleMetadataIntegrityDigest) {
3067
+ throw new Error('stateInventoryFailed: lifecycle active generation changed after verified inventory.');
3068
+ }
2835
3069
  historicalArtifacts = active?.artifacts ?? [];
2836
3070
  }
2837
3071
  let writes = [];
3072
+ let currentRemovalComponents = [];
2838
3073
  if (input.command !== 'cleanup' && input.command !== 'uninstall') {
2839
3074
  if (input.readOnlyInspectionPlan) {
2840
3075
  writes = await buildInspectionWrites({
@@ -2844,6 +3079,8 @@ export function createRebuildIntegration(options) {
2844
3079
  cwd: input.cwd,
2845
3080
  env: options.context.env,
2846
3081
  marketplaceRoot: input.marketplaceRoot,
3082
+ marketplaceName: input.marketplaceName,
3083
+ userActivationDesired: options.context.codexUserActivationDesired,
2847
3084
  });
2848
3085
  }
2849
3086
  else if (input.platform === 'codex') {
@@ -2853,6 +3090,8 @@ export function createRebuildIntegration(options) {
2853
3090
  event,
2854
3091
  historicalArtifacts,
2855
3092
  scriptTimeoutMs: options.scriptTimeoutMs,
3093
+ marketplaceName: input.marketplaceName,
3094
+ userActivationDesired: options.context.codexUserActivationDesired,
2856
3095
  });
2857
3096
  }
2858
3097
  else {
@@ -2865,6 +3104,22 @@ export function createRebuildIntegration(options) {
2865
3104
  });
2866
3105
  }
2867
3106
  }
3107
+ else if (input.platform === 'codex' && input.scope === 'project') {
3108
+ const currentRemovalWrites = await buildInspectionWrites({
3109
+ plugin: input.plugin,
3110
+ platform: input.platform,
3111
+ scope: input.scope,
3112
+ cwd: input.cwd,
3113
+ env: options.context.env,
3114
+ marketplaceRoot: input.marketplaceRoot,
3115
+ marketplaceName: input.marketplaceName,
3116
+ userActivationDesired: options.context.codexUserActivationDesired,
3117
+ });
3118
+ currentRemovalComponents = currentRemovalWrites.flatMap((write) => [
3119
+ ...(write.additionalComponents ?? []),
3120
+ ...(write.component ? [write.component] : []),
3121
+ ]);
3122
+ }
2868
3123
  const desiredComponents = writes.filter((write) => !write.optional).flatMap((write) => [
2869
3124
  ...(write.additionalComponents ?? []),
2870
3125
  ...(write.component ? [write.component] : []),
@@ -2892,8 +3147,13 @@ export function createRebuildIntegration(options) {
2892
3147
  })))).flat();
2893
3148
  const skillRoots = input.platform === 'codex'
2894
3149
  ? input.scope === 'project'
2895
- ? [path.join(await resolveRepoRoot(input.cwd), '.agents', 'skills')]
2896
- : [path.join(input.marketplaceRoot ?? resolveAwHome(options.context.env), 'plugins', input.plugin.manifest.name)]
3150
+ ? []
3151
+ : [
3152
+ path.join(getCodexHome(options.context.env), 'skills'),
3153
+ ...(input.preserveCodexSharedNativePayload
3154
+ ? []
3155
+ : [await canonicalizePathThroughExistingAncestor(path.join(input.marketplaceRoot ?? resolveAwHome(options.context.env), 'plugins', input.plugin.manifest.name))]),
3156
+ ]
2897
3157
  : [path.join(configDir, 'skills')];
2898
3158
  const scanSkills = () => Promise.all(skillRoots.map((root) => scanOwnedSkills({
2899
3159
  sourceId: input.sourceId,
@@ -2903,12 +3163,8 @@ export function createRebuildIntegration(options) {
2903
3163
  platform: input.platform,
2904
3164
  scope: input.scope,
2905
3165
  })));
2906
- const scannedSkills = (await (input.platform === 'codex'
2907
- && input.scope === 'user'
2908
- && input.readOnlyInspectionPlan !== true
2909
- ? withActiveMarketplaceRuntimeLease(options.context.env, scanSkills)
2910
- : scanSkills())).flat();
2911
- const historicalComponents = await buildHistoricalComponents({
3166
+ const scannedSkills = (await scanSkills()).flat();
3167
+ let historicalComponents = await buildHistoricalComponents({
2912
3168
  sourceId: input.sourceId,
2913
3169
  sourceIdentity: input.sourceIdentity,
2914
3170
  pluginId: input.pluginId,
@@ -2918,7 +3174,24 @@ export function createRebuildIntegration(options) {
2918
3174
  cwd: input.cwd,
2919
3175
  env: options.context.env,
2920
3176
  artifacts: historicalArtifacts,
3177
+ command: input.command,
2921
3178
  });
3179
+ if (input.preserveCodexSharedNativePayload) {
3180
+ const nativePluginRoots = await Promise.all([
3181
+ path.join(input.marketplaceRoot ?? resolveAwHome(options.context.env), 'plugins', input.plugin.manifest.name),
3182
+ path.join(getCodexHome(options.context.env), 'plugins', 'cache', input.marketplaceName ?? MARKETPLACE_NAME, input.plugin.manifest.name),
3183
+ ].map((root) => canonicalizePathThroughExistingAncestor(root)));
3184
+ historicalComponents = historicalComponents.filter((component) => {
3185
+ if (!('path' in component))
3186
+ return true;
3187
+ return nativePluginRoots.every((nativePluginRoot) => {
3188
+ const relative = path.relative(nativePluginRoot, path.resolve(component.path));
3189
+ return relative === '..'
3190
+ || relative.startsWith(`..${path.sep}`)
3191
+ || path.isAbsolute(relative);
3192
+ });
3193
+ });
3194
+ }
2922
3195
  const sharedComponents = input.platform === 'codex'
2923
3196
  ? await scanCodexSharedComponents({
2924
3197
  sourceId: input.sourceId,
@@ -2928,7 +3201,6 @@ export function createRebuildIntegration(options) {
2928
3201
  scope: input.scope,
2929
3202
  paths: resolveCodexPaths(input.cwd, input.scope, options.context.env),
2930
3203
  scanConfig: true,
2931
- scanHooks: true,
2932
3204
  ignoreReadErrors: true,
2933
3205
  })
2934
3206
  : await scanClaudeSharedComponents({
@@ -2940,9 +3212,28 @@ export function createRebuildIntegration(options) {
2940
3212
  settingsPath: resolveClaudePaths(input.cwd, input.scope, options.context.env).settingsPath,
2941
3213
  });
2942
3214
  const nativeRequired = input.platform === 'claude'
2943
- || (input.scope === 'user' && requiresCodexNativePlugin(input.plugin, input.scope));
2944
- const nativeCleanupCandidate = input.platform === 'claude' || input.scope === 'user';
2945
- const nativePlugin = (nativeRequired || nativeCleanupCandidate)
3215
+ || analyzeCodexSurface(input.plugin.manifest).nativeKinds.length > 0;
3216
+ const nativeDesired = nativeRequired
3217
+ && input.command !== 'cleanup'
3218
+ && input.command !== 'uninstall';
3219
+ const codexNative = input.platform === 'codex' && nativeRequired
3220
+ ? await inspectCodexNativeResidue({
3221
+ pluginName: input.plugin.manifest.name,
3222
+ cwd: input.cwd,
3223
+ env: options.context.env,
3224
+ marketplaceName: input.marketplaceName,
3225
+ marketplaceRoot: input.marketplaceRoot,
3226
+ })
3227
+ : undefined;
3228
+ const codexNativeExact = codexNative?.state === 'present'
3229
+ && codexNative.versions.length > 0
3230
+ && codexNative.versions.every((version) => version === input.plugin.manifest.version);
3231
+ const materializeNativeRequired = nativeDesired && (input.platform === 'claude'
3232
+ || input.command === 'reinstall'
3233
+ || !codexNativeExact);
3234
+ const nativeCleanupCandidate = input.platform === 'claude'
3235
+ && input.includeNativeCleanupCandidate !== false;
3236
+ const nativePlugin = (nativeDesired || nativeCleanupCandidate)
2946
3237
  ? [{
2947
3238
  kind: 'native-plugin',
2948
3239
  identity: ownerIdentity(input.plugin.manifest.name, 'native', input.platform, input.scope),
@@ -2952,9 +3243,10 @@ export function createRebuildIntegration(options) {
2952
3243
  const marketplaceDesired = nativeRequired
2953
3244
  && input.command !== 'cleanup'
2954
3245
  && input.command !== 'uninstall';
2955
- const marketplace = input.platform === 'codex' && input.scope === 'user'
3246
+ const marketplace = input.platform === 'codex' && input.scope === 'user' && nativeDesired
2956
3247
  ? [createCodexMarketplaceComponent({
2957
3248
  pluginName: input.plugin.manifest.name,
3249
+ codexManager: options.context.codexManager,
2958
3250
  cwd: input.cwd,
2959
3251
  env: options.context.env,
2960
3252
  runner: options.context.nativeCliRunner,
@@ -2974,20 +3266,48 @@ export function createRebuildIntegration(options) {
2974
3266
  desired: marketplaceDesired,
2975
3267
  })]
2976
3268
  : [];
2977
- if (nativeRequired
2978
- && input.command !== 'cleanup'
2979
- && input.command !== 'uninstall')
3269
+ if (nativeDesired)
2980
3270
  desiredComponents.push(...nativePlugin, ...marketplace);
3271
+ const activationCleanup = input.platform === 'codex'
3272
+ && nativeRequired
3273
+ && (input.command === 'cleanup' || input.command === 'uninstall')
3274
+ ? (input.scope === 'project'
3275
+ ? [
3276
+ createCodexActivationComponent({
3277
+ identity: ownerIdentity(input.plugin.manifest.name, internalArtifactKey('activation', 'project'), 'codex', input.scope),
3278
+ configPath: resolveCodexPaths(input.cwd, 'project', options.context.env).configTomlPath,
3279
+ codexPluginId: `${input.plugin.manifest.name}@${input.marketplaceName ?? MARKETPLACE_NAME}`,
3280
+ desired: true,
3281
+ }),
3282
+ ]
3283
+ : [createCodexActivationComponent({
3284
+ identity: ownerIdentity(input.plugin.manifest.name, internalArtifactKey('activation', 'user'), 'codex', input.scope),
3285
+ configPath: resolveCodexPaths(input.cwd, 'user', options.context.env).configTomlPath,
3286
+ codexPluginId: `${input.plugin.manifest.name}@${input.marketplaceName ?? MARKETPLACE_NAME}`,
3287
+ desired: true,
3288
+ })])
3289
+ : [];
3290
+ const explicitActivationCleanup = input.platform === 'codex'
3291
+ ? (input.codexActivationCleanup ?? []).map((activation) => createCodexActivationCleanupComponent({
3292
+ identity: ownerIdentity(input.plugin.manifest.name, internalArtifactKey('activation', activation.scope), 'codex', input.scope),
3293
+ configPath: resolveCodexPaths(input.cwd, activation.scope, options.context.env).configTomlPath,
3294
+ codexPluginId: activation.codexPluginId,
3295
+ disposition: activation.disposition,
3296
+ }))
3297
+ : [];
2981
3298
  const allComponents = dedupeComponents([
2982
3299
  ...desiredComponents,
2983
3300
  ...optionalComponents,
2984
3301
  ...(input.platform === 'codex' ? sharedComponents : []),
2985
3302
  ...scannedText,
2986
3303
  ...scannedSkills,
3304
+ ...currentRemovalComponents,
2987
3305
  ...historicalComponents,
2988
3306
  ...(input.platform === 'claude' ? sharedComponents : []),
2989
3307
  ...nativePlugin,
2990
3308
  ...marketplace,
3309
+ ...activationCleanup,
3310
+ ...explicitActivationCleanup,
2991
3311
  ]);
2992
3312
  bindNamespacedOwnerIdentity(allComponents, input);
2993
3313
  const desiredKeys = new Set(desiredComponents.map(componentIdentityKey));
@@ -3010,11 +3330,15 @@ export function createRebuildIntegration(options) {
3010
3330
  ownedComponents: allComponents,
3011
3331
  desiredOwnedComponents: allComponents.filter((component) => desiredKeys.has(componentIdentityKey(component))),
3012
3332
  optionalOwnedComponents: allComponents.filter((component) => optionalComponentKeys.has(componentIdentityKey(component))),
3013
- requiresNative: nativeRequired
3014
- && input.command !== 'cleanup'
3015
- && input.command !== 'uninstall',
3333
+ requiresNative: nativeDesired,
3334
+ writeNativeRequired: materializeNativeRequired,
3016
3335
  artifacts: writes.map((write) => write.artifact),
3017
3336
  lifecycleSource: input.plugin,
3337
+ ...(input.expectedLifecycleMetadataIntegrityDigest
3338
+ ? {
3339
+ historicalLifecycleMetadataIntegrityDigest: input.expectedLifecycleMetadataIntegrityDigest,
3340
+ }
3341
+ : {}),
3018
3342
  };
3019
3343
  plans.set(target, {
3020
3344
  catalog: input.catalog,
@@ -3027,7 +3351,13 @@ export function createRebuildIntegration(options) {
3027
3351
  };
3028
3352
  }
3029
3353
  export async function inspectRebuildTarget(options) {
3030
- const integration = createRebuildIntegration({ context: options.context });
3354
+ const integration = createRebuildIntegration({
3355
+ context: {
3356
+ ...options.context,
3357
+ codexUserActivationDesired: options.userActivationDesired
3358
+ ?? options.context.codexUserActivationDesired,
3359
+ },
3360
+ });
3031
3361
  const target = await integration.buildTarget({
3032
3362
  ...(options.sourceId ? {
3033
3363
  sourceId: options.sourceId,
@@ -3047,7 +3377,9 @@ export async function inspectRebuildTarget(options) {
3047
3377
  });
3048
3378
  const inspection = await inspectOwnedComponents({
3049
3379
  components: target.ownedComponents,
3050
- nativeProbe: integration.driver.probeNativeHealth ?? integration.driver.probeNative,
3380
+ nativeProbe: options.nativeProbe
3381
+ ?? integration.driver.probeNativeHealth
3382
+ ?? integration.driver.probeNative,
3051
3383
  verifyHealth: true,
3052
3384
  });
3053
3385
  if (!target.requiresNative)
@@ -3146,7 +3478,7 @@ export async function discoverRebuildTargetNames(options) {
3146
3478
  cwd: options.cwd,
3147
3479
  rootNames: options.platform === 'codex' ? ['.agents', '.codex'] : ['.claude'],
3148
3480
  managedPaths: options.platform === 'codex'
3149
- ? ['.codex/config.toml', '.codex/hooks.json']
3481
+ ? ['.codex/config.toml']
3150
3482
  : ['.claude/settings.json'],
3151
3483
  });
3152
3484
  options = { ...options, cwd: canonicalRepoRoot };
@@ -3160,7 +3492,7 @@ export async function discoverRebuildTargetNames(options) {
3160
3492
  names.add(name);
3161
3493
  const skillRoots = options.platform === 'codex'
3162
3494
  ? options.scope === 'project'
3163
- ? [path.join(await resolveRepoRoot(options.cwd), '.agents', 'skills')]
3495
+ ? []
3164
3496
  : [path.join(resolveAwHome(options.env), 'plugins')]
3165
3497
  : [path.join(paths.configDir, 'skills')];
3166
3498
  if (options.platform === 'codex' && options.scope === 'user') {
@@ -3195,24 +3527,14 @@ export async function discoverRebuildTargetNames(options) {
3195
3527
  for (const name of Object.keys(config.snapshot.developerInstructions.managedBlocks)) {
3196
3528
  names.add(sharedOwnerPluginName(name));
3197
3529
  }
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
3530
  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);
3531
+ const suffix = `@${options.marketplaceName ?? MARKETPLACE_NAME}`;
3532
+ for (const pluginId of Object.keys(config.snapshot.plugins)) {
3533
+ if (!pluginId.endsWith(suffix))
3534
+ continue;
3535
+ const name = pluginId.slice(0, -suffix.length);
3536
+ if (/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name))
3537
+ names.add(name);
3216
3538
  }
3217
3539
  }
3218
3540
  else {