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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (275) hide show
  1. package/dist/authoring/scripts/add-skill.mjs +4 -4
  2. package/dist/authoring/scripts/add-skill.mjs.map +1 -1
  3. package/dist/authoring/scripts/init-plugin.mjs +4 -4
  4. package/dist/authoring/scripts/init-plugin.mjs.map +1 -1
  5. package/dist/authoring/scripts/lib/plugin-version-check.mjs +1 -1
  6. package/dist/authoring/scripts/lib/skill-scaffold.d.mts +77 -90
  7. package/dist/authoring/scripts/lib/skill-scaffold.mjs +99 -120
  8. package/dist/authoring/scripts/lib/skill-scaffold.mjs.map +1 -1
  9. package/dist/authoring/scripts/remove-plugin.mjs +1 -1
  10. package/dist/authoring/scripts/remove-plugin.mjs.map +1 -1
  11. package/dist/authoring/scripts/validate-plugin.mjs +34 -778
  12. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
  13. package/dist/scripts/aw-skill-eval-materializer.d.ts +24 -0
  14. package/dist/scripts/{plugin-eval-materializer.js → aw-skill-eval-materializer.js} +34 -145
  15. package/dist/scripts/aw-skill-eval-materializer.js.map +1 -0
  16. package/dist/scripts/bundle-plugins.d.ts +1 -0
  17. package/dist/scripts/bundle-plugins.js +2 -1
  18. package/dist/scripts/bundle-plugins.js.map +1 -1
  19. package/dist/scripts/plugin-staging.d.ts +5 -6
  20. package/dist/scripts/plugin-staging.js +12 -13
  21. package/dist/scripts/plugin-staging.js.map +1 -1
  22. package/dist/src/authoring/workspace-commands.d.ts +2 -1
  23. package/dist/src/authoring/workspace-commands.js.map +1 -1
  24. package/dist/src/commands/aw-capabilities.d.ts +2 -2
  25. package/dist/src/commands/aw-capabilities.js +6 -11
  26. package/dist/src/commands/aw-capabilities.js.map +1 -1
  27. package/dist/src/commands/catalog-dev-source.d.ts +18 -12
  28. package/dist/src/commands/catalog-dev-source.js +241 -305
  29. package/dist/src/commands/catalog-dev-source.js.map +1 -1
  30. package/dist/src/commands/catalog-generation.d.ts +6 -19
  31. package/dist/src/commands/catalog-generation.js +10 -11
  32. package/dist/src/commands/catalog-generation.js.map +1 -1
  33. package/dist/src/commands/catalog-publication-inspection.d.ts +28 -27
  34. package/dist/src/commands/catalog-publication-inspection.js +139 -50
  35. package/dist/src/commands/catalog-publication-inspection.js.map +1 -1
  36. package/dist/src/commands/catalog-publication-journal.d.ts +49 -0
  37. package/dist/src/commands/catalog-publication-journal.js +147 -0
  38. package/dist/src/commands/catalog-publication-journal.js.map +1 -0
  39. package/dist/src/commands/catalog-publication-target.d.ts +2 -1
  40. package/dist/src/commands/catalog-publication-target.js +37 -2
  41. package/dist/src/commands/catalog-publication-target.js.map +1 -1
  42. package/dist/src/commands/catalog-publish.d.ts +51 -15
  43. package/dist/src/commands/catalog-publish.js +576 -219
  44. package/dist/src/commands/catalog-publish.js.map +1 -1
  45. package/dist/src/commands/catalog-source.d.ts +3 -28
  46. package/dist/src/commands/catalog-source.js +29 -140
  47. package/dist/src/commands/catalog-source.js.map +1 -1
  48. package/dist/src/commands/catalog-status.d.ts +6 -26
  49. package/dist/src/commands/catalog-status.js +8 -146
  50. package/dist/src/commands/catalog-status.js.map +1 -1
  51. package/dist/src/commands/catalog-workspace-resolution.d.ts +13 -0
  52. package/dist/src/commands/catalog-workspace-resolution.js +42 -0
  53. package/dist/src/commands/catalog-workspace-resolution.js.map +1 -0
  54. package/dist/src/commands/dependency-operation-plan.d.ts +88 -9
  55. package/dist/src/commands/dependency-operation-plan.js +331 -6
  56. package/dist/src/commands/dependency-operation-plan.js.map +1 -1
  57. package/dist/src/commands/init.d.ts +5 -11
  58. package/dist/src/commands/init.js +30 -51
  59. package/dist/src/commands/init.js.map +1 -1
  60. package/dist/src/commands/offline-cleanup-evidence.d.ts +20 -0
  61. package/dist/src/commands/offline-cleanup-evidence.js +87 -9
  62. package/dist/src/commands/offline-cleanup-evidence.js.map +1 -1
  63. package/dist/src/commands/options.d.ts +6 -1
  64. package/dist/src/commands/options.js +64 -6
  65. package/dist/src/commands/options.js.map +1 -1
  66. package/dist/src/commands/plugin-capabilities.js +4 -0
  67. package/dist/src/commands/plugin-capabilities.js.map +1 -1
  68. package/dist/src/commands/rebuild-command.d.ts +10 -8
  69. package/dist/src/commands/rebuild-command.js +1217 -426
  70. package/dist/src/commands/rebuild-command.js.map +1 -1
  71. package/dist/src/commands/rebuild-integration.d.ts +54 -6
  72. package/dist/src/commands/rebuild-integration.js +691 -364
  73. package/dist/src/commands/rebuild-integration.js.map +1 -1
  74. package/dist/src/commands/rebuild-operations.d.ts +2 -0
  75. package/dist/src/commands/rebuild-operations.js +31 -17
  76. package/dist/src/commands/rebuild-operations.js.map +1 -1
  77. package/dist/src/commands/skill-eval.d.ts +33 -0
  78. package/dist/src/commands/skill-eval.js +363 -0
  79. package/dist/src/commands/skill-eval.js.map +1 -0
  80. package/dist/src/commands/{status-v5.d.ts → status-v6.d.ts} +59 -26
  81. package/dist/src/commands/{status-v5.js → status-v6.js} +882 -79
  82. package/dist/src/commands/status-v6.js.map +1 -0
  83. package/dist/src/commands/sync-command.d.ts +21 -0
  84. package/dist/src/commands/sync-command.js +206 -0
  85. package/dist/src/commands/sync-command.js.map +1 -0
  86. package/dist/src/commands/wizard.d.ts +2 -2
  87. package/dist/src/commands/write-commands.js +1 -43
  88. package/dist/src/commands/write-commands.js.map +1 -1
  89. package/dist/src/constants.d.ts +5 -4
  90. package/dist/src/constants.js +5 -4
  91. package/dist/src/constants.js.map +1 -1
  92. package/dist/src/eval/manifest-v1.d.ts +66 -0
  93. package/dist/src/eval/manifest-v1.js +326 -0
  94. package/dist/src/eval/manifest-v1.js.map +1 -0
  95. package/dist/src/generators/local-plugin.d.ts +3 -0
  96. package/dist/src/generators/local-plugin.js +123 -55
  97. package/dist/src/generators/local-plugin.js.map +1 -1
  98. package/dist/src/index.d.ts +2 -0
  99. package/dist/src/index.js +412 -152
  100. package/dist/src/index.js.map +1 -1
  101. package/dist/src/output/command-receipt.d.ts +58 -8
  102. package/dist/src/output/command-receipt.js +141 -11
  103. package/dist/src/output/command-receipt.js.map +1 -1
  104. package/dist/src/output/model.d.ts +3 -11
  105. package/dist/src/portable/commands.d.ts +2 -2
  106. package/dist/src/portable/commands.js +2 -8
  107. package/dist/src/portable/commands.js.map +1 -1
  108. package/dist/src/portable/materializer.d.ts +1 -4
  109. package/dist/src/portable/materializer.js +0 -2
  110. package/dist/src/portable/materializer.js.map +1 -1
  111. package/dist/src/tui/catalog-generation.d.ts +20 -41
  112. package/dist/src/tui/catalog-generation.js +65 -224
  113. package/dist/src/tui/catalog-generation.js.map +1 -1
  114. package/dist/src/tui/components.js +1 -2
  115. package/dist/src/tui/components.js.map +1 -1
  116. package/dist/src/tui/console-app-v4.js +461 -330
  117. package/dist/src/tui/console-app-v4.js.map +1 -1
  118. package/dist/src/tui/controller-runtime.d.ts +3 -5
  119. package/dist/src/tui/controller-runtime.js +182 -187
  120. package/dist/src/tui/controller-runtime.js.map +1 -1
  121. package/dist/src/tui/controller.d.ts +42 -45
  122. package/dist/src/tui/controller.js +454 -407
  123. package/dist/src/tui/controller.js.map +1 -1
  124. package/dist/src/tui/diagnostics.d.ts +1 -2
  125. package/dist/src/tui/diagnostics.js.map +1 -1
  126. package/dist/src/tui/index.d.ts +1 -1
  127. package/dist/src/tui/index.js +1 -1
  128. package/dist/src/tui/index.js.map +1 -1
  129. package/dist/src/tui/model.d.ts +81 -13
  130. package/dist/src/tui/model.js +378 -95
  131. package/dist/src/tui/model.js.map +1 -1
  132. package/dist/src/tui/router.d.ts +9 -0
  133. package/dist/src/tui/router.js.map +1 -1
  134. package/dist/src/types.d.ts +54 -47
  135. package/dist/src/types.js.map +1 -1
  136. package/dist/src/utils/agent-version.d.ts +4 -1
  137. package/dist/src/utils/agent-version.js +28 -3
  138. package/dist/src/utils/agent-version.js.map +1 -1
  139. package/dist/src/utils/catalog-digests.d.ts +7 -0
  140. package/dist/src/utils/catalog-digests.js +27 -0
  141. package/dist/src/utils/catalog-digests.js.map +1 -1
  142. package/dist/src/utils/catalog-generation-control-snapshot.d.ts +11 -0
  143. package/dist/src/utils/catalog-generation-control-snapshot.js +45 -0
  144. package/dist/src/utils/catalog-generation-control-snapshot.js.map +1 -0
  145. package/dist/src/utils/catalog-generation-error.d.ts +2 -1
  146. package/dist/src/utils/catalog-generation-error.js +12 -0
  147. package/dist/src/utils/catalog-generation-error.js.map +1 -1
  148. package/dist/src/utils/catalog-generation-resolver.d.ts +10 -3
  149. package/dist/src/utils/catalog-generation-resolver.js +90 -76
  150. package/dist/src/utils/catalog-generation-resolver.js.map +1 -1
  151. package/dist/src/utils/catalog-generation-service.d.ts +24 -33
  152. package/dist/src/utils/catalog-generation-service.js +253 -567
  153. package/dist/src/utils/catalog-generation-service.js.map +1 -1
  154. package/dist/src/utils/catalog-generation-state.d.ts +4 -0
  155. package/dist/src/utils/catalog-generation-state.js +73 -106
  156. package/dist/src/utils/catalog-generation-state.js.map +1 -1
  157. package/dist/src/utils/catalog-generation-store.d.ts +16 -26
  158. package/dist/src/utils/catalog-generation-store.js +154 -290
  159. package/dist/src/utils/catalog-generation-store.js.map +1 -1
  160. package/dist/src/utils/catalog-manifest-v6.d.ts +5 -0
  161. package/dist/src/utils/{catalog-manifest-v5.js → catalog-manifest-v6.js} +23 -35
  162. package/dist/src/utils/catalog-manifest-v6.js.map +1 -0
  163. package/dist/src/utils/catalog-operation-context.d.ts +6 -14
  164. package/dist/src/utils/catalog-operation-context.js +68 -169
  165. package/dist/src/utils/catalog-operation-context.js.map +1 -1
  166. package/dist/src/utils/catalog-public-error.js +1 -4
  167. package/dist/src/utils/catalog-public-error.js.map +1 -1
  168. package/dist/src/utils/catalog-runtime-projection.d.ts +3 -0
  169. package/dist/src/utils/catalog-runtime-projection.js +5 -40
  170. package/dist/src/utils/catalog-runtime-projection.js.map +1 -1
  171. package/dist/src/utils/catalog-snapshot.d.ts +2 -2
  172. package/dist/src/utils/catalog-source-references.d.ts +7 -0
  173. package/dist/src/utils/catalog-source-references.js +44 -55
  174. package/dist/src/utils/catalog-source-references.js.map +1 -1
  175. package/dist/src/utils/catalog-sources.js +25 -9
  176. package/dist/src/utils/catalog-sources.js.map +1 -1
  177. package/dist/src/utils/codex-config.d.ts +2 -0
  178. package/dist/src/utils/codex-config.js +22 -1
  179. package/dist/src/utils/codex-config.js.map +1 -1
  180. package/dist/src/utils/codex-manager.d.ts +50 -2
  181. package/dist/src/utils/codex-manager.js +160 -6
  182. package/dist/src/utils/codex-manager.js.map +1 -1
  183. package/dist/src/utils/codex-reconciler.d.ts +131 -0
  184. package/dist/src/utils/codex-reconciler.js +327 -0
  185. package/dist/src/utils/codex-reconciler.js.map +1 -0
  186. package/dist/src/utils/codex-shared-native.d.ts +56 -0
  187. package/dist/src/utils/codex-shared-native.js +87 -0
  188. package/dist/src/utils/codex-shared-native.js.map +1 -0
  189. package/dist/src/utils/codex-surface-analysis.d.ts +44 -0
  190. package/dist/src/utils/codex-surface-analysis.js +247 -0
  191. package/dist/src/utils/codex-surface-analysis.js.map +1 -0
  192. package/dist/src/utils/git-catalog-provider.js +56 -115
  193. package/dist/src/utils/git-catalog-provider.js.map +1 -1
  194. package/dist/src/utils/manifest.d.ts +1 -3
  195. package/dist/src/utils/manifest.js +1 -3
  196. package/dist/src/utils/manifest.js.map +1 -1
  197. package/dist/src/utils/multi-source-catalog.d.ts +1 -10
  198. package/dist/src/utils/multi-source-catalog.js +8 -175
  199. package/dist/src/utils/multi-source-catalog.js.map +1 -1
  200. package/dist/src/utils/native-cli.d.ts +34 -0
  201. package/dist/src/utils/native-cli.js +91 -0
  202. package/dist/src/utils/native-cli.js.map +1 -1
  203. package/dist/src/utils/orphan-marketplace-cleanup.js +1 -0
  204. package/dist/src/utils/orphan-marketplace-cleanup.js.map +1 -1
  205. package/dist/src/utils/ownership.d.ts +0 -10
  206. package/dist/src/utils/ownership.js +3 -88
  207. package/dist/src/utils/ownership.js.map +1 -1
  208. package/dist/src/utils/path-catalog-provider.d.ts +1 -1
  209. package/dist/src/utils/path-catalog-provider.js +45 -62
  210. package/dist/src/utils/path-catalog-provider.js.map +1 -1
  211. package/dist/src/utils/paths.d.ts +0 -1
  212. package/dist/src/utils/paths.js +0 -1
  213. package/dist/src/utils/paths.js.map +1 -1
  214. package/dist/src/utils/plugin-config-write.js +0 -5
  215. package/dist/src/utils/plugin-config-write.js.map +1 -1
  216. package/dist/src/utils/plugin-intent-state.d.ts +39 -0
  217. package/dist/src/utils/plugin-intent-state.js +325 -0
  218. package/dist/src/utils/plugin-intent-state.js.map +1 -0
  219. package/dist/src/utils/target-eligibility.d.ts +3 -2
  220. package/dist/src/utils/target-eligibility.js +4 -3
  221. package/dist/src/utils/target-eligibility.js.map +1 -1
  222. package/package.json +5 -4
  223. package/dist/scripts/aw-skill-eval-engine.d.ts +0 -13
  224. package/dist/scripts/aw-skill-eval-engine.js +0 -31
  225. package/dist/scripts/aw-skill-eval-engine.js.map +0 -1
  226. package/dist/scripts/codex-eval-app-server.d.ts +0 -91
  227. package/dist/scripts/codex-eval-app-server.js +0 -1334
  228. package/dist/scripts/codex-eval-app-server.js.map +0 -1
  229. package/dist/scripts/codex-eval-isolation.d.ts +0 -61
  230. package/dist/scripts/codex-eval-isolation.js +0 -318
  231. package/dist/scripts/codex-eval-isolation.js.map +0 -1
  232. package/dist/scripts/codex-eval-process-management.d.ts +0 -8
  233. package/dist/scripts/codex-eval-process-management.js +0 -69
  234. package/dist/scripts/codex-eval-process-management.js.map +0 -1
  235. package/dist/scripts/codex-eval-process-tree.d.ts +0 -38
  236. package/dist/scripts/codex-eval-process-tree.js +0 -803
  237. package/dist/scripts/codex-eval-process-tree.js.map +0 -1
  238. package/dist/scripts/codex-eval-runtime-config.d.ts +0 -62
  239. package/dist/scripts/codex-eval-runtime-config.js +0 -843
  240. package/dist/scripts/codex-eval-runtime-config.js.map +0 -1
  241. package/dist/scripts/codex-trigger-eval.d.ts +0 -84
  242. package/dist/scripts/codex-trigger-eval.js +0 -968
  243. package/dist/scripts/codex-trigger-eval.js.map +0 -1
  244. package/dist/scripts/eval-plugin-skills-audited.d.ts +0 -6
  245. package/dist/scripts/eval-plugin-skills-audited.js +0 -261
  246. package/dist/scripts/eval-plugin-skills-audited.js.map +0 -1
  247. package/dist/scripts/eval-plugin-skills.d.ts +0 -45
  248. package/dist/scripts/eval-plugin-skills.js +0 -1589
  249. package/dist/scripts/eval-plugin-skills.js.map +0 -1
  250. package/dist/scripts/eval-run-layout.d.ts +0 -17
  251. package/dist/scripts/eval-run-layout.js +0 -26
  252. package/dist/scripts/eval-run-layout.js.map +0 -1
  253. package/dist/scripts/plugin-eval-contract.d.ts +0 -189
  254. package/dist/scripts/plugin-eval-contract.js +0 -745
  255. package/dist/scripts/plugin-eval-contract.js.map +0 -1
  256. package/dist/scripts/plugin-eval-materializer.d.ts +0 -13
  257. package/dist/scripts/plugin-eval-materializer.js.map +0 -1
  258. package/dist/scripts/skill-reporting.d.ts +0 -212
  259. package/dist/scripts/skill-reporting.js +0 -922
  260. package/dist/scripts/skill-reporting.js.map +0 -1
  261. package/dist/src/commands/catalog-context.d.ts +0 -52
  262. package/dist/src/commands/catalog-context.js +0 -156
  263. package/dist/src/commands/catalog-context.js.map +0 -1
  264. package/dist/src/commands/status-v5.js.map +0 -1
  265. package/dist/src/utils/catalog-manifest-v5.d.ts +0 -5
  266. package/dist/src/utils/catalog-manifest-v5.js.map +0 -1
  267. package/dist/src/utils/catalog-source-cache.d.ts +0 -62
  268. package/dist/src/utils/catalog-source-cache.js +0 -321
  269. package/dist/src/utils/catalog-source-cache.js.map +0 -1
  270. package/dist/src/utils/codex-project-surface.d.ts +0 -3
  271. package/dist/src/utils/codex-project-surface.js +0 -49
  272. package/dist/src/utils/codex-project-surface.js.map +0 -1
  273. package/dist/src/utils/plugin-release.d.ts +0 -2
  274. package/dist/src/utils/plugin-release.js +0 -2
  275. package/dist/src/utils/plugin-release.js.map +0 -1
@@ -1,17 +1,18 @@
1
1
  import crypto from 'node:crypto';
2
+ import os from 'node:os';
2
3
  import path from 'node:path';
3
4
  import fs from 'fs-extra';
4
5
  import { generateAgentArtifacts } from '../generators/agents.js';
5
- import { extractCodexHookGroups } from '../generators/hooks.js';
6
6
  import { generateInstructionArtifacts, renderCodexManagedInstructionBlock, } from '../generators/instructions.js';
7
7
  import { generateCodexPermissionArtifacts, mergeClaudePermissions, } from '../generators/permissions.js';
8
8
  import { buildGeneratedName } from '../generators/shared.js';
9
- import { applyConditionalCodexConfigTransactionFile, applyCodexConfigTransactionFile, composeManagedDeveloperInstructionsBlock, readCodexConfigFile, readRenderedCodexMcpServersFile, } from '../utils/codex-config.js';
9
+ import { applyConditionalCodexConfigTransactionFile, applyCodexConfigTransactionFile, composeManagedDeveloperInstructionsBlock, readCodexConfigFile, } from '../utils/codex-config.js';
10
+ import { canonicalizePathThroughExistingAncestor } from '../utils/state-paths.js';
10
11
  import { createCodexSkillExpectedTree, createCodexSkillStagingTreeContentDigest, } from '../utils/codex-skill-staging.js';
11
12
  import { createDynamicSkillExpectedTree, createDynamicSkillTreeContentDigest, skillDirectoryMatchesExpectedTree, validateSkillDirectory, writeDynamicSkillTree, } from '../utils/dynamic-skill-tree.js';
12
- import { parseCodexSharedHooksFile, renderTrackedHookGroupIdentity, resolvePlatformHookDefinitions, serializeTrackedHookGroupIdentity, } from '../utils/hooks-contract.js';
13
- import { buildCodexMarketplaceAddInvocation, buildCodexMarketplaceListInvocation, buildCodexMarketplaceRemoveInvocation, buildCodexPluginAddInvocation, buildCodexPluginListInvocation, buildCodexPluginRemoveInvocation, runNativeCliChecked, } from '../utils/native-cli.js';
14
- import { OWNED_TEXT_HEADER_PREFIX, createSharedOwnerToken, createSkillOwnerMarkerContent, parseSharedOwnerToken, parseOwnedTextHeader, readSkillOwnerMarker, inspectOwnedComponents, writeOwnedTextFile, } from '../utils/ownership.js';
13
+ import { parseCodexSharedHooksFile, serializeTrackedHookGroupIdentity, } from '../utils/hooks-contract.js';
14
+ import { buildCodexMarketplaceAddInvocation, buildCodexMarketplaceListInvocation, buildCodexMarketplaceRemoveInvocation, buildCodexPluginAddInvocation, buildCodexPluginListInvocation, buildCodexProjectPluginListInvocations, buildCodexPluginRemoveInvocation, postcheckCodexProjectPluginActivation, runNativeCliChecked, } from '../utils/native-cli.js';
15
+ import { OWNED_TEXT_HEADER_PREFIX, createSharedOwnerToken, parseSharedOwnerToken, parseOwnedTextHeader, readSkillOwnerMarker, inspectOwnedComponents, writeOwnedTextFile, } from '../utils/ownership.js';
15
16
  import { resolveArtifactRenderConfig, runArtifactRenderer } from '../utils/artifact-renderer.js';
16
17
  import { validateArtifactPlan } from '../utils/artifact-plan.js';
17
18
  import { createAwPluginRequirementsReceipt, readPluginManifest, } from '../utils/manifest.js';
@@ -23,11 +24,13 @@ import { readCatalogSourcesConfigSnapshot } from '../utils/catalog-sources.js';
23
24
  import { getNamespacedMarketplaceName, projectMarketplaceManifestForPlatform, replayNamespacedCodexSkillOverlays, } from '../utils/local-marketplace.js';
24
25
  import { cleanupOrphanNamespacedMarketplaceRoot, commitMarketplaceGenerationBackup, } from '../utils/orphan-marketplace-cleanup.js';
25
26
  import { readClaudeSettings, writeClaudeSettings } from '../adapters/helpers.js';
26
- import { requiresCodexNativePlugin } from '../utils/codex-project-surface.js';
27
+ import { analyzeCodexSurface } from '../utils/codex-surface-analysis.js';
28
+ import { inspectCodexSharedNative, } from '../utils/codex-shared-native.js';
29
+ import { calculateCodexSharedNativePayloadDigest } from '../utils/catalog-digests.js';
27
30
  import { assertCanonicalProjectManagedRoots } from '../utils/project-boundary.js';
28
31
  import { findCodexRoleNameConflict, resolveCodexRoleNamespace, } from '../utils/codex-roles.js';
29
32
  const MARKETPLACE_NAME = 'aw';
30
- const MCP_OWNER_ENV = 'AW_AGENT_WORKFLOWS_OWNER';
33
+ const LEGACY_MCP_OWNER_ENV = 'AW_AGENT_WORKFLOWS_OWNER';
31
34
  function requirementsReceipt(plugin) {
32
35
  const receipt = createAwPluginRequirementsReceipt({ name: plugin.manifest.name, version: plugin.manifest.version }, plugin.manifest.requires?.aw.capabilities ?? []);
33
36
  return Buffer.from(`${JSON.stringify(receipt, null, 2)}\n`, 'utf8');
@@ -38,6 +41,112 @@ function requireCodexManager(manager) {
38
41
  }
39
42
  return manager;
40
43
  }
44
+ export async function executeCodexReconcileActivations(options) {
45
+ const byScope = {
46
+ user: { upserts: {}, removals: new Set() },
47
+ project: { upserts: {}, removals: new Set() },
48
+ };
49
+ for (const plan of options.plans) {
50
+ for (const scope of ['user', 'project']) {
51
+ for (const action of plan.activationActions[scope]) {
52
+ if (action.action === 'enable' || action.action === 'disable') {
53
+ byScope[scope].upserts[action.codexPluginId] = action.desired;
54
+ byScope[scope].removals.delete(action.codexPluginId);
55
+ }
56
+ else if (action.action === 'remove') {
57
+ byScope[scope].removals.add(action.codexPluginId);
58
+ }
59
+ }
60
+ }
61
+ for (const removal of plan.staleActivationRemovals) {
62
+ if (!Object.hasOwn(byScope[removal.scope].upserts, removal.codexPluginId)) {
63
+ byScope[removal.scope].removals.add(removal.codexPluginId);
64
+ }
65
+ }
66
+ }
67
+ for (const scope of ['user', 'project']) {
68
+ const enables = Object.fromEntries(Object.entries(byScope[scope].upserts)
69
+ .filter(([, desired]) => desired));
70
+ if (Object.keys(enables).length === 0)
71
+ continue;
72
+ const configPath = resolveCodexPaths(options.cwd, scope, options.env).configTomlPath;
73
+ await applyCodexConfigTransactionFile(configPath, { upsertPluginActivations: enables });
74
+ const enabledConfig = await readCodexConfigFile(configPath);
75
+ for (const pluginId of Object.keys(enables)) {
76
+ if (enabledConfig.snapshot.plugins[pluginId]?.enabled !== true) {
77
+ throw new Error(`codexActivationPostcheckFailed: ${pluginId} ${scope} activation was not enabled before obsolete activation removal.`);
78
+ }
79
+ }
80
+ }
81
+ for (const scope of ['user', 'project']) {
82
+ const transaction = byScope[scope];
83
+ const disables = Object.fromEntries(Object.entries(transaction.upserts)
84
+ .filter(([, desired]) => !desired));
85
+ if (Object.keys(disables).length === 0 && transaction.removals.size === 0)
86
+ continue;
87
+ await applyCodexConfigTransactionFile(resolveCodexPaths(options.cwd, scope, options.env).configTomlPath, {
88
+ ...(transaction.removals.size > 0
89
+ ? { removePluginActivations: [...transaction.removals].sort() }
90
+ : {}),
91
+ ...(Object.keys(disables).length > 0
92
+ ? { upsertPluginActivations: disables }
93
+ : {}),
94
+ });
95
+ }
96
+ const [userConfig, projectConfig] = await Promise.all([
97
+ readCodexConfigFile(resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath),
98
+ readCodexConfigFile(resolveCodexPaths(options.cwd, 'project', options.env).configTomlPath),
99
+ ]);
100
+ for (const plan of options.plans) {
101
+ for (const scope of ['user', 'project']) {
102
+ const snapshot = scope === 'user' ? userConfig.snapshot : projectConfig.snapshot;
103
+ for (const action of plan.activationActions[scope]) {
104
+ const actual = snapshot.plugins[action.codexPluginId]?.enabled;
105
+ const expected = action.desired
106
+ ? true
107
+ : action.action === 'disable' || actual === false ? false : undefined;
108
+ if (actual !== expected) {
109
+ throw new Error(`codexActivationPostcheckFailed: ${action.codexPluginId} ${scope} activation is ${String(actual)}; expected ${String(expected)}.`);
110
+ }
111
+ }
112
+ for (const removal of plan.staleActivationRemovals.filter((entry) => entry.scope === scope)) {
113
+ if (snapshot.plugins[removal.codexPluginId]?.enabled !== undefined) {
114
+ throw new Error(`codexActivationPostcheckFailed: stale ${removal.codexPluginId} ${scope} activation remains.`);
115
+ }
116
+ }
117
+ }
118
+ }
119
+ const observedProjectChecks = options.plans.flatMap((plan) => plan.postcheck.support === 'observed'
120
+ ? plan.postcheck.plugins.filter((plugin) => plugin.neutralCwdRequired)
121
+ : []);
122
+ if (observedProjectChecks.length === 0)
123
+ return;
124
+ const manager = requireCodexManager(options.manager);
125
+ const neutralCwd = await fs.mkdtemp(path.join(os.tmpdir(), 'aw-codex-postcheck-'));
126
+ try {
127
+ const invocations = buildCodexProjectPluginListInvocations(manager, {
128
+ exactCwd: options.cwd,
129
+ neutralCwd,
130
+ env: options.env,
131
+ });
132
+ const [exactResult, neutralResult] = await Promise.all([
133
+ runNativeCliChecked(invocations.exact, options.runner),
134
+ runNativeCliChecked(invocations.neutral, options.runner),
135
+ ]);
136
+ for (const plugin of observedProjectChecks) {
137
+ postcheckCodexProjectPluginActivation({
138
+ pluginId: plugin.codexPluginId,
139
+ userEnabled: plugin.expectedUserEnabled,
140
+ projectEnabled: plugin.expectedProjectEnabled,
141
+ exactResult,
142
+ neutralResult,
143
+ });
144
+ }
145
+ }
146
+ finally {
147
+ await fs.remove(neutralCwd);
148
+ }
149
+ }
41
150
  const CODEX_REMOVE_RECOVERY = 'Codex residue was retained. Retry cleanup or uninstall, upgrade AW, or install a compatible official Codex CLI.';
42
151
  function ownerIdentity(plugin, artifactKey, platform, scope) {
43
152
  return { owner: 'agent-workflows', plugin, artifactKey, platform, scope };
@@ -202,84 +311,85 @@ function createCodexInstructionComponent(options) {
202
311
  },
203
312
  };
204
313
  }
205
- function codexMcpOwner(actual) {
206
- const envValue = actual?.value?.env;
207
- return envValue
208
- && typeof envValue === 'object'
209
- && !Array.isArray(envValue)
210
- && typeof envValue[MCP_OWNER_ENV] === 'string'
211
- ? envValue[MCP_OWNER_ENV]
212
- : null;
314
+ function createCodexActivationComponent(options) {
315
+ return {
316
+ kind: 'shared-config',
317
+ identity: options.identity,
318
+ label: `${options.configPath}#plugins.${options.codexPluginId}.enabled`,
319
+ inspect: async () => {
320
+ const plugin = (await readCodexConfigFile(options.configPath))
321
+ .snapshot.plugins[options.codexPluginId];
322
+ return plugin?.enabled === options.desired ? 'present' : 'absent';
323
+ },
324
+ remove: async () => {
325
+ await applyCodexConfigTransactionFile(options.configPath, {
326
+ removePluginActivations: [options.codexPluginId],
327
+ });
328
+ },
329
+ };
213
330
  }
214
- function createCodexMcpComponent(options) {
215
- const ownerKey = createSharedOwnerToken(options.identity);
331
+ function createCodexActivationCleanupComponent(options) {
216
332
  return {
217
333
  kind: 'shared-config',
218
334
  identity: options.identity,
219
- label: `${options.configPath}#mcp_servers.${options.serverKey}`,
335
+ label: `${options.configPath}#plugins.${options.codexPluginId}.enabled`,
220
336
  inspect: async () => {
221
- const config = await readCodexConfigFile(options.configPath);
222
- const actual = config.snapshot.mcpServers[options.serverKey];
223
- if (!actual)
224
- return 'absent';
225
- return codexMcpOwner(actual) === ownerKey ? 'present' : 'foreign';
337
+ const actual = (await readCodexConfigFile(options.configPath))
338
+ .snapshot.plugins[options.codexPluginId]?.enabled;
339
+ return options.disposition === 'remove'
340
+ ? actual === undefined ? 'absent' : 'present'
341
+ : actual === true ? 'present' : 'absent';
226
342
  },
227
343
  remove: async () => {
228
344
  await applyConditionalCodexConfigTransactionFile(options.configPath, (snapshot) => {
229
- const actual = snapshot.mcpServers[options.serverKey];
230
- return codexMcpOwner(actual) === ownerKey
231
- ? { removeMcpServers: [options.serverKey] }
345
+ const actual = snapshot.plugins[options.codexPluginId]?.enabled;
346
+ if (options.disposition === 'remove') {
347
+ return actual === undefined
348
+ ? null
349
+ : { removePluginActivations: [options.codexPluginId] };
350
+ }
351
+ return actual === true
352
+ ? { upsertPluginActivations: { [options.codexPluginId]: false } }
232
353
  : null;
233
354
  });
234
355
  },
235
356
  };
236
357
  }
237
- async function codexRoleRegistrationOwned(options) {
238
- if (!options.registration?.configFile)
239
- return false;
240
- const expected = path.normalize(path.relative(path.dirname(options.configPath), options.rolePath));
241
- const actual = path.normalize(path.relative(path.dirname(options.configPath), path.resolve(path.dirname(options.configPath), options.registration.configFile)));
242
- if (actual !== expected)
243
- return false;
244
- const raw = await fs.readFile(options.rolePath, 'utf8').catch(() => '');
245
- try {
246
- return Boolean(parseOwnedTextHeader(raw, options.fileIdentity));
247
- }
248
- catch {
249
- return false;
250
- }
358
+ function legacyCodexMcpOwner(actual) {
359
+ const envValue = actual?.value?.env;
360
+ return envValue
361
+ && typeof envValue === 'object'
362
+ && !Array.isArray(envValue)
363
+ && typeof envValue[LEGACY_MCP_OWNER_ENV] === 'string'
364
+ ? envValue[LEGACY_MCP_OWNER_ENV]
365
+ : null;
251
366
  }
252
- function createCodexRoleRegistrationComponent(options) {
253
- const inspect = async () => {
254
- const config = await readCodexConfigFile(options.configPath);
255
- const registration = config.snapshot.agents[options.roleKey];
256
- if (!registration)
257
- return 'absent';
258
- return await codexRoleRegistrationOwned({ ...options, registration })
259
- ? 'present'
260
- : 'foreign';
261
- };
367
+ function createLegacyCodexMcpCleanupComponent(options) {
368
+ const ownerKey = createSharedOwnerToken(options.identity);
262
369
  return {
263
370
  kind: 'shared-config',
264
371
  identity: options.identity,
265
- label: `${options.configPath}#agents.${options.roleKey}`,
266
- inspect,
372
+ label: `${options.configPath}#mcp_servers.${options.serverKey}`,
373
+ inspect: async () => {
374
+ const actual = (await readCodexConfigFile(options.configPath))
375
+ .snapshot.mcpServers[options.serverKey];
376
+ if (!actual)
377
+ return 'absent';
378
+ return legacyCodexMcpOwner(actual) === ownerKey ? 'present' : 'foreign';
379
+ },
267
380
  remove: async () => {
268
- await applyConditionalCodexConfigTransactionFile(options.configPath, async (snapshot) => {
269
- const registration = snapshot.agents[options.roleKey];
270
- return await codexRoleRegistrationOwned({ ...options, registration })
271
- ? { removeAgentRegistrations: [options.roleKey] }
272
- : null;
273
- });
381
+ await applyConditionalCodexConfigTransactionFile(options.configPath, (snapshot) => legacyCodexMcpOwner(snapshot.mcpServers[options.serverKey]) === ownerKey
382
+ ? { removeMcpServers: [options.serverKey] }
383
+ : null);
274
384
  },
275
385
  };
276
386
  }
277
- async function readCodexHooks(hooksPath) {
387
+ async function readLegacyCodexHooks(hooksPath) {
278
388
  if (!await fs.pathExists(hooksPath))
279
389
  return parseCodexSharedHooksFile({ hooks: {} });
280
390
  return parseCodexSharedHooksFile(await fs.readJson(hooksPath));
281
391
  }
282
- function renderCodexHookGroups(groups) {
392
+ function renderLegacyCodexHookGroups(groups) {
283
393
  const hooks = {};
284
394
  for (const group of groups) {
285
395
  hooks[group.event] ??= [];
@@ -287,16 +397,15 @@ function renderCodexHookGroups(groups) {
287
397
  }
288
398
  return `${JSON.stringify({ hooks }, null, 2)}\n`;
289
399
  }
290
- export async function applyCodexHookGroupsTransactionFile(hooksPath, update, testHooks) {
400
+ async function applyLegacyCodexHookCleanupTransactionFile(hooksPath, update) {
291
401
  return withConfigFileMutation({
292
402
  filePath: hooksPath,
293
403
  allowedRoot: path.dirname(hooksPath),
294
404
  action: async () => {
295
- const parsed = await readCodexHooks(hooksPath);
405
+ const parsed = await readLegacyCodexHooks(hooksPath);
296
406
  const groups = update(parsed);
297
407
  if (!groups)
298
408
  return false;
299
- await testHooks?.beforeCommit?.();
300
409
  if (groups.length === 0) {
301
410
  await atomicRemoveConfigFile({
302
411
  filePath: hooksPath,
@@ -307,40 +416,27 @@ export async function applyCodexHookGroupsTransactionFile(hooksPath, update, tes
307
416
  await atomicReplaceConfigFile({
308
417
  filePath: hooksPath,
309
418
  allowedRoot: path.dirname(hooksPath),
310
- content: renderCodexHookGroups(groups),
419
+ content: renderLegacyCodexHookGroups(groups),
311
420
  });
312
421
  }
313
422
  return true;
314
423
  },
315
424
  });
316
425
  }
317
- function renderCodexSharedHookGroup(identity) {
318
- const rendered = renderTrackedHookGroupIdentity(identity);
319
- const rawGroup = {
320
- ...(rendered.matcher === undefined ? {} : { matcher: rendered.matcher }),
321
- hooks: rendered.hooks,
322
- };
323
- return {
324
- event: rendered.event,
325
- matcher: rendered.matcher,
326
- hooks: rendered.hooks,
327
- rawGroup,
328
- };
329
- }
330
- function createCodexHookComponent(options) {
426
+ function createLegacyCodexHookCleanupComponent(options) {
331
427
  const serialized = serializeTrackedHookGroupIdentity(options.identity);
332
428
  return {
333
429
  kind: 'shared-config',
334
430
  identity: ownerIdentity(options.pluginName, internalArtifactKey('hook', serialized), 'codex', options.scope),
335
431
  label: `${options.hooksPath}#${serialized}`,
336
432
  inspect: async () => {
337
- const parsed = await readCodexHooks(options.hooksPath);
433
+ const parsed = await readLegacyCodexHooks(options.hooksPath);
338
434
  return parsed.managedIdentities.some((entry) => serializeTrackedHookGroupIdentity(entry) === serialized)
339
435
  ? 'present'
340
436
  : 'absent';
341
437
  },
342
438
  remove: async () => {
343
- await applyCodexHookGroupsTransactionFile(options.hooksPath, (parsed) => {
439
+ await applyLegacyCodexHookCleanupTransactionFile(options.hooksPath, (parsed) => {
344
440
  let removed = false;
345
441
  const remaining = parsed.groups.flatMap((entry) => {
346
442
  if (!removed
@@ -356,19 +452,44 @@ function createCodexHookComponent(options) {
356
452
  },
357
453
  };
358
454
  }
359
- function ownedCodexHookIdentity(options) {
360
- const artifactKey = internalArtifactKey('hook', serializeTrackedHookGroupIdentity(options.identity));
361
- const identity = {
362
- ...ownerIdentity(options.pluginName, artifactKey, 'codex', options.scope),
363
- ...(options.sourceId ? {
364
- sourceId: options.sourceId,
365
- sourceIdentity: options.sourceIdentity,
366
- pluginId: options.pluginId,
367
- } : {}),
455
+ async function codexRoleRegistrationOwned(options) {
456
+ if (!options.registration?.configFile)
457
+ return false;
458
+ const expected = path.normalize(path.relative(path.dirname(options.configPath), options.rolePath));
459
+ const actual = path.normalize(path.relative(path.dirname(options.configPath), path.resolve(path.dirname(options.configPath), options.registration.configFile)));
460
+ if (actual !== expected)
461
+ return false;
462
+ const raw = await fs.readFile(options.rolePath, 'utf8').catch(() => '');
463
+ try {
464
+ return Boolean(parseOwnedTextHeader(raw, options.fileIdentity));
465
+ }
466
+ catch {
467
+ return false;
468
+ }
469
+ }
470
+ function createCodexRoleRegistrationComponent(options) {
471
+ const inspect = async () => {
472
+ const config = await readCodexConfigFile(options.configPath);
473
+ const registration = config.snapshot.agents[options.roleKey];
474
+ if (!registration)
475
+ return 'absent';
476
+ return await codexRoleRegistrationOwned({ ...options, registration })
477
+ ? 'present'
478
+ : 'foreign';
368
479
  };
369
480
  return {
370
- ...options.identity,
371
- command: `${OWNED_TEXT_HEADER_PREFIX}${JSON.stringify(JSON.parse(createSkillOwnerMarkerContent(identity)))}\n${options.identity.command}`,
481
+ kind: 'shared-config',
482
+ identity: options.identity,
483
+ label: `${options.configPath}#agents.${options.roleKey}`,
484
+ inspect,
485
+ remove: async () => {
486
+ await applyConditionalCodexConfigTransactionFile(options.configPath, async (snapshot) => {
487
+ const registration = snapshot.agents[options.roleKey];
488
+ return await codexRoleRegistrationOwned({ ...options, registration })
489
+ ? { removeAgentRegistrations: [options.roleKey] }
490
+ : null;
491
+ });
492
+ },
372
493
  };
373
494
  }
374
495
  function sameOwnerIdentity(left, right) {
@@ -381,17 +502,6 @@ function sameOwnerIdentity(left, right) {
381
502
  && left.platform === right.platform
382
503
  && left.scope === right.scope;
383
504
  }
384
- function readOwnedHookIdentity(identity, scope) {
385
- try {
386
- const parsed = parseOwnedTextHeader(identity.command);
387
- return parsed?.identity.platform === 'codex' && parsed.identity.scope === scope
388
- ? parsed.identity
389
- : null;
390
- }
391
- catch {
392
- return null;
393
- }
394
- }
395
505
  function sharedOwnerPluginName(value) {
396
506
  return parseSharedOwnerToken(value)?.plugin ?? value;
397
507
  }
@@ -415,7 +525,6 @@ async function scanCodexSharedComponents(options) {
415
525
  remove: async () => undefined,
416
526
  });
417
527
  const containsOwnedEvidence = (raw) => raw.includes(options.pluginName) && (raw.includes('[[aw-managed:start plugin=')
418
- || raw.includes(MCP_OWNER_ENV)
419
528
  || raw.includes(OWNED_TEXT_HEADER_PREFIX)
420
529
  || raw.includes('agent-workflows'));
421
530
  if (options.scanConfig) {
@@ -437,24 +546,6 @@ async function scanCodexSharedComponents(options) {
437
546
  configPath: options.paths.configTomlPath,
438
547
  }));
439
548
  }
440
- for (const [serverKey, server] of Object.entries(config?.snapshot.mcpServers ?? {})) {
441
- const envValue = server.value?.env;
442
- if (envValue
443
- && typeof envValue === 'object'
444
- && !Array.isArray(envValue)
445
- && parseSharedOwnerToken(String(envValue[MCP_OWNER_ENV]))?.pluginId === options.pluginId) {
446
- const identity = expectedIdentity(internalArtifactKey('mcp', serverKey));
447
- if (createSharedOwnerToken(identity) !== envValue[MCP_OWNER_ENV])
448
- continue;
449
- output.push(createCodexMcpComponent({
450
- identity,
451
- scope: options.scope,
452
- configPath: options.paths.configTomlPath,
453
- serverKey,
454
- owned: true,
455
- }));
456
- }
457
- }
458
549
  for (const [roleKey, registration] of Object.entries(config?.snapshot.agents ?? {})) {
459
550
  if (!registration.configFile)
460
551
  continue;
@@ -479,28 +570,6 @@ async function scanCodexSharedComponents(options) {
479
570
  }));
480
571
  }
481
572
  }
482
- if (options.scope === 'project' && options.scanHooks) {
483
- const hooks = await readCodexHooks(options.paths.hooksPath).catch(async (error) => {
484
- if (!options.ignoreReadErrors)
485
- throw error;
486
- const raw = await fs.readFile(options.paths.hooksPath, 'utf8').catch(() => '');
487
- if (containsOwnedEvidence(raw)) {
488
- output.push(corruptedComponent(options.paths.hooksPath, error));
489
- }
490
- return null;
491
- });
492
- for (const identity of hooks?.managedIdentities ?? []) {
493
- const parsedIdentity = readOwnedHookIdentity(identity, options.scope);
494
- if (!parsedIdentity || !sameOwnerIdentity(parsedIdentity, expectedIdentity(parsedIdentity.artifactKey)))
495
- continue;
496
- output.push(createCodexHookComponent({
497
- pluginName: options.pluginName,
498
- scope: options.scope,
499
- hooksPath: options.paths.hooksPath,
500
- identity,
501
- }));
502
- }
503
- }
504
573
  return output;
505
574
  }
506
575
  async function scanClaudeSharedComponents(options) {
@@ -615,14 +684,19 @@ async function preflightCodexWriteSafety(options) {
615
684
  const pluginName = options.plugin.manifest.name;
616
685
  const userConfigPath = resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath;
617
686
  const projectConfigPath = resolveCodexPaths(options.cwd, 'project', options.env).configTomlPath;
687
+ const nativeSurface = analyzeCodexSurface(options.plugin.manifest).nativeKinds.length > 0;
688
+ const needsUserConfig = options.scope === 'user'
689
+ || nativeSurface;
618
690
  let userConfig;
619
- try {
620
- userConfig = await readCodexConfigFile(userConfigPath);
621
- }
622
- catch (error) {
623
- throw new Error(`codexProjectUserConfigUnreadable: 无法解析用户级 Codex 配置 ${userConfigPath}:${error instanceof Error ? error.message : String(error)}`);
691
+ if (needsUserConfig) {
692
+ try {
693
+ userConfig = await readCodexConfigFile(userConfigPath);
694
+ }
695
+ catch (error) {
696
+ throw new Error(`codexProjectUserConfigUnreadable: 无法解析用户级 Codex 配置 ${userConfigPath}:${error instanceof Error ? error.message : String(error)}`);
697
+ }
624
698
  }
625
- if (options.scope === 'project') {
699
+ if (options.scope === 'project' && userConfig) {
626
700
  const expectedPluginId = `${pluginName}@${options.plugin.marketplaceName ?? MARKETPLACE_NAME}`;
627
701
  for (const [key, plugin] of Object.entries(userConfig.snapshot.plugins)) {
628
702
  if (key !== expectedPluginId
@@ -637,7 +711,7 @@ async function preflightCodexWriteSafety(options) {
637
711
  && target.target === 'codex.agentRole'
638
712
  && target.roleKey)
639
713
  .map((target) => target.roleKey));
640
- const needsProjectConfig = options.scope === 'project' && Boolean(options.plugin.manifest.mcpServers
714
+ const needsProjectConfig = options.scope === 'project' && Boolean(nativeSurface
641
715
  || resolvePlatformEntries(options.plugin.manifest.extensions?.instructions, 'codex').length > 0
642
716
  || (options.plugin.manifest.artifacts?.targets ?? []).some((target) => target.platform === 'codex'
643
717
  && target.scopes.includes('project')
@@ -654,7 +728,7 @@ async function preflightCodexWriteSafety(options) {
654
728
  if (roleKeys.size === 0)
655
729
  return;
656
730
  for (const configPath of options.scope === 'project'
657
- ? [userConfigPath, projectConfigPath]
731
+ ? [projectConfigPath]
658
732
  : [userConfigPath]) {
659
733
  const configScope = configPath === userConfigPath ? 'user' : 'project';
660
734
  const ownedRolePaths = [];
@@ -705,33 +779,6 @@ function renderCodexRole(options) {
705
779
  '',
706
780
  ].join('\n');
707
781
  }
708
- async function resolveCodexMcpServers(plugin, scope) {
709
- if (!plugin.manifest.mcpServers)
710
- return {};
711
- const relative = plugin.manifest.mcpServers.replace(/^\.\//u, '');
712
- const direct = path.resolve(plugin.rootDir, relative);
713
- const mcpPath = await fs.pathExists(direct)
714
- ? direct
715
- : path.resolve(plugin.rootDir, 'codex', relative);
716
- const servers = await readRenderedCodexMcpServersFile(mcpPath);
717
- return Object.fromEntries(Object.entries(servers).map(([key, server]) => {
718
- const existingEnv = server.env;
719
- if (existingEnv !== undefined && (typeof existingEnv !== 'object' || Array.isArray(existingEnv))) {
720
- throw new Error(`MCP server ${key} 的 env 必须是 object。`);
721
- }
722
- const env = (existingEnv ?? {});
723
- const ownerKey = createSharedOwnerToken(pluginOwnerIdentity(plugin, internalArtifactKey('mcp', key), 'codex', scope));
724
- if (env[MCP_OWNER_ENV] !== undefined
725
- && env[MCP_OWNER_ENV] !== plugin.manifest.name
726
- && env[MCP_OWNER_ENV] !== ownerKey) {
727
- throw new Error(`MCP server ${key} 不能覆盖保留的 ${MCP_OWNER_ENV} owner marker。`);
728
- }
729
- return [key, {
730
- ...server,
731
- env: { ...env, [MCP_OWNER_ENV]: ownerKey },
732
- }];
733
- }));
734
- }
735
782
  async function resolveDynamicArtifacts(options) {
736
783
  const declarations = (options.plugin.manifest.artifacts?.targets ?? []).filter((target) => target.platform === options.platform && target.scopes.includes(options.scope));
737
784
  if (declarations.length === 0)
@@ -845,9 +892,7 @@ async function buildBundledCodexSkillWrites(options) {
845
892
  const dynamicKeys = new Set((options.plugin.manifest.artifacts?.targets ?? [])
846
893
  .filter((target) => target.target === 'codex.skill' && target.skillKey)
847
894
  .map((target) => target.skillKey));
848
- const targetRoot = options.scope === 'project'
849
- ? path.join(options.repoRoot, '.agents', 'skills')
850
- : path.join(options.marketplaceRoot ?? resolveAwHome(options.env), 'plugins', options.plugin.manifest.name, ...relativeRoot.split('/'));
895
+ const targetRoot = await canonicalizePathThroughExistingAncestor(path.join(options.marketplaceRoot ?? resolveAwHome(options.env), 'plugins', options.plugin.manifest.name, ...relativeRoot.split('/')));
851
896
  const writes = [];
852
897
  for (const entry of await fs.readdir(sourceRoot, { withFileTypes: true })) {
853
898
  if (entry.isSymbolicLink()) {
@@ -869,9 +914,9 @@ async function buildBundledCodexSkillWrites(options) {
869
914
  sourceRequirementsReceiptPolicy: 'allow',
870
915
  });
871
916
  const skillPath = path.join(targetRoot, entry.name);
872
- const canRecoverPristine = options.scope === 'user'
873
- && path.resolve(skillSource) !== path.resolve(skillPath);
874
- const identity = ownerIdentity(options.plugin.manifest.name, entry.name, 'codex', options.scope);
917
+ const canRecoverPristine = await canonicalizePathThroughExistingAncestor(skillSource)
918
+ !== path.resolve(skillPath);
919
+ const identity = ownerIdentity(options.plugin.manifest.name, entry.name, 'codex', 'user');
875
920
  const component = pathComponent({
876
921
  kind: 'skill-directory',
877
922
  path: skillPath,
@@ -971,16 +1016,15 @@ async function buildCodexWrites(options) {
971
1016
  const dynamicRoles = dynamic.artifacts.filter((entry) => entry.target === 'codex.agentRole');
972
1017
  const dynamicSkills = dynamic.artifacts.filter((entry) => entry.target === 'codex.skill');
973
1018
  const repoRoot = await resolveRepoRoot(options.cwd);
974
- writes.push(...await buildBundledCodexSkillWrites({
975
- plugin: options.plugin,
976
- scope: options.scope,
977
- repoRoot,
978
- env: options.env,
979
- marketplaceRoot: options.marketplaceRoot,
980
- }));
981
- const relativeSkillsRoot = await resolveCodexSkillsRelativeRoot(options.plugin);
1019
+ if (options.scope === 'user') {
1020
+ writes.push(...await buildBundledCodexSkillWrites({
1021
+ plugin: options.plugin,
1022
+ env: options.env,
1023
+ marketplaceRoot: options.marketplaceRoot,
1024
+ }));
1025
+ }
982
1026
  const skillRoot = options.scope === 'user'
983
- ? path.join(options.marketplaceRoot ?? resolveAwHome(options.env), 'plugins', options.plugin.manifest.name, ...relativeSkillsRoot.split('/'))
1027
+ ? path.join(getCodexHome(options.env), 'skills')
984
1028
  : path.join(repoRoot, '.agents', 'skills');
985
1029
  for (const artifact of dynamicSkills) {
986
1030
  const tree = await createCodexSkillExpectedTree({
@@ -1002,7 +1046,6 @@ async function buildCodexWrites(options) {
1002
1046
  const staticInstructions = dynamicInstructions.length > 0
1003
1047
  ? []
1004
1048
  : resolvePlatformEntries(options.plugin.manifest.extensions?.instructions, 'codex');
1005
- const mcpServers = await resolveCodexMcpServers(options.plugin, options.scope);
1006
1049
  const hasInstructionOutput = dynamicInstructions.length > 0 || staticInstructions.length > 0;
1007
1050
  if (hasInstructionOutput) {
1008
1051
  const component = createCodexInstructionComponent({
@@ -1052,34 +1095,6 @@ async function buildCodexWrites(options) {
1052
1095
  });
1053
1096
  }
1054
1097
  }
1055
- for (const [serverKey, server] of Object.entries(mcpServers)) {
1056
- const component = createCodexMcpComponent({
1057
- identity: pluginOwnerIdentity(options.plugin, internalArtifactKey('mcp', serverKey), 'codex', options.scope),
1058
- scope: options.scope,
1059
- configPath: paths.configTomlPath,
1060
- serverKey,
1061
- owned: false,
1062
- });
1063
- writes.push({
1064
- artifact: {
1065
- path: paths.configTomlPath,
1066
- kind: 'mcpServer',
1067
- platform: 'codex',
1068
- artifactKey: serverKey,
1069
- },
1070
- component,
1071
- write: async () => {
1072
- await applyConditionalCodexConfigTransactionFile(paths.configTomlPath, (snapshot) => {
1073
- const actual = snapshot.mcpServers[serverKey];
1074
- const actualOwner = codexMcpOwner(actual);
1075
- if (actual && actualOwner !== createSharedOwnerToken(component.identity)) {
1076
- throw new Error(`Codex MCP server "${serverKey}" is foreign and cannot be overwritten by plugin "${options.plugin.manifest.name}".`);
1077
- }
1078
- return { upsertMcpServers: { [serverKey]: server } };
1079
- });
1080
- },
1081
- });
1082
- }
1083
1098
  for (const artifact of dynamicRoles) {
1084
1099
  const roleKey = artifact.roleKey;
1085
1100
  const rolePath = path.join(paths.configDir, 'agents', `${buildGeneratedName(options.plugin.manifest.name, roleKey)}.toml`);
@@ -1179,42 +1194,40 @@ async function buildCodexWrites(options) {
1179
1194
  kind: 'agent',
1180
1195
  }));
1181
1196
  }
1182
- if (options.scope === 'project') {
1183
- const hooks = resolvePlatformHookDefinitions(options.plugin.manifest, 'codex');
1184
- for (const rawIdentity of extractCodexHookGroups(hooks)) {
1185
- const identity = ownedCodexHookIdentity({
1186
- pluginName: options.plugin.manifest.name,
1187
- sourceId: options.plugin.sourceId,
1188
- sourceIdentity: options.plugin.sourceIdentity,
1189
- pluginId: options.plugin.pluginId,
1190
- scope: options.scope,
1191
- identity: rawIdentity,
1192
- });
1193
- const component = createCodexHookComponent({
1194
- pluginName: options.plugin.manifest.name,
1195
- scope: options.scope,
1196
- hooksPath: paths.hooksPath,
1197
- identity,
1198
- });
1199
- writes.push({
1200
- artifact: {
1201
- path: paths.hooksPath,
1202
- kind: 'hook',
1203
- platform: 'codex',
1204
- hookGroups: [identity],
1205
- },
1206
- component,
1207
- write: async () => {
1208
- await applyCodexHookGroupsTransactionFile(paths.hooksPath, (parsed) => {
1209
- const serialized = serializeTrackedHookGroupIdentity(identity);
1210
- const exists = parsed.managedIdentities.some((entry) => serializeTrackedHookGroupIdentity(entry) === serialized);
1211
- return exists
1212
- ? null
1213
- : [...parsed.rawGroups, renderCodexSharedHookGroup(identity)];
1214
- });
1215
- },
1216
- });
1217
- }
1197
+ const codexPluginId = `${options.plugin.manifest.name}@${options.marketplaceName ?? options.plugin.marketplaceName ?? MARKETPLACE_NAME}`;
1198
+ const nativeSurface = analyzeCodexSurface(options.plugin.manifest).nativeKinds.length > 0;
1199
+ const userActivationDesired = options.scope === 'user'
1200
+ || options.userActivationDesired?.has(codexPluginId) === true;
1201
+ for (const activation of !nativeSurface ? [] : options.scope === 'project'
1202
+ ? [
1203
+ {
1204
+ scope: 'user',
1205
+ desired: userActivationDesired,
1206
+ configPath: resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath,
1207
+ },
1208
+ { scope: 'project', desired: true, configPath: paths.configTomlPath },
1209
+ ]
1210
+ : [{ scope: 'user', desired: true, configPath: paths.configTomlPath }]) {
1211
+ const component = createCodexActivationComponent({
1212
+ identity: pluginOwnerIdentity(options.plugin, internalArtifactKey('activation', activation.scope), 'codex', options.scope),
1213
+ configPath: activation.configPath,
1214
+ codexPluginId,
1215
+ desired: activation.desired,
1216
+ });
1217
+ writes.push({
1218
+ artifact: {
1219
+ path: activation.configPath,
1220
+ kind: 'instruction',
1221
+ platform: 'codex',
1222
+ artifactKey: internalArtifactKey('activation', activation.scope),
1223
+ },
1224
+ component,
1225
+ write: async () => {
1226
+ await applyCodexConfigTransactionFile(activation.configPath, {
1227
+ upsertPluginActivations: { [codexPluginId]: activation.desired },
1228
+ });
1229
+ },
1230
+ });
1218
1231
  }
1219
1232
  return writes;
1220
1233
  }
@@ -1278,13 +1291,13 @@ async function buildInspectionWrites(options) {
1278
1291
  }
1279
1292
  const paths = resolveCodexPaths(options.cwd, options.scope, options.env);
1280
1293
  const repoRoot = await resolveRepoRoot(options.cwd);
1281
- writes.push(...await buildBundledCodexSkillWrites({
1282
- plugin: options.plugin,
1283
- scope: options.scope,
1284
- repoRoot,
1285
- env: options.env,
1286
- marketplaceRoot: options.marketplaceRoot,
1287
- }));
1294
+ if (options.scope === 'user') {
1295
+ writes.push(...await buildBundledCodexSkillWrites({
1296
+ plugin: options.plugin,
1297
+ env: options.env,
1298
+ marketplaceRoot: options.marketplaceRoot,
1299
+ }));
1300
+ }
1288
1301
  const instructions = resolvePlatformEntries(options.plugin.manifest.extensions?.instructions, 'codex');
1289
1302
  const targets = (options.plugin.manifest.artifacts?.targets ?? []).filter((target) => target.platform === 'codex' && target.scopes.includes(options.scope));
1290
1303
  const instructionTargets = targets.filter((target) => target.target === 'codex.developerInstructionsBlock');
@@ -1304,28 +1317,8 @@ async function buildInspectionWrites(options) {
1304
1317
  write: async () => undefined,
1305
1318
  });
1306
1319
  }
1307
- const mcpServers = await resolveCodexMcpServers(options.plugin, options.scope);
1308
- for (const serverKey of Object.keys(mcpServers)) {
1309
- writes.push({
1310
- artifact: {
1311
- path: paths.configTomlPath,
1312
- kind: 'mcpServer',
1313
- platform: 'codex',
1314
- artifactKey: serverKey,
1315
- },
1316
- component: createCodexMcpComponent({
1317
- identity: pluginOwnerIdentity(options.plugin, internalArtifactKey('mcp', serverKey), 'codex', options.scope),
1318
- scope: options.scope,
1319
- configPath: paths.configTomlPath,
1320
- serverKey,
1321
- owned: false,
1322
- }),
1323
- write: async () => undefined,
1324
- });
1325
- }
1326
- const relativeSkillsRoot = await resolveCodexSkillsRelativeRoot(options.plugin);
1327
1320
  const dynamicSkillRoot = options.scope === 'user'
1328
- ? path.join(options.marketplaceRoot ?? resolveAwHome(options.env), 'plugins', options.plugin.manifest.name, ...relativeSkillsRoot.split('/'))
1321
+ ? path.join(getCodexHome(options.env), 'skills')
1329
1322
  : path.join(repoRoot, '.agents', 'skills');
1330
1323
  for (const target of targets) {
1331
1324
  const state = target.states.includes('present') ? 'present' : 'disabled';
@@ -1410,32 +1403,35 @@ async function buildInspectionWrites(options) {
1410
1403
  kind: 'agent',
1411
1404
  }));
1412
1405
  }
1413
- if (options.scope === 'project') {
1414
- for (const rawIdentity of extractCodexHookGroups(resolvePlatformHookDefinitions(options.plugin.manifest, 'codex'))) {
1415
- const identity = ownedCodexHookIdentity({
1416
- pluginName: options.plugin.manifest.name,
1417
- sourceId: options.plugin.sourceId,
1418
- sourceIdentity: options.plugin.sourceIdentity,
1419
- pluginId: options.plugin.pluginId,
1420
- scope: options.scope,
1421
- identity: rawIdentity,
1422
- });
1423
- writes.push({
1424
- artifact: {
1425
- path: paths.hooksPath,
1426
- kind: 'hook',
1427
- platform: 'codex',
1428
- hookGroups: [identity],
1429
- },
1430
- component: createCodexHookComponent({
1431
- pluginName: options.plugin.manifest.name,
1432
- scope: options.scope,
1433
- hooksPath: paths.hooksPath,
1434
- identity,
1435
- }),
1436
- write: async () => undefined,
1437
- });
1438
- }
1406
+ const nativeSurface = analyzeCodexSurface(options.plugin.manifest).nativeKinds.length > 0;
1407
+ const codexPluginId = `${options.plugin.manifest.name}@${options.marketplaceName ?? options.plugin.marketplaceName ?? MARKETPLACE_NAME}`;
1408
+ const userActivationDesired = options.scope === 'user'
1409
+ || options.userActivationDesired?.has(codexPluginId) === true;
1410
+ for (const activation of !nativeSurface ? [] : options.scope === 'project'
1411
+ ? [
1412
+ {
1413
+ scope: 'user',
1414
+ desired: userActivationDesired,
1415
+ configPath: resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath,
1416
+ },
1417
+ { scope: 'project', desired: true, configPath: paths.configTomlPath },
1418
+ ]
1419
+ : [{ scope: 'user', desired: true, configPath: paths.configTomlPath }]) {
1420
+ writes.push({
1421
+ artifact: {
1422
+ path: activation.configPath,
1423
+ kind: 'instruction',
1424
+ platform: 'codex',
1425
+ artifactKey: internalArtifactKey('activation', activation.scope),
1426
+ },
1427
+ component: createCodexActivationComponent({
1428
+ identity: pluginOwnerIdentity(options.plugin, internalArtifactKey('activation', activation.scope), 'codex', options.scope),
1429
+ configPath: activation.configPath,
1430
+ codexPluginId,
1431
+ desired: activation.desired,
1432
+ }),
1433
+ write: async () => undefined,
1434
+ });
1439
1435
  }
1440
1436
  return writes;
1441
1437
  }
@@ -1457,24 +1453,35 @@ async function buildHistoricalComponents(options) {
1457
1453
  continue;
1458
1454
  if (artifact.state === 'disabled')
1459
1455
  continue;
1460
- if (artifact.kind === 'hook' && options.platform === 'codex') {
1456
+ if (options.platform === 'codex'
1457
+ && artifact.artifactKey !== undefined
1458
+ && new Set([
1459
+ internalArtifactKey('activation', 'user'),
1460
+ internalArtifactKey('activation', 'project'),
1461
+ ]).has(artifact.artifactKey)) {
1462
+ continue;
1463
+ }
1464
+ if (options.command === 'cleanup'
1465
+ && artifact.kind === 'hook'
1466
+ && options.platform === 'codex') {
1461
1467
  for (const identity of artifact.hookGroups ?? []) {
1462
- components.push(createCodexHookComponent({
1468
+ components.push(createLegacyCodexHookCleanupComponent({
1463
1469
  pluginName: options.pluginName,
1464
1470
  scope: options.scope,
1465
- hooksPath: resolveCodexPaths(options.cwd, options.scope, options.env).hooksPath,
1471
+ hooksPath: path.join(resolveCodexPaths(options.cwd, options.scope, options.env).configDir, 'hooks.json'),
1466
1472
  identity,
1467
1473
  }));
1468
1474
  }
1469
1475
  continue;
1470
1476
  }
1471
- if (artifact.kind === 'mcpServer' && options.platform === 'codex' && artifact.artifactKey) {
1472
- components.push(createCodexMcpComponent({
1477
+ if (options.command === 'cleanup'
1478
+ && artifact.kind === 'mcpServer'
1479
+ && options.platform === 'codex'
1480
+ && artifact.artifactKey) {
1481
+ components.push(createLegacyCodexMcpCleanupComponent({
1473
1482
  identity: historicalIdentity(internalArtifactKey('mcp', artifact.artifactKey)),
1474
- scope: options.scope,
1475
1483
  configPath: resolveCodexPaths(options.cwd, options.scope, options.env).configTomlPath,
1476
1484
  serverKey: artifact.artifactKey,
1477
- owned: false,
1478
1485
  }));
1479
1486
  continue;
1480
1487
  }
@@ -1538,7 +1545,7 @@ async function readdirCodexResidueIfPresent(directory) {
1538
1545
  }
1539
1546
  return fs.readdir(directory, { withFileTypes: true });
1540
1547
  }
1541
- async function inspectCodexNativeResidue(options) {
1548
+ export async function inspectCodexNativeResidue(options) {
1542
1549
  const marketplaceName = options.marketplaceName ?? MARKETPLACE_NAME;
1543
1550
  const marketplaceRoot = options.marketplaceRoot ?? resolveAwHome(options.env);
1544
1551
  const pluginId = `${options.pluginName}@${marketplaceName}`;
@@ -1626,6 +1633,186 @@ async function inspectCodexNativeResidue(options) {
1626
1633
  installedVersions: [], cacheVersions: [], enabled: false,
1627
1634
  };
1628
1635
  }
1636
+ function isPathWithin(root, candidate) {
1637
+ const relative = path.relative(path.resolve(root), path.resolve(candidate));
1638
+ return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
1639
+ }
1640
+ async function codexPayloadPathSafe(payloadRoot) {
1641
+ const rootStats = await lstatCodexResidueIfPresent(payloadRoot);
1642
+ if (!rootStats?.isDirectory() || rootStats.isSymbolicLink())
1643
+ return false;
1644
+ const pending = [payloadRoot];
1645
+ while (pending.length > 0) {
1646
+ const current = pending.pop();
1647
+ for (const entry of await fs.readdir(current, { withFileTypes: true })) {
1648
+ const entryPath = path.join(current, entry.name);
1649
+ if (entry.isSymbolicLink()) {
1650
+ const target = path.resolve(path.dirname(entryPath), await fs.readlink(entryPath));
1651
+ if (!isPathWithin(payloadRoot, target))
1652
+ return false;
1653
+ }
1654
+ else if (entry.isDirectory()) {
1655
+ pending.push(entryPath);
1656
+ }
1657
+ else if (!entry.isFile()) {
1658
+ return false;
1659
+ }
1660
+ }
1661
+ }
1662
+ return true;
1663
+ }
1664
+ /**
1665
+ * Read-only filesystem projection for the payload-aware shared-native classifier.
1666
+ * It does not invoke Codex or create native/config state.
1667
+ */
1668
+ export async function inspectCodexSharedNativeLive(options) {
1669
+ const marketplaceName = options.marketplaceName ?? MARKETPLACE_NAME;
1670
+ const marketplaceRoot = options.marketplaceRoot ?? resolveAwHome(options.env);
1671
+ const pluginId = `${options.pluginName}@${marketplaceName}`;
1672
+ const config = await readCodexConfigFile(resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath);
1673
+ const cacheRoot = path.join(getCodexHome(options.env), 'plugins', 'cache', marketplaceName, options.pluginName);
1674
+ const installedManifestPath = path.join(getCodexHome(options.env), 'plugins', pluginId, 'manifest.json');
1675
+ const cacheRootStats = await lstatCodexResidueIfPresent(cacheRoot);
1676
+ let unsafeCachePath = Boolean(cacheRootStats
1677
+ && (!cacheRootStats.isDirectory() || cacheRootStats.isSymbolicLink()));
1678
+ const candidates = [];
1679
+ if (cacheRootStats?.isDirectory() && !cacheRootStats.isSymbolicLink()) {
1680
+ for (const entry of await fs.readdir(cacheRoot, { withFileTypes: true })) {
1681
+ const payloadPath = path.join(cacheRoot, entry.name);
1682
+ if (!entry.isDirectory() || entry.isSymbolicLink()) {
1683
+ unsafeCachePath = true;
1684
+ continue;
1685
+ }
1686
+ const manifestPaths = [
1687
+ path.join(payloadPath, 'plugin.json'),
1688
+ path.join(payloadPath, '.codex-plugin', 'plugin.json'),
1689
+ ];
1690
+ const existingManifestPaths = [];
1691
+ for (const manifestPath of manifestPaths) {
1692
+ const stats = await lstatCodexResidueIfPresent(manifestPath);
1693
+ if (stats)
1694
+ existingManifestPaths.push(manifestPath);
1695
+ }
1696
+ if (existingManifestPaths.length !== 1) {
1697
+ candidates.push({
1698
+ version: entry.name,
1699
+ manifest: existingManifestPaths.length === 0 ? { state: 'absent' } : { state: 'unreadable' },
1700
+ payloadPath,
1701
+ });
1702
+ continue;
1703
+ }
1704
+ const manifestPath = existingManifestPaths[0];
1705
+ const manifestStats = await lstatCodexResidueIfPresent(manifestPath);
1706
+ if (!manifestStats?.isFile() || manifestStats.isSymbolicLink()) {
1707
+ candidates.push({ version: entry.name, manifest: { state: 'unreadable' }, payloadPath });
1708
+ continue;
1709
+ }
1710
+ let manifest;
1711
+ try {
1712
+ const value = await fs.readJson(manifestPath);
1713
+ if (!value || typeof value !== 'object' || Array.isArray(value))
1714
+ throw new Error('invalid manifest');
1715
+ manifest = value;
1716
+ }
1717
+ catch {
1718
+ candidates.push({ version: entry.name, manifest: { state: 'unreadable' }, payloadPath });
1719
+ continue;
1720
+ }
1721
+ const pathSafe = await codexPayloadPathSafe(payloadPath);
1722
+ let payloadDigest = options.expectedPayloadDigest;
1723
+ if (pathSafe) {
1724
+ try {
1725
+ payloadDigest = await calculateCodexSharedNativePayloadDigest(payloadPath);
1726
+ }
1727
+ catch {
1728
+ candidates.push({ version: entry.name, manifest: { state: 'unreadable' }, payloadPath });
1729
+ continue;
1730
+ }
1731
+ }
1732
+ candidates.push({
1733
+ version: typeof manifest.version === 'string' ? manifest.version : entry.name,
1734
+ payloadPath,
1735
+ manifest: {
1736
+ state: 'present',
1737
+ identity: typeof manifest.id === 'string'
1738
+ ? manifest.id
1739
+ : typeof manifest.name === 'string' ? manifest.name : '',
1740
+ version: typeof manifest.version === 'string' ? manifest.version : '',
1741
+ payloadDigest,
1742
+ payloadPath,
1743
+ pathSafe,
1744
+ },
1745
+ });
1746
+ }
1747
+ }
1748
+ let installedVersion;
1749
+ const installedStats = await lstatCodexResidueIfPresent(installedManifestPath);
1750
+ if (installedStats?.isFile() && !installedStats.isSymbolicLink()) {
1751
+ try {
1752
+ const installed = await fs.readJson(installedManifestPath);
1753
+ if (typeof installed.version === 'string')
1754
+ installedVersion = installed.version;
1755
+ }
1756
+ catch {
1757
+ installedVersion = undefined;
1758
+ }
1759
+ }
1760
+ const selected = candidates.find((candidate) => candidate.version === installedVersion)
1761
+ ?? candidates.find((candidate) => candidate.version === options.desiredVersion)
1762
+ ?? (candidates.length === 1 ? candidates[0] : undefined);
1763
+ const pluginListEntries = candidates.map((candidate) => ({
1764
+ pluginId,
1765
+ enabled: config.snapshot.plugins[pluginId]?.enabled === true,
1766
+ ...(candidate.version ? { version: candidate.version } : {}),
1767
+ path: candidate.payloadPath,
1768
+ }));
1769
+ if (installedStats && pluginListEntries.length === 0) {
1770
+ pluginListEntries.push({
1771
+ pluginId,
1772
+ enabled: config.snapshot.plugins[pluginId]?.enabled === true,
1773
+ ...(installedVersion ? { version: installedVersion } : {}),
1774
+ path: path.dirname(installedManifestPath),
1775
+ });
1776
+ }
1777
+ if (unsafeCachePath && pluginListEntries.length === 0) {
1778
+ pluginListEntries.push({
1779
+ pluginId,
1780
+ enabled: config.snapshot.plugins[pluginId]?.enabled === true,
1781
+ version: options.desiredVersion,
1782
+ path: cacheRoot,
1783
+ });
1784
+ }
1785
+ const manifest = unsafeCachePath
1786
+ ? {
1787
+ state: 'present',
1788
+ identity: pluginId,
1789
+ version: options.desiredVersion,
1790
+ payloadDigest: options.expectedPayloadDigest,
1791
+ payloadPath: cacheRoot,
1792
+ pathSafe: false,
1793
+ }
1794
+ : selected?.manifest ?? { state: 'absent' };
1795
+ const sharedNative = inspectCodexSharedNative({
1796
+ pluginName: options.pluginName,
1797
+ pluginId,
1798
+ marketplaceName,
1799
+ canonicalMarketplaceRoot: marketplaceRoot,
1800
+ desiredVersion: options.desiredVersion,
1801
+ expectedPayloadDigest: options.expectedPayloadDigest,
1802
+ marketplaceRegistration: config.snapshot.marketplaces[marketplaceName],
1803
+ marketplacePathSafe: true,
1804
+ pluginListEntries,
1805
+ manifest,
1806
+ managedByCurrentIntent: options.managedByCurrentIntent && manifest.state === 'present',
1807
+ });
1808
+ return {
1809
+ sharedNative,
1810
+ ...(manifest.state === 'present' ? {
1811
+ actualPayloadDigest: manifest.payloadDigest,
1812
+ payloadPath: manifest.payloadPath,
1813
+ } : {}),
1814
+ };
1815
+ }
1629
1816
  export async function codexNativeRemovalRequired(options) {
1630
1817
  const residue = await inspectCodexNativeResidue(options);
1631
1818
  if (residue.state === 'present')
@@ -2457,6 +2644,8 @@ function createNativeDriver(options) {
2457
2644
  const codexManager = options.context.codexManager;
2458
2645
  const mirrorTransactions = new WeakMap();
2459
2646
  const finalizeNamespacedCleanup = async (target) => {
2647
+ if (target.identity.platform === 'codex')
2648
+ return;
2460
2649
  const { sourceId, sourceIdentity, marketplaceRoot, marketplaceName } = target.identity;
2461
2650
  if (!sourceId || !sourceIdentity || !marketplaceRoot)
2462
2651
  return;
@@ -2530,7 +2719,7 @@ function createNativeDriver(options) {
2530
2719
  });
2531
2720
  return state.pluginState;
2532
2721
  }
2533
- if (target.identity.platform === 'codex' && target.identity.scope === 'user') {
2722
+ if (target.identity.platform === 'codex') {
2534
2723
  const residue = await inspectCodexNativeResidue({
2535
2724
  pluginName: target.identity.pluginName,
2536
2725
  cwd: target.identity.cwd,
@@ -2562,7 +2751,7 @@ function createNativeDriver(options) {
2562
2751
  return state.pluginState;
2563
2752
  return state.pluginEnabled ? 'present' : 'partial';
2564
2753
  }
2565
- if (target.identity.platform === 'codex' && target.identity.scope === 'user') {
2754
+ if (target.identity.platform === 'codex') {
2566
2755
  const residue = await inspectCodexNativeResidue({
2567
2756
  pluginName: target.identity.pluginName,
2568
2757
  cwd: target.identity.cwd,
@@ -2572,7 +2761,34 @@ function createNativeDriver(options) {
2572
2761
  });
2573
2762
  if (residue.state !== 'present')
2574
2763
  return residue.state;
2575
- return residue.enabled ? 'present' : 'partial';
2764
+ if (target.identity.scope !== 'project' || !codexManager)
2765
+ return 'present';
2766
+ const userConfig = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath);
2767
+ const projectConfig = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'project', options.context.env).configTomlPath);
2768
+ const pluginId = `${target.identity.pluginName}@${target.identity.marketplaceName ?? MARKETPLACE_NAME}`;
2769
+ const neutralCwd = await fs.mkdtemp(path.join(os.tmpdir(), 'aw-codex-postcheck-'));
2770
+ try {
2771
+ const invocations = buildCodexProjectPluginListInvocations(codexManager, {
2772
+ exactCwd: target.identity.cwd,
2773
+ neutralCwd,
2774
+ env: options.context.env,
2775
+ });
2776
+ const [exactResult, neutralResult] = await Promise.all([
2777
+ runNativeCliChecked(invocations.exact, runner),
2778
+ runNativeCliChecked(invocations.neutral, runner),
2779
+ ]);
2780
+ postcheckCodexProjectPluginActivation({
2781
+ pluginId,
2782
+ userEnabled: userConfig.snapshot.plugins[pluginId]?.enabled === true,
2783
+ projectEnabled: projectConfig.snapshot.plugins[pluginId]?.enabled === true,
2784
+ exactResult,
2785
+ neutralResult,
2786
+ });
2787
+ }
2788
+ finally {
2789
+ await fs.remove(neutralCwd);
2790
+ }
2791
+ return 'present';
2576
2792
  }
2577
2793
  return 'absent';
2578
2794
  },
@@ -2645,6 +2861,21 @@ function createNativeDriver(options) {
2645
2861
  throw new Error('Missing rebuild integration plan.');
2646
2862
  const marketplaceName = target.identity.marketplaceName ?? MARKETPLACE_NAME;
2647
2863
  const expectedMarketplaceRoot = target.identity.marketplaceRoot ?? resolveAwHome(options.context.env);
2864
+ if (target.identity.platform === 'codex') {
2865
+ const before = await inspectCodexNativeResidue({
2866
+ pluginName: target.identity.pluginName,
2867
+ cwd: target.identity.cwd,
2868
+ env: options.context.env,
2869
+ marketplaceName,
2870
+ marketplaceRoot: expectedMarketplaceRoot,
2871
+ });
2872
+ if (before.state === 'foreign') {
2873
+ throw new Error(`codexSharedNativeForeign: ${before.labels.join(', ')}`);
2874
+ }
2875
+ if (before.state === 'present') {
2876
+ await runNativeCliChecked(buildCodexPluginRemoveInvocation(requireCodexManager(codexManager), `${target.identity.pluginName}@${marketplaceName}`, target.identity.cwd, options.context.env), runner);
2877
+ }
2878
+ }
2648
2879
  const writeGeneration = async () => {
2649
2880
  if (mirrorTransactions.has(target)) {
2650
2881
  throw new Error('Marketplace mirror transaction is already active for this target.');
@@ -2677,18 +2908,22 @@ function createNativeDriver(options) {
2677
2908
  const marketplaceRoot = mirrored.rootDir;
2678
2909
  const pluginId = `${target.identity.pluginName}@${marketplaceName}`;
2679
2910
  if (target.identity.platform === 'codex') {
2911
+ const nativePluginRoot = await canonicalizePathThroughExistingAncestor(path.join(marketplaceRoot, 'plugins', target.identity.pluginName));
2680
2912
  const requiredSkillWrites = plan.writes.filter((entry) => !entry.optional
2681
2913
  && entry.component?.kind === 'skill-directory'
2682
2914
  && entry.artifact.kind === 'skill'
2683
2915
  && entry.artifact.state !== 'disabled'
2684
- && target.identity.scope === 'user');
2916
+ && target.identity.scope === 'user'
2917
+ && !path.relative(nativePluginRoot, entry.component.path).startsWith('..'));
2685
2918
  for (const write of requiredSkillWrites) {
2686
2919
  await write.write();
2687
2920
  }
2688
2921
  const skillsRelativeRoot = await ensureCodexActiveSkillsManifest({
2689
2922
  plugin: target.plugin,
2690
2923
  marketplaceRoot,
2691
- hasRequiredSkills: requiredSkillWrites.length > 0,
2924
+ hasRequiredSkills: target.identity.scope === 'project'
2925
+ ? target.plugin.manifest.skills !== undefined
2926
+ : requiredSkillWrites.length > 0,
2692
2927
  });
2693
2928
  const config = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath);
2694
2929
  const marketplace = config.snapshot.marketplaces[marketplaceName];
@@ -2796,13 +3031,10 @@ export function createRebuildIntegration(options) {
2796
3031
  cwd: input.cwd,
2797
3032
  rootNames: input.platform === 'codex' ? ['.agents', '.codex'] : ['.claude'],
2798
3033
  managedPaths: input.platform === 'codex'
2799
- ? ['.codex/config.toml', '.codex/hooks.json']
3034
+ ? ['.codex/config.toml']
2800
3035
  : ['.claude/settings.json'],
2801
3036
  });
2802
3037
  input = { ...input, cwd: canonicalRepoRoot };
2803
- if (materializing && input.platform === 'codex' && input.plugin.manifest.apps) {
2804
- throw new Error(`Codex project scope 不支持 apps:${input.plugin.manifest.name}`);
2805
- }
2806
3038
  }
2807
3039
  if (materializing && input.platform === 'codex') {
2808
3040
  await preflightCodexWriteSafety({
@@ -2829,9 +3061,15 @@ export function createRebuildIntegration(options) {
2829
3061
  env: options.context.env,
2830
3062
  loadPlugin,
2831
3063
  });
3064
+ if (input.expectedLifecycleMetadataIntegrityDigest !== undefined
3065
+ && active?.metadata.metadataIntegrityDigest
3066
+ !== input.expectedLifecycleMetadataIntegrityDigest) {
3067
+ throw new Error('stateInventoryFailed: lifecycle active generation changed after verified inventory.');
3068
+ }
2832
3069
  historicalArtifacts = active?.artifacts ?? [];
2833
3070
  }
2834
3071
  let writes = [];
3072
+ let currentRemovalComponents = [];
2835
3073
  if (input.command !== 'cleanup' && input.command !== 'uninstall') {
2836
3074
  if (input.readOnlyInspectionPlan) {
2837
3075
  writes = await buildInspectionWrites({
@@ -2841,6 +3079,8 @@ export function createRebuildIntegration(options) {
2841
3079
  cwd: input.cwd,
2842
3080
  env: options.context.env,
2843
3081
  marketplaceRoot: input.marketplaceRoot,
3082
+ marketplaceName: input.marketplaceName,
3083
+ userActivationDesired: options.context.codexUserActivationDesired,
2844
3084
  });
2845
3085
  }
2846
3086
  else if (input.platform === 'codex') {
@@ -2850,6 +3090,8 @@ export function createRebuildIntegration(options) {
2850
3090
  event,
2851
3091
  historicalArtifacts,
2852
3092
  scriptTimeoutMs: options.scriptTimeoutMs,
3093
+ marketplaceName: input.marketplaceName,
3094
+ userActivationDesired: options.context.codexUserActivationDesired,
2853
3095
  });
2854
3096
  }
2855
3097
  else {
@@ -2862,6 +3104,22 @@ export function createRebuildIntegration(options) {
2862
3104
  });
2863
3105
  }
2864
3106
  }
3107
+ else if (input.platform === 'codex' && input.scope === 'project') {
3108
+ const currentRemovalWrites = await buildInspectionWrites({
3109
+ plugin: input.plugin,
3110
+ platform: input.platform,
3111
+ scope: input.scope,
3112
+ cwd: input.cwd,
3113
+ env: options.context.env,
3114
+ marketplaceRoot: input.marketplaceRoot,
3115
+ marketplaceName: input.marketplaceName,
3116
+ userActivationDesired: options.context.codexUserActivationDesired,
3117
+ });
3118
+ currentRemovalComponents = currentRemovalWrites.flatMap((write) => [
3119
+ ...(write.additionalComponents ?? []),
3120
+ ...(write.component ? [write.component] : []),
3121
+ ]);
3122
+ }
2865
3123
  const desiredComponents = writes.filter((write) => !write.optional).flatMap((write) => [
2866
3124
  ...(write.additionalComponents ?? []),
2867
3125
  ...(write.component ? [write.component] : []),
@@ -2889,8 +3147,13 @@ export function createRebuildIntegration(options) {
2889
3147
  })))).flat();
2890
3148
  const skillRoots = input.platform === 'codex'
2891
3149
  ? input.scope === 'project'
2892
- ? [path.join(await resolveRepoRoot(input.cwd), '.agents', 'skills')]
2893
- : [path.join(input.marketplaceRoot ?? resolveAwHome(options.context.env), 'plugins', input.plugin.manifest.name)]
3150
+ ? []
3151
+ : [
3152
+ path.join(getCodexHome(options.context.env), 'skills'),
3153
+ ...(input.preserveCodexSharedNativePayload
3154
+ ? []
3155
+ : [await canonicalizePathThroughExistingAncestor(path.join(input.marketplaceRoot ?? resolveAwHome(options.context.env), 'plugins', input.plugin.manifest.name))]),
3156
+ ]
2894
3157
  : [path.join(configDir, 'skills')];
2895
3158
  const scanSkills = () => Promise.all(skillRoots.map((root) => scanOwnedSkills({
2896
3159
  sourceId: input.sourceId,
@@ -2901,7 +3164,7 @@ export function createRebuildIntegration(options) {
2901
3164
  scope: input.scope,
2902
3165
  })));
2903
3166
  const scannedSkills = (await scanSkills()).flat();
2904
- const historicalComponents = await buildHistoricalComponents({
3167
+ let historicalComponents = await buildHistoricalComponents({
2905
3168
  sourceId: input.sourceId,
2906
3169
  sourceIdentity: input.sourceIdentity,
2907
3170
  pluginId: input.pluginId,
@@ -2911,7 +3174,24 @@ export function createRebuildIntegration(options) {
2911
3174
  cwd: input.cwd,
2912
3175
  env: options.context.env,
2913
3176
  artifacts: historicalArtifacts,
3177
+ command: input.command,
2914
3178
  });
3179
+ if (input.preserveCodexSharedNativePayload) {
3180
+ const nativePluginRoots = await Promise.all([
3181
+ path.join(input.marketplaceRoot ?? resolveAwHome(options.context.env), 'plugins', input.plugin.manifest.name),
3182
+ path.join(getCodexHome(options.context.env), 'plugins', 'cache', input.marketplaceName ?? MARKETPLACE_NAME, input.plugin.manifest.name),
3183
+ ].map((root) => canonicalizePathThroughExistingAncestor(root)));
3184
+ historicalComponents = historicalComponents.filter((component) => {
3185
+ if (!('path' in component))
3186
+ return true;
3187
+ return nativePluginRoots.every((nativePluginRoot) => {
3188
+ const relative = path.relative(nativePluginRoot, path.resolve(component.path));
3189
+ return relative === '..'
3190
+ || relative.startsWith(`..${path.sep}`)
3191
+ || path.isAbsolute(relative);
3192
+ });
3193
+ });
3194
+ }
2915
3195
  const sharedComponents = input.platform === 'codex'
2916
3196
  ? await scanCodexSharedComponents({
2917
3197
  sourceId: input.sourceId,
@@ -2921,7 +3201,6 @@ export function createRebuildIntegration(options) {
2921
3201
  scope: input.scope,
2922
3202
  paths: resolveCodexPaths(input.cwd, input.scope, options.context.env),
2923
3203
  scanConfig: true,
2924
- scanHooks: true,
2925
3204
  ignoreReadErrors: true,
2926
3205
  })
2927
3206
  : await scanClaudeSharedComponents({
@@ -2933,10 +3212,28 @@ export function createRebuildIntegration(options) {
2933
3212
  settingsPath: resolveClaudePaths(input.cwd, input.scope, options.context.env).settingsPath,
2934
3213
  });
2935
3214
  const nativeRequired = input.platform === 'claude'
2936
- || (input.scope === 'user' && requiresCodexNativePlugin(input.plugin, input.scope));
2937
- const nativeCleanupCandidate = (input.platform === 'claude' || input.scope === 'user')
3215
+ || analyzeCodexSurface(input.plugin.manifest).nativeKinds.length > 0;
3216
+ const nativeDesired = nativeRequired
3217
+ && input.command !== 'cleanup'
3218
+ && input.command !== 'uninstall';
3219
+ const codexNative = input.platform === 'codex' && nativeRequired
3220
+ ? await inspectCodexNativeResidue({
3221
+ pluginName: input.plugin.manifest.name,
3222
+ cwd: input.cwd,
3223
+ env: options.context.env,
3224
+ marketplaceName: input.marketplaceName,
3225
+ marketplaceRoot: input.marketplaceRoot,
3226
+ })
3227
+ : undefined;
3228
+ const codexNativeExact = codexNative?.state === 'present'
3229
+ && codexNative.versions.length > 0
3230
+ && codexNative.versions.every((version) => version === input.plugin.manifest.version);
3231
+ const materializeNativeRequired = nativeDesired && (input.platform === 'claude'
3232
+ || input.command === 'reinstall'
3233
+ || !codexNativeExact);
3234
+ const nativeCleanupCandidate = input.platform === 'claude'
2938
3235
  && input.includeNativeCleanupCandidate !== false;
2939
- const nativePlugin = (nativeRequired || nativeCleanupCandidate)
3236
+ const nativePlugin = (nativeDesired || nativeCleanupCandidate)
2940
3237
  ? [{
2941
3238
  kind: 'native-plugin',
2942
3239
  identity: ownerIdentity(input.plugin.manifest.name, 'native', input.platform, input.scope),
@@ -2946,7 +3243,7 @@ export function createRebuildIntegration(options) {
2946
3243
  const marketplaceDesired = nativeRequired
2947
3244
  && input.command !== 'cleanup'
2948
3245
  && input.command !== 'uninstall';
2949
- const marketplace = input.platform === 'codex' && input.scope === 'user'
3246
+ const marketplace = input.platform === 'codex' && input.scope === 'user' && nativeDesired
2950
3247
  ? [createCodexMarketplaceComponent({
2951
3248
  pluginName: input.plugin.manifest.name,
2952
3249
  codexManager: options.context.codexManager,
@@ -2969,20 +3266,48 @@ export function createRebuildIntegration(options) {
2969
3266
  desired: marketplaceDesired,
2970
3267
  })]
2971
3268
  : [];
2972
- if (nativeRequired
2973
- && input.command !== 'cleanup'
2974
- && input.command !== 'uninstall')
3269
+ if (nativeDesired)
2975
3270
  desiredComponents.push(...nativePlugin, ...marketplace);
3271
+ const activationCleanup = input.platform === 'codex'
3272
+ && nativeRequired
3273
+ && (input.command === 'cleanup' || input.command === 'uninstall')
3274
+ ? (input.scope === 'project'
3275
+ ? [
3276
+ createCodexActivationComponent({
3277
+ identity: ownerIdentity(input.plugin.manifest.name, internalArtifactKey('activation', 'project'), 'codex', input.scope),
3278
+ configPath: resolveCodexPaths(input.cwd, 'project', options.context.env).configTomlPath,
3279
+ codexPluginId: `${input.plugin.manifest.name}@${input.marketplaceName ?? MARKETPLACE_NAME}`,
3280
+ desired: true,
3281
+ }),
3282
+ ]
3283
+ : [createCodexActivationComponent({
3284
+ identity: ownerIdentity(input.plugin.manifest.name, internalArtifactKey('activation', 'user'), 'codex', input.scope),
3285
+ configPath: resolveCodexPaths(input.cwd, 'user', options.context.env).configTomlPath,
3286
+ codexPluginId: `${input.plugin.manifest.name}@${input.marketplaceName ?? MARKETPLACE_NAME}`,
3287
+ desired: true,
3288
+ })])
3289
+ : [];
3290
+ const explicitActivationCleanup = input.platform === 'codex'
3291
+ ? (input.codexActivationCleanup ?? []).map((activation) => createCodexActivationCleanupComponent({
3292
+ identity: ownerIdentity(input.plugin.manifest.name, internalArtifactKey('activation', activation.scope), 'codex', input.scope),
3293
+ configPath: resolveCodexPaths(input.cwd, activation.scope, options.context.env).configTomlPath,
3294
+ codexPluginId: activation.codexPluginId,
3295
+ disposition: activation.disposition,
3296
+ }))
3297
+ : [];
2976
3298
  const allComponents = dedupeComponents([
2977
3299
  ...desiredComponents,
2978
3300
  ...optionalComponents,
2979
3301
  ...(input.platform === 'codex' ? sharedComponents : []),
2980
3302
  ...scannedText,
2981
3303
  ...scannedSkills,
3304
+ ...currentRemovalComponents,
2982
3305
  ...historicalComponents,
2983
3306
  ...(input.platform === 'claude' ? sharedComponents : []),
2984
3307
  ...nativePlugin,
2985
3308
  ...marketplace,
3309
+ ...activationCleanup,
3310
+ ...explicitActivationCleanup,
2986
3311
  ]);
2987
3312
  bindNamespacedOwnerIdentity(allComponents, input);
2988
3313
  const desiredKeys = new Set(desiredComponents.map(componentIdentityKey));
@@ -3005,11 +3330,15 @@ export function createRebuildIntegration(options) {
3005
3330
  ownedComponents: allComponents,
3006
3331
  desiredOwnedComponents: allComponents.filter((component) => desiredKeys.has(componentIdentityKey(component))),
3007
3332
  optionalOwnedComponents: allComponents.filter((component) => optionalComponentKeys.has(componentIdentityKey(component))),
3008
- requiresNative: nativeRequired
3009
- && input.command !== 'cleanup'
3010
- && input.command !== 'uninstall',
3333
+ requiresNative: nativeDesired,
3334
+ writeNativeRequired: materializeNativeRequired,
3011
3335
  artifacts: writes.map((write) => write.artifact),
3012
3336
  lifecycleSource: input.plugin,
3337
+ ...(input.expectedLifecycleMetadataIntegrityDigest
3338
+ ? {
3339
+ historicalLifecycleMetadataIntegrityDigest: input.expectedLifecycleMetadataIntegrityDigest,
3340
+ }
3341
+ : {}),
3013
3342
  };
3014
3343
  plans.set(target, {
3015
3344
  catalog: input.catalog,
@@ -3022,7 +3351,13 @@ export function createRebuildIntegration(options) {
3022
3351
  };
3023
3352
  }
3024
3353
  export async function inspectRebuildTarget(options) {
3025
- const integration = createRebuildIntegration({ context: options.context });
3354
+ const integration = createRebuildIntegration({
3355
+ context: {
3356
+ ...options.context,
3357
+ codexUserActivationDesired: options.userActivationDesired
3358
+ ?? options.context.codexUserActivationDesired,
3359
+ },
3360
+ });
3026
3361
  const target = await integration.buildTarget({
3027
3362
  ...(options.sourceId ? {
3028
3363
  sourceId: options.sourceId,
@@ -3042,7 +3377,9 @@ export async function inspectRebuildTarget(options) {
3042
3377
  });
3043
3378
  const inspection = await inspectOwnedComponents({
3044
3379
  components: target.ownedComponents,
3045
- nativeProbe: integration.driver.probeNativeHealth ?? integration.driver.probeNative,
3380
+ nativeProbe: options.nativeProbe
3381
+ ?? integration.driver.probeNativeHealth
3382
+ ?? integration.driver.probeNative,
3046
3383
  verifyHealth: true,
3047
3384
  });
3048
3385
  if (!target.requiresNative)
@@ -3141,7 +3478,7 @@ export async function discoverRebuildTargetNames(options) {
3141
3478
  cwd: options.cwd,
3142
3479
  rootNames: options.platform === 'codex' ? ['.agents', '.codex'] : ['.claude'],
3143
3480
  managedPaths: options.platform === 'codex'
3144
- ? ['.codex/config.toml', '.codex/hooks.json']
3481
+ ? ['.codex/config.toml']
3145
3482
  : ['.claude/settings.json'],
3146
3483
  });
3147
3484
  options = { ...options, cwd: canonicalRepoRoot };
@@ -3155,7 +3492,7 @@ export async function discoverRebuildTargetNames(options) {
3155
3492
  names.add(name);
3156
3493
  const skillRoots = options.platform === 'codex'
3157
3494
  ? options.scope === 'project'
3158
- ? [path.join(await resolveRepoRoot(options.cwd), '.agents', 'skills')]
3495
+ ? []
3159
3496
  : [path.join(resolveAwHome(options.env), 'plugins')]
3160
3497
  : [path.join(paths.configDir, 'skills')];
3161
3498
  if (options.platform === 'codex' && options.scope === 'user') {
@@ -3190,24 +3527,14 @@ export async function discoverRebuildTargetNames(options) {
3190
3527
  for (const name of Object.keys(config.snapshot.developerInstructions.managedBlocks)) {
3191
3528
  names.add(sharedOwnerPluginName(name));
3192
3529
  }
3193
- for (const server of Object.values(config.snapshot.mcpServers)) {
3194
- const envValue = server.value?.env;
3195
- const owner = envValue
3196
- && typeof envValue === 'object'
3197
- && !Array.isArray(envValue)
3198
- && typeof envValue[MCP_OWNER_ENV] === 'string'
3199
- ? envValue[MCP_OWNER_ENV]
3200
- : null;
3201
- if (owner)
3202
- names.add(sharedOwnerPluginName(owner));
3203
- }
3204
3530
  if (options.scope === 'project') {
3205
- const hooksPath = resolveCodexPaths(options.cwd, options.scope, options.env).hooksPath;
3206
- const hooks = await readCodexHooks(hooksPath).catch(() => null);
3207
- for (const identity of hooks?.managedIdentities ?? []) {
3208
- const owner = readOwnedHookIdentity(identity, options.scope);
3209
- if (owner)
3210
- names.add(owner.plugin);
3531
+ const suffix = `@${options.marketplaceName ?? MARKETPLACE_NAME}`;
3532
+ for (const pluginId of Object.keys(config.snapshot.plugins)) {
3533
+ if (!pluginId.endsWith(suffix))
3534
+ continue;
3535
+ const name = pluginId.slice(0, -suffix.length);
3536
+ if (/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name))
3537
+ names.add(name);
3211
3538
  }
3212
3539
  }
3213
3540
  else {