@lesliechan721/aw 0.1.0-beta.10

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