@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,31 +1,62 @@
1
1
  import path from 'node:path';
2
2
  import fs from 'fs-extra';
3
- import { probeAgentVersion } from '../utils/agent-version.js';
4
- import { previewCodexManager, resolveCodexManager, } from '../utils/codex-manager.js';
3
+ import { getCodexOperationRequirement, probeAgentVersion } from '../utils/agent-version.js';
4
+ import { previewCodexManager, resolveCodexOperationConsumer, } from '../utils/codex-manager.js';
5
5
  import { createNoopDebugLogger } from '../output/debug-log.js';
6
6
  import { createNoopReporter } from '../output/reporter.js';
7
7
  import { getDefaultNativeCliRunner } from '../utils/native-cli.js';
8
8
  import { readPluginManifest } from '../utils/manifest.js';
9
- import { resolveAwHome } from '../utils/paths.js';
9
+ import { resolveAwHome, resolveCodexPaths } from '../utils/paths.js';
10
10
  import { resolveCliPackageRoot } from '../utils/paths.js';
11
11
  import { supportsPlatform } from '../utils/platform-support.js';
12
- import { hasCodexProjectManagedArtifacts } from '../utils/codex-project-surface.js';
12
+ import { analyzeCodexSurface } from '../utils/codex-surface-analysis.js';
13
+ import { createCodexReconcilePlan, } from '../utils/codex-reconciler.js';
14
+ import { readCodexConfigFile } from '../utils/codex-config.js';
13
15
  import { createRebuildActiveStore, executeRebuildOperation, } from './rebuild-operations.js';
14
- import { assertNoClaudeUnboundNativeNameCollision, codexNativeRemovalRequired, createRebuildIntegration, discoverRebuildTargetNames, inspectNativeMarketplaceRegistrations, } from './rebuild-integration.js';
15
- import { getNamespacedMarketplaceName } from '../utils/local-marketplace.js';
16
+ import { assertNoClaudeUnboundNativeNameCollision, createRebuildIntegration, discoverRebuildTargetNames, executeCodexReconcileActivations, inspectCodexSharedNativeLive, inspectNativeMarketplaceRegistrations, } from './rebuild-integration.js';
17
+ import { getNamespacedMarketplaceName, readStrictNamespacedMarketplaceRoot, } from '../utils/local-marketplace.js';
16
18
  import { formatPluginId, parsePluginId, planPluginSelectorSourceIds, preflightDesiredArtifactCollisions, withResolvedMultiSourceCatalog, } from '../utils/multi-source-catalog.js';
19
+ import { calculateCodexSharedNativePayloadDigest } from '../utils/catalog-digests.js';
17
20
  import { resolveCatalogOperationContext, } from '../utils/catalog-operation-context.js';
18
21
  import { assertStrictSemVer } from '../utils/manifest.js';
19
22
  import { assertCatalogSourceInventoryComplete, canonicalMarketplaceRegistrationNames, inventoryCatalogSourceReferences, } from '../utils/catalog-source-references.js';
20
23
  import { resolveEffectivePluginConfig } from '../utils/plugin-config.js';
21
- import { cleanupCorruptExclusivePluginDirectory, inspectOwnedComponents, } from '../utils/ownership.js';
22
- import { codexProjectConfigAliasesUserHome, getClaudeHome, getCodexHome, resolveCanonicalRepoProjectKey, resolveRepoRootSync, } from '../utils/state-paths.js';
24
+ import { inspectOwnedComponents, } from '../utils/ownership.js';
25
+ import { codexProjectConfigAliasesUserHome, resolveCanonicalRepoProjectKey, } from '../utils/state-paths.js';
23
26
  import { resolvePluginMetaDependencyClosure } from '../utils/plugin-dependencies.js';
27
+ import { readCatalogSourcesConfigSnapshot } from '../utils/catalog-sources.js';
28
+ import { readCatalogStoreEntry } from '../utils/catalog-generation-store.js';
29
+ import { catalogGenerationErrorCode, isOfflineCleanupCatalogGenerationError, } from '../utils/catalog-generation-error.js';
30
+ import { canonicalizePluginIntentState, convergePluginIntentManagedCodexOwnership, createEmptyPluginIntentState, mergePluginIntentRootPlatforms, readPluginIntentState, removePluginIntentRootPlatforms, widenPluginIntentManagedCodexOwnership, writePluginIntentState, } from '../utils/plugin-intent-state.js';
24
31
  import { validateCatalogSnapshotPluginEntriesComplete } from '../utils/catalog-snapshot.js';
25
32
  import { AwCapabilityUnsupportedError, IMPLEMENTED_AW_CAPABILITIES, } from './aw-capabilities.js';
26
33
  import { checkAwCapabilitySupport } from '../utils/manifest.js';
27
- import { createDependencyOperationPlan, } from './dependency-operation-plan.js';
34
+ import { createDependencyOperationPlan, createDependencyPlacementPlan, } from './dependency-operation-plan.js';
28
35
  import { offlineCleanupEvidenceFromInventory, offlineCleanupEvidenceMatches, } from './offline-cleanup-evidence.js';
36
+ async function measureCommandPerformanceStage(runtime, name, operation) {
37
+ const startedAt = Date.now();
38
+ let phase = 'success';
39
+ try {
40
+ return await operation();
41
+ }
42
+ catch (error) {
43
+ phase = 'failure';
44
+ throw error;
45
+ }
46
+ finally {
47
+ await Promise.resolve().then(() => runtime?.debugLogger?.log({
48
+ type: 'performance:stage',
49
+ level: 'debug',
50
+ target: 'debugLog',
51
+ message: 'Command performance stage completed.',
52
+ data: {
53
+ name,
54
+ phase,
55
+ durationMs: Math.max(0, Date.now() - startedAt),
56
+ },
57
+ })).catch(() => undefined);
58
+ }
59
+ }
29
60
  let rebuildCommandTestHooks;
30
61
  async function emitRebuildActionEvent(runtime, event) {
31
62
  await Promise.allSettled([
@@ -39,6 +70,81 @@ export function setRebuildCommandTestHooksForTests(hooks) {
39
70
  function selectedPlatforms(selection) {
40
71
  return selection === 'both' ? ['codex', 'claude'] : [selection];
41
72
  }
73
+ function updateIntentOverrides(options) {
74
+ const plannedKeys = new Set(options.plan.targets.map((target) => `${target.pluginId}\0${target.platform}`));
75
+ return canonicalizePluginIntentState({
76
+ ...options.state,
77
+ dependencyScopeOverrides: [
78
+ ...options.state.dependencyScopeOverrides.filter((override) => !plannedKeys.has(`${override.pluginId}\0${override.platform}`)),
79
+ ...options.plan.scopeOverrides,
80
+ ],
81
+ });
82
+ }
83
+ function effectiveDependencyScopeOverrides(options) {
84
+ const nodeKeys = new Set(options.nodes.map((node) => `${node.pluginId}\0${node.platform}`));
85
+ const explicitMatches = (pluginId, platform) => options.explicit.some((override) => override.pluginId === pluginId
86
+ && (override.platform === undefined || override.platform === platform));
87
+ return [
88
+ ...options.state.dependencyScopeOverrides.filter((override) => nodeKeys.has(`${override.pluginId}\0${override.platform}`)
89
+ && !options.ignoredPersistedRootIds?.has(override.pluginId)
90
+ && !explicitMatches(override.pluginId, override.platform)),
91
+ ...options.explicit.filter((override) => options.nodes.some((node) => node.pluginId === override.pluginId
92
+ && (override.platform === undefined || node.platform === override.platform))),
93
+ ];
94
+ }
95
+ function deriveDesiredIntentState(options) {
96
+ if (options.reconcileIntentOnly)
97
+ return options.state;
98
+ let next = options.state;
99
+ if (options.command === 'install') {
100
+ for (const root of options.selectedRoots)
101
+ next = mergePluginIntentRootPlatforms(next, root);
102
+ next = updateIntentOverrides({ state: next, plan: options.plan });
103
+ }
104
+ else if (options.command === 'uninstall') {
105
+ for (const root of options.selectedRoots)
106
+ next = removePluginIntentRootPlatforms(next, root);
107
+ }
108
+ return next;
109
+ }
110
+ async function writeDesiredIntentSuperset(options) {
111
+ const location = { scope: options.scope, cwd: options.cwd, env: options.env };
112
+ let next = deriveDesiredIntentState({
113
+ command: options.command,
114
+ state: await readPluginIntentState(location),
115
+ selectedRoots: options.selectedRoots,
116
+ plan: options.plan,
117
+ reconcileIntentOnly: options.reconcileIntentOnly,
118
+ });
119
+ const convergedCodexPluginIds = options.codexPlan?.managedInventory.converged
120
+ ?? next.managed.codexPluginIds;
121
+ const superset = options.codexPlan
122
+ ? widenPluginIntentManagedCodexOwnership(next, options.codexPlan.managedInventory.ownershipSuperset)
123
+ : next;
124
+ await writePluginIntentState(location, superset);
125
+ const writes = [{ location, superset, convergedCodexPluginIds }];
126
+ if (options.command === 'install' || options.codexPlan) {
127
+ const otherScope = options.scope === 'user' ? 'project' : 'user';
128
+ const otherLocation = { scope: otherScope, cwd: options.cwd, env: options.env };
129
+ let other = await readPluginIntentState(otherLocation);
130
+ if (options.command === 'install' && !options.reconcileIntentOnly) {
131
+ for (const root of options.selectedRoots)
132
+ other = removePluginIntentRootPlatforms(other, root);
133
+ }
134
+ const otherConvergedCodexPluginIds = options.codexPlan?.managedInventory.related.converged
135
+ ?? other.managed.codexPluginIds;
136
+ const otherSuperset = options.codexPlan
137
+ ? widenPluginIntentManagedCodexOwnership(other, options.codexPlan.managedInventory.related.ownershipSuperset)
138
+ : other;
139
+ await writePluginIntentState(otherLocation, otherSuperset);
140
+ writes.push({
141
+ location: otherLocation,
142
+ superset: otherSuperset,
143
+ convergedCodexPluginIds: otherConvergedCodexPluginIds,
144
+ });
145
+ }
146
+ return writes;
147
+ }
42
148
  function createSyntheticPlugin(pluginName, env) {
43
149
  const rootDir = path.join(resolveAwHome(env), 'plugins', pluginName);
44
150
  return {
@@ -243,24 +349,6 @@ async function runSingleSourceRebuildCommand(options) {
243
349
  results.push(failedResult(options.command, identity, 'codexScopePathAlias: Codex project config 与用户配置目录相同。请进入实际项目目录后重试。'));
244
350
  continue;
245
351
  }
246
- if (currentPlugin
247
- && platform === 'codex'
248
- && options.commandOptions.scope === 'project'
249
- && options.command !== 'cleanup'
250
- && options.command !== 'uninstall'
251
- && currentPlugin.manifest.apps) {
252
- results.push(failedResult(options.command, identity, `${pluginName} 声明了 apps;Codex project scope 不支持 apps。`));
253
- continue;
254
- }
255
- if (currentPlugin
256
- && platform === 'codex'
257
- && options.commandOptions.scope === 'project'
258
- && options.command !== 'cleanup'
259
- && options.command !== 'uninstall'
260
- && !hasCodexProjectManagedArtifacts(currentPlugin)) {
261
- results.push(failedResult(options.command, identity, `${pluginName} 没有 Codex project scope 可生成的 Skills、instructions、roles、MCP、hooks 或 permissions。`));
262
- continue;
263
- }
264
352
  let result;
265
353
  try {
266
354
  if (options.runtime?.preparedTargetError)
@@ -310,17 +398,6 @@ async function runSingleSourceRebuildCommand(options) {
310
398
  }
311
399
  }
312
400
  const successful = results.filter((result) => result.status === 'succeeded');
313
- const artifacts = targets.flatMap((target) => [...target.artifacts]);
314
- const nativePlugins = {};
315
- for (const result of successful) {
316
- const target = targets.find((entry) => entry.identity.pluginName === result.target.pluginName
317
- && entry.identity.platform === result.target.platform
318
- && entry.identity.scope === result.target.scope);
319
- if (target?.requiresNative && options.command !== 'cleanup' && options.command !== 'uninstall') {
320
- nativePlugins[result.target.platform] ??= [];
321
- nativePlugins[result.target.platform].push(result.target.pluginName);
322
- }
323
- }
324
401
  return {
325
402
  platforms,
326
403
  plugins: [...new Set(successful.map((result) => result.target.pluginName))],
@@ -328,8 +405,6 @@ async function runSingleSourceRebuildCommand(options) {
328
405
  rebuildResults: results,
329
406
  targetResults: results.map(toTargetResult),
330
407
  overallState: results.every((result) => result.status === 'succeeded') ? 'succeeded' : 'failed',
331
- nativePlugins,
332
- repoManagedArtifacts: artifacts,
333
408
  receiptTargets: results.map((result) => ({
334
409
  pluginName: result.target.pluginName,
335
410
  ...(result.target.sourceId ? {
@@ -356,14 +431,6 @@ function toCatalogResolution(source) {
356
431
  function mergeRebuildCommandResults(results) {
357
432
  const rebuildResults = results.flatMap((result) => result.rebuildResults);
358
433
  const platforms = [...new Set(results.flatMap((result) => result.platforms))];
359
- const nativePlugins = {};
360
- for (const result of results) {
361
- for (const platform of ['codex', 'claude']) {
362
- const names = result.nativePlugins[platform];
363
- if (names)
364
- nativePlugins[platform] = [...new Set([...(nativePlugins[platform] ?? []), ...names])];
365
- }
366
- }
367
434
  return {
368
435
  platforms,
369
436
  plugins: [...new Set(results.flatMap((result) => result.plugins))],
@@ -373,8 +440,6 @@ function mergeRebuildCommandResults(results) {
373
440
  overallState: rebuildResults.every((result) => result.status === 'succeeded')
374
441
  ? 'succeeded'
375
442
  : 'failed',
376
- nativePlugins,
377
- repoManagedArtifacts: [],
378
443
  receiptTargets: results.flatMap((result) => result.receiptTargets),
379
444
  ...(results.find((result) => result.dependencyPlan)?.dependencyPlan
380
445
  ? { dependencyPlan: results.find((result) => result.dependencyPlan).dependencyPlan }
@@ -382,13 +447,15 @@ function mergeRebuildCommandResults(results) {
382
447
  ...(results.find((result) => result.codexManagerPreview)?.codexManagerPreview
383
448
  ? { codexManagerPreview: results.find((result) => result.codexManagerPreview).codexManagerPreview }
384
449
  : {}),
450
+ ...(results.some((result) => result.offlineCleanupPlans !== undefined)
451
+ ? { offlineCleanupPlans: results.flatMap((result) => result.offlineCleanupPlans ?? []) }
452
+ : {}),
385
453
  };
386
454
  }
387
455
  function leaseSafeRebuildResult(result) {
388
456
  const projectPath = (value) => path.basename(value);
389
457
  return {
390
458
  ...result,
391
- repoManagedArtifacts: [],
392
459
  rebuildResults: result.rebuildResults.map((entry) => ({
393
460
  ...entry,
394
461
  target: { ...entry.target, cwd: '.' },
@@ -419,12 +486,18 @@ function projectLegacyTargets(references) {
419
486
  return [`${target.pluginName}\0${target.platform}\0${target.scope}`, target];
420
487
  })).values()];
421
488
  }
422
- function exclusiveSkillRoot(options) {
423
- if (options.scope === 'user') {
424
- return path.join(options.platform === 'codex' ? getCodexHome(options.env) : getClaudeHome(options.env), 'skills');
425
- }
426
- const repoRoot = resolveRepoRootSync(options.cwd);
427
- return path.join(repoRoot, options.platform === 'codex' ? '.agents' : '.claude', 'skills');
489
+ function desiredCleanupNoopResult(targets, cwd) {
490
+ const rebuildResults = targets.map((target) => ({
491
+ status: 'succeeded', command: 'cleanup', target: { ...target, pluginVersion: 'unavailable', cwd },
492
+ actions: [], cleanupAttempted: false, removed: [], absent: [], residual: [], quarantined: [], failures: [],
493
+ }));
494
+ return {
495
+ platforms: [...new Set(targets.map((target) => target.platform))],
496
+ plugins: [...new Set(targets.map((target) => target.pluginName))],
497
+ warnings: [], rebuildResults,
498
+ targetResults: rebuildResults.map((result) => ({ target: result.target, action: 'keep', state: 'completed' })),
499
+ overallState: 'succeeded', receiptTargets: targets,
500
+ };
428
501
  }
429
502
  function resolveLiveCleanupTargets(options) {
430
503
  const visible = options.references.filter((reference) => (reference.kind === 'namespacedOwner'
@@ -442,6 +515,12 @@ function resolveLiveCleanupTargets(options) {
442
515
  const matchesByTarget = new Map();
443
516
  for (const reference of visible.filter((entry) => entry.pluginName === parsed.pluginName
444
517
  && (parsed.sourceId === undefined || entry.sourceId === parsed.sourceId))) {
518
+ const lifecycleReference = visible.find((entry) => entry.kind === 'namespacedLifecycle'
519
+ && entry.sourceId === reference.sourceId
520
+ && entry.sourceIdentity === reference.sourceIdentity
521
+ && entry.pluginName === reference.pluginName
522
+ && entry.platform === reference.platform
523
+ && entry.scope === reference.scope);
445
524
  const liveNames = new Set(options.references.flatMap((candidate) => candidate.sourceId === reference.sourceId
446
525
  && candidate.sourceIdentity === reference.sourceIdentity
447
526
  && candidate.pluginName === reference.pluginName
@@ -480,6 +559,14 @@ function resolveLiveCleanupTargets(options) {
480
559
  ? { nativeRegistration: true }
481
560
  : {}),
482
561
  ...(marketplaceIdentityUnresolved ? { marketplaceIdentityUnresolved: true } : {}),
562
+ ...(lifecycleReference?.lifecycleMetadataIntegrityDigest
563
+ ? {
564
+ lifecycleMetadataIntegrityDigest: lifecycleReference.lifecycleMetadataIntegrityDigest,
565
+ }
566
+ : {}),
567
+ ...(lifecycleReference?.pluginVersion
568
+ ? { lifecyclePluginVersion: lifecycleReference.pluginVersion }
569
+ : {}),
483
570
  };
484
571
  const key = [target.pluginId, target.sourceIdentity, target.platform, target.scope].join('\0');
485
572
  const previous = matchesByTarget.get(key);
@@ -500,51 +587,6 @@ function resolveLiveCleanupTargets(options) {
500
587
  ].join('\0'), target])).values()],
501
588
  };
502
589
  }
503
- async function discoverDamagedExclusiveCleanupTargets(options) {
504
- const parsed = parsePluginId(options.selector);
505
- if (!parsed.sourceId)
506
- return [];
507
- const targets = [];
508
- for (const platform of options.platforms) {
509
- const root = exclusiveSkillRoot({
510
- platform,
511
- scope: options.scope,
512
- cwd: options.cwd,
513
- env: options.env,
514
- });
515
- const entries = await fs.readdir(root, { withFileTypes: true }).catch(() => []);
516
- for (const entry of entries) {
517
- if (!entry.isDirectory() || entry.isSymbolicLink())
518
- continue;
519
- const markerPath = path.join(root, entry.name, '.agent-workflows-owner.json');
520
- const markerStats = await fs.lstat(markerPath).catch(() => null);
521
- if (!markerStats?.isFile() || markerStats.isSymbolicLink())
522
- continue;
523
- const raw = await fs.readJson(markerPath).catch(() => null);
524
- if (!raw
525
- || raw.schemaVersion !== 2
526
- || raw.owner !== 'agent-workflows'
527
- || raw.sourceId !== parsed.sourceId
528
- || raw.plugin !== parsed.pluginName
529
- || raw.pluginId !== options.selector
530
- || raw.platform !== platform
531
- || raw.scope !== options.scope)
532
- continue;
533
- targets.push({
534
- pluginId: options.selector,
535
- pluginName: parsed.pluginName,
536
- sourceId: parsed.sourceId,
537
- sourceIdentity: typeof raw.sourceIdentity === 'string'
538
- && /^sha256:[0-9a-f]{64}$/u.test(raw.sourceIdentity)
539
- ? raw.sourceIdentity
540
- : `sha256:${'0'.repeat(64)}`,
541
- platform,
542
- scope: raw.scope,
543
- });
544
- }
545
- }
546
- return [...new Map(targets.map((target) => [[target.platform, target.scope].join('\0'), target])).values()];
547
- }
548
590
  async function cleanupOfflineOwnedComponents(options) {
549
591
  if (options.targets.some((target) => target.marketplaceIdentityUnresolved)) {
550
592
  throw namespacedPreflightError('marketplaceIdentityUnavailable: Cannot uniquely recover the native marketplace name from live inventory or a canonical native registration path.', options.targets);
@@ -552,35 +594,7 @@ async function cleanupOfflineOwnedComponents(options) {
552
594
  const results = [];
553
595
  const warnings = [];
554
596
  const nativeCliRunner = options.nativeCliRunner ?? getDefaultNativeCliRunner();
555
- let codexManager = options.codexManager;
556
- const codexManagerNeedFailures = new Map();
557
- if (!codexManager) {
558
- const codexManagerRequired = (await Promise.all(options.targets.map(async (target) => {
559
- if (target.platform !== 'codex' || target.scope !== 'user')
560
- return false;
561
- try {
562
- return await codexNativeRemovalRequired({
563
- pluginName: target.pluginName,
564
- cwd: options.cwd,
565
- env: options.env,
566
- marketplaceName: target.marketplaceName,
567
- marketplaceRoot: path.join(resolveAwHome(options.env), 'marketplaces', target.sourceId),
568
- });
569
- }
570
- catch (error) {
571
- codexManagerNeedFailures.set(target, new Error('codexNativeResidueProbeFailed: Codex native residue was retained because its config or cache evidence could not be read. Repair the damaged Codex weak state, then retry cleanup; you can also upgrade AW or install a compatible official Codex CLI.', { cause: error }));
572
- return false;
573
- }
574
- }))).some(Boolean);
575
- if (codexManagerRequired) {
576
- codexManager = await (options.codexManagerResolver ?? resolveCodexManager)({
577
- cwd: options.cwd,
578
- env: options.env,
579
- runner: nativeCliRunner,
580
- authorizer: options.managedCodexAuthorizer,
581
- });
582
- }
583
- }
597
+ const codexManager = options.codexManager;
584
598
  const context = {
585
599
  env: options.env,
586
600
  nativeCliRunner,
@@ -594,59 +608,39 @@ async function cleanupOfflineOwnedComponents(options) {
594
608
  loadPlugin: (bundleRoot) => readPluginManifest(bundleRoot, { mode: 'bundle' }),
595
609
  });
596
610
  for (const target of options.targets) {
597
- const exclusiveRecovery = {
598
- removed: [], absent: [], residual: [], quarantined: [], failures: [],
599
- };
600
- if (options.force) {
601
- const root = exclusiveSkillRoot({
602
- platform: target.platform,
603
- scope: target.scope,
611
+ const inventoryErrors = (options.inventoryErrors ?? []).filter((error) => error.relevance.global || ((error.relevance.sourceId === undefined || error.relevance.sourceId === target.sourceId)
612
+ && (error.relevance.pluginName === undefined || error.relevance.pluginName === target.pluginName)
613
+ && (error.relevance.platform === undefined || error.relevance.platform === target.platform)
614
+ && (error.relevance.scope === undefined || error.relevance.scope === target.scope)));
615
+ const evidence = options.evidence?.find((candidate) => candidate.pluginId === target.pluginId
616
+ && candidate.sourceIdentity === target.sourceIdentity
617
+ && candidate.platform === target.platform
618
+ && candidate.scope === target.scope);
619
+ const activationAction = target.platform === 'codex'
620
+ ? evidence?.plan.actions.find((action) => action.action === 'removeNativeRegistration'
621
+ && action.activationScope === target.scope)
622
+ : undefined;
623
+ let activationDisposition = 'remove';
624
+ if (activationAction && target.scope === 'user') {
625
+ const projectIntent = await readPluginIntentState({
626
+ scope: 'project',
604
627
  cwd: options.cwd,
605
628
  env: options.env,
606
629
  });
607
- const entries = await fs.readdir(root, { withFileTypes: true }).catch(() => []);
608
- for (const entry of entries) {
609
- if (!entry.isDirectory() || entry.isSymbolicLink())
610
- continue;
611
- const directory = path.join(root, entry.name);
612
- let recovered;
613
- try {
614
- recovered = await cleanupCorruptExclusivePluginDirectory({
615
- directory,
616
- allowedRoot: root,
617
- sourceId: target.sourceId,
618
- pluginName: target.pluginName,
619
- platform: target.platform,
620
- scope: target.scope,
621
- ...options.corruptExclusiveDirectoryOperations,
622
- });
623
- }
624
- catch (error) {
625
- recovered = {
626
- removed: [], absent: [], residual: [directory], quarantined: [],
627
- failures: [{
628
- label: directory,
629
- phase: 'mutation',
630
- message: error instanceof Error ? error.message : String(error),
631
- }],
632
- };
633
- }
634
- if (recovered) {
635
- exclusiveRecovery.removed.push(...recovered.removed);
636
- exclusiveRecovery.absent.push(...recovered.absent);
637
- exclusiveRecovery.residual.push(...recovered.residual);
638
- exclusiveRecovery.quarantined.push(...recovered.quarantined);
639
- exclusiveRecovery.failures.push(...recovered.failures);
640
- }
641
- }
630
+ const remainsDesired = projectIntent.roots.some((root) => root.pluginId === target.pluginId && root.platforms.includes('codex'))
631
+ || projectIntent.managed.codexPluginIds.includes(activationAction.target);
632
+ if (remainsDesired)
633
+ activationDisposition = 'disable';
642
634
  }
643
635
  const plugin = createSyntheticPlugin(target.pluginName, options.env);
636
+ if (target.lifecyclePluginVersion) {
637
+ plugin.manifest.version = target.lifecyclePluginVersion;
638
+ }
644
639
  plugin.sourceId = target.sourceId;
645
640
  plugin.sourceIdentity = target.sourceIdentity;
646
641
  plugin.pluginId = target.pluginId;
647
642
  const marketplaceName = target.marketplaceName ?? 'aw';
648
643
  plugin.marketplaceName = marketplaceName;
649
- const codexManagerNeedFailure = codexManagerNeedFailures.get(target);
650
644
  const desired = await integration.buildTarget({
651
645
  sourceId: target.sourceId,
652
646
  sourceIdentity: target.sourceIdentity,
@@ -664,68 +658,45 @@ async function cleanupOfflineOwnedComponents(options) {
664
658
  catalogVersion: '0.0.0',
665
659
  channel: 'beta',
666
660
  },
667
- includeHistoricalLifecycleArtifacts: false,
668
- includeNativeCleanupCandidate: target.nativeRegistration === true && !codexManagerNeedFailure,
661
+ includeHistoricalLifecycleArtifacts: target.lifecycleMetadataIntegrityDigest !== undefined,
662
+ ...(target.lifecycleMetadataIntegrityDigest
663
+ ? {
664
+ expectedLifecycleMetadataIntegrityDigest: target.lifecycleMetadataIntegrityDigest,
665
+ }
666
+ : {}),
667
+ includeNativeCleanupCandidate: target.nativeRegistration === true,
668
+ ...(activationAction
669
+ ? {
670
+ codexActivationCleanup: [{
671
+ codexPluginId: activationAction.target,
672
+ scope: activationAction.activationScope,
673
+ disposition: activationDisposition,
674
+ }],
675
+ preserveCodexSharedNativePayload: true,
676
+ }
677
+ : {}),
669
678
  });
670
- const driver = codexManagerNeedFailure && integration.driver.probeNative
671
- ? {
672
- ...integration.driver,
673
- probeNative: async () => {
674
- throw codexManagerNeedFailure;
675
- },
676
- }
677
- : integration.driver;
678
- let result = await executeRebuildOperation({
679
+ const preserveDamagedLifecycle = inventoryErrors.some((error) => error.kind.startsWith('lifecycle'));
680
+ const result = await executeRebuildOperation({
679
681
  command: 'cleanup',
680
682
  target: desired,
681
- driver,
682
- activeStore,
683
+ driver: integration.driver,
684
+ activeStore: preserveDamagedLifecycle
685
+ ? { ...activeStore, remove: async () => undefined }
686
+ : activeStore,
683
687
  });
684
- if (codexManagerNeedFailure) {
685
- const residual = `${target.pluginName}@${marketplaceName}`;
686
- result = {
687
- ...result,
688
- status: 'failed',
689
- actions: [...result.actions, { name: 'cleanup.owned', status: 'failed' }],
690
- residual: [...new Set([...result.residual, residual])],
691
- failures: [...result.failures, {
692
- phase: 'cleanup.owned',
693
- label: residual,
694
- message: codexManagerNeedFailure.message,
695
- }],
696
- error: { phase: 'cleanup.owned', message: codexManagerNeedFailure.message },
697
- };
688
+ if (result.status === 'succeeded' && evidence) {
689
+ result.removed = [...new Set([
690
+ ...result.removed,
691
+ ...evidence.plan.actions.flatMap((action) => action.action === 'removeLifecycleSnapshot' ? [action.target] : []),
692
+ ])];
698
693
  }
699
- const exclusiveResidual = (await Promise.all(exclusiveRecovery.residual.map(async (entry) => await fs.pathExists(entry) ? entry : null))).filter((entry) => entry !== null);
700
- const exclusiveQuarantined = (await Promise.all(exclusiveRecovery.quarantined.map(async (entry) => await fs.pathExists(entry) ? entry : null))).filter((entry) => entry !== null);
701
- const exclusiveFailures = (await Promise.all(exclusiveRecovery.failures.map(async (failure) => {
702
- const residualPath = failure.quarantinePath ?? failure.label;
703
- return await fs.pathExists(residualPath) ? failure : null;
704
- }))).filter((failure) => failure !== null);
705
- const removed = [...new Set([...result.removed, ...exclusiveRecovery.removed])];
706
- const absent = [...new Set([...result.absent, ...exclusiveRecovery.absent])]
707
- .filter((entry) => !removed.includes(entry));
708
- result = {
709
- ...result,
710
- removed,
711
- absent,
712
- residual: [...new Set([...result.residual, ...exclusiveResidual])],
713
- quarantined: [...new Set([...result.quarantined, ...exclusiveQuarantined])],
714
- };
715
- if (exclusiveFailures.length > 0) {
716
- const failures = exclusiveFailures.map((failure) => ({
717
- phase: 'cleanup.owned',
718
- label: failure.label,
719
- message: `exclusiveRecoveryFailed: ${failure.phase} ${failure.label}: ${failure.message}`,
720
- }));
721
- const message = failures[0].message;
722
- result = {
723
- ...result,
724
- status: 'failed',
725
- actions: [{ name: 'cleanup.owned', status: 'failed' }],
726
- failures: [...result.failures, ...failures],
727
- error: { phase: 'cleanup.owned', message },
728
- };
694
+ if (inventoryErrors.length > 0) {
695
+ result.residual = [...new Set([
696
+ ...result.residual,
697
+ ...inventoryErrors.map((error) => error.location),
698
+ ])];
699
+ warnings.push(...inventoryErrors.map((error) => `stateInventoryResidual: ${error.kind}: ${error.message}`));
729
700
  }
730
701
  results.push(result);
731
702
  }
@@ -739,11 +710,10 @@ async function cleanupOfflineOwnedComponents(options) {
739
710
  return leaseSafeRebuildResult({
740
711
  platforms: [...new Set(results.map((result) => result.target.platform))],
741
712
  plugins: results.filter((result) => result.status === 'succeeded').map((result) => result.target.pluginName),
742
- warnings,
713
+ warnings: [...new Set(warnings)],
743
714
  rebuildResults: results,
744
715
  targetResults: results.map(toTargetResult),
745
716
  overallState: results.every((result) => result.status === 'succeeded') ? 'succeeded' : 'failed',
746
- nativePlugins: {}, repoManagedArtifacts: [],
747
717
  receiptTargets: results.map((result) => ({
748
718
  pluginName: result.target.pluginName,
749
719
  pluginId: result.target.pluginId,
@@ -751,6 +721,7 @@ async function cleanupOfflineOwnedComponents(options) {
751
721
  platform: result.target.platform,
752
722
  scope: result.target.scope,
753
723
  })),
724
+ ...(options.evidence ? { offlineCleanupPlans: options.evidence } : {}),
754
725
  });
755
726
  }
756
727
  async function previewOfflineOwnedComponents(options) {
@@ -759,55 +730,139 @@ async function previewOfflineOwnedComponents(options) {
759
730
  }
760
731
  await options.onPreviewStage?.('inventory');
761
732
  const preview = mergeRebuildCommandResults([]);
762
- const codexManagerRequired = (await Promise.all(options.targets.flatMap((target) => target.platform === 'codex' && target.scope === 'user'
763
- ? [codexNativeRemovalRequired({
764
- pluginName: target.pluginName,
765
- cwd: options.cwd,
766
- env: options.env,
767
- marketplaceName: target.marketplaceName,
768
- marketplaceRoot: path.join(resolveAwHome(options.env), 'marketplaces', target.sourceId),
769
- })]
770
- : []))).some(Boolean);
733
+ if (options.evidence)
734
+ preview.offlineCleanupPlans = options.evidence;
771
735
  await options.onPreviewStage?.('dependencyPlan');
772
- if (codexManagerRequired) {
773
- preview.codexManagerPreview = await (options.codexManagerPreviewer ?? previewCodexManager)({
774
- cwd: options.cwd,
775
- env: options.env,
776
- runner: options.nativeCliRunner,
777
- });
778
- }
779
736
  return preview;
780
737
  }
781
738
  async function assertOfflineCleanupDependents(options) {
782
739
  const inventory = await options.readInventory({
783
- cwd: options.cwd,
784
- env: options.env,
785
- includeHistoricalProjects: true,
740
+ cwd: options.cwd, env: options.env, includeHistoricalProjects: true,
786
741
  });
787
742
  const currentProjectKey = await resolveCanonicalRepoProjectKey(options.cwd);
743
+ const cleanupIds = new Set(options.targets.map((target) => target.pluginId));
788
744
  for (const target of options.targets) {
789
- const pluginsRoot = path.join(resolveAwHome(options.env), 'marketplaces', target.sourceId, 'plugins');
790
- const entries = await fs.readdir(pluginsRoot, { withFileTypes: true }).catch(() => []);
791
- for (const entry of entries) {
792
- if (!entry.isDirectory() || entry.name === target.pluginName)
793
- continue;
794
- const consumer = await readPluginManifest(path.join(pluginsRoot, entry.name), { mode: 'bundle' })
795
- .catch(() => null);
745
+ const consumerNames = [...new Set(inventory.references.flatMap((reference) => (reference.kind === 'namespacedOwner'
746
+ || reference.kind === 'namespacedMarketplace'
747
+ || reference.kind === 'namespacedLifecycle')
748
+ && reference.sourceId === target.sourceId
749
+ && reference.sourceIdentity === target.sourceIdentity
750
+ && reference.pluginName
751
+ && reference.pluginName !== target.pluginName
752
+ && reference.platform === target.platform
753
+ && (target.scope === 'user' || reference.scope === 'project')
754
+ ? [reference.pluginName]
755
+ : []))];
756
+ const consumerPlugins = (await Promise.all(consumerNames.map((pluginName) => readOfflinePluginManifestCandidates({
757
+ target,
758
+ sourceIdentity: target.sourceIdentity,
759
+ pluginName,
760
+ env: options.env,
761
+ inventory,
762
+ })))).flat();
763
+ for (const consumer of consumerPlugins) {
796
764
  if (!consumer?.manifest.dependencies?.includes(target.pluginName))
797
765
  continue;
798
766
  const consumerId = formatPluginId({ sourceId: target.sourceId, pluginName: consumer.manifest.name });
799
- const live = inventory.references.some((reference) => (reference.kind === 'namespacedOwner' || reference.kind === 'namespacedMarketplace')
767
+ if (cleanupIds.has(consumerId))
768
+ continue;
769
+ const live = inventory.references.some((reference) => (reference.kind === 'namespacedOwner'
770
+ || reference.kind === 'namespacedMarketplace'
771
+ || reference.kind === 'namespacedLifecycle')
800
772
  && reference.sourceId === target.sourceId
801
773
  && reference.sourceIdentity === target.sourceIdentity
802
774
  && reference.pluginName === consumer.manifest.name
803
775
  && reference.platform === target.platform
804
776
  && (target.scope === 'user' || reference.scope === 'project')
805
777
  && (target.scope === 'user' || reference.projectKey === currentProjectKey));
806
- if (live) {
778
+ if (live)
807
779
  throw namespacedPreflightError(`dependencyStillRequired: ${target.pluginId} is required by ${consumerId}.`, options.targets);
780
+ }
781
+ }
782
+ }
783
+ async function readPluginManifestIfPresent(root) {
784
+ return readPluginManifest(root, { mode: 'bundle' }).catch(() => null);
785
+ }
786
+ async function readOfflinePluginManifestCandidates(options) {
787
+ const lifecycleRoots = options.inventory.references.flatMap((reference) => reference.kind === 'namespacedLifecycle'
788
+ && reference.sourceId === options.target.sourceId
789
+ && reference.sourceIdentity === options.sourceIdentity
790
+ && reference.pluginName === options.pluginName
791
+ ? [path.join(reference.location, 'bundle')]
792
+ : []);
793
+ const marketplaceRoot = path.join(resolveAwHome(options.env), 'marketplaces', options.target.sourceId);
794
+ const marketplace = await readStrictNamespacedMarketplaceRoot({
795
+ rootDir: marketplaceRoot,
796
+ sourceId: options.target.sourceId,
797
+ }).catch(() => null);
798
+ const marketplaceRoots = marketplace?.config.sourceIdentity === options.sourceIdentity
799
+ ? [marketplaceRoot]
800
+ : [];
801
+ const roots = [
802
+ ...lifecycleRoots,
803
+ ...marketplaceRoots.map((root) => path.join(root, 'plugins', options.pluginName)),
804
+ ];
805
+ const storeRoot = path.join(resolveAwHome(options.env), 'catalog-store');
806
+ const logicalEntries = await fs.readdir(storeRoot, { withFileTypes: true }).catch(() => []);
807
+ for (const logicalEntry of logicalEntries) {
808
+ if (!logicalEntry.isDirectory() || logicalEntry.isSymbolicLink())
809
+ continue;
810
+ const logicalRoot = path.join(storeRoot, logicalEntry.name);
811
+ const snapshotEntries = await fs.readdir(logicalRoot, { withFileTypes: true }).catch(() => []);
812
+ for (const snapshotEntry of snapshotEntries) {
813
+ if (!snapshotEntry.isDirectory()
814
+ || snapshotEntry.isSymbolicLink()
815
+ || !/^[0-9a-f]{64}$/u.test(snapshotEntry.name))
816
+ continue;
817
+ let logicalIdentity;
818
+ try {
819
+ logicalIdentity = decodeURIComponent(logicalEntry.name);
808
820
  }
821
+ catch {
822
+ continue;
823
+ }
824
+ const entry = await readCatalogStoreEntry({
825
+ logicalIdentity,
826
+ snapshotDigest: `sha256:${snapshotEntry.name}`,
827
+ env: options.env,
828
+ }).catch(() => null);
829
+ if (!entry)
830
+ continue;
831
+ const storeAnchor = await readPluginManifestIfPresent(path.join(entry.snapshotRoot, 'plugins', options.target.pluginName));
832
+ if (!storeAnchor || storeAnchor.manifest.name !== options.target.pluginName)
833
+ continue;
834
+ roots.push(path.join(entry.snapshotRoot, 'plugins', options.pluginName));
809
835
  }
810
836
  }
837
+ const plugins = await Promise.all([...new Set(roots)].map(readPluginManifestIfPresent));
838
+ return plugins.filter((plugin) => plugin !== null);
839
+ }
840
+ async function cleanupTargetIsDesired(options) {
841
+ const pending = options.roots.filter((root) => root.platforms.includes(options.target.platform));
842
+ const visited = new Set();
843
+ while (pending.length > 0) {
844
+ const root = pending.pop();
845
+ if (visited.has(root.pluginId))
846
+ continue;
847
+ visited.add(root.pluginId);
848
+ if (root.pluginId === options.target.pluginId)
849
+ return true;
850
+ const parsed = parsePluginId(root.pluginId);
851
+ if (parsed.sourceId !== options.target.sourceId)
852
+ continue;
853
+ const plugins = await readOfflinePluginManifestCandidates({
854
+ target: options.target,
855
+ sourceIdentity: options.target.sourceIdentity,
856
+ pluginName: parsed.pluginName,
857
+ env: options.env,
858
+ inventory: options.inventory,
859
+ });
860
+ pending.push(...plugins.flatMap((plugin) => plugin.manifest.dependencies ?? []).map((pluginName) => ({
861
+ pluginId: formatPluginId({ sourceId: parsed.sourceId, pluginName }),
862
+ platforms: [options.target.platform],
863
+ })));
864
+ }
865
+ return false;
811
866
  }
812
867
  export async function runRebuildCommand(options) {
813
868
  if (options.runtime?.catalogResolution && options.runtime.bypassDependencyPlan) {
@@ -818,10 +873,24 @@ export async function runRebuildCommand(options) {
818
873
  ?? inventoryCatalogSourceReferences;
819
874
  let ordinaryInventory;
820
875
  const readOrdinaryInventory = async () => {
821
- ordinaryInventory ??= await readInventory({
876
+ ordinaryInventory ??= await measureCommandPerformanceStage(options.runtime, 'inventory', () => readInventory({
822
877
  cwd: options.commandOptions.cwd,
823
878
  env: runtimeEnv,
824
- });
879
+ ...(options.command === 'cleanup' && options.all
880
+ ? { includeOrphanedCodexNativeResidue: true }
881
+ : {}),
882
+ ...(options.command === 'cleanup' && !options.all
883
+ ? {
884
+ codexNativeResidueTargets: options.pluginNames.map((selector) => {
885
+ const parsed = parsePluginId(selector);
886
+ return {
887
+ pluginName: parsed.pluginName,
888
+ ...(parsed.sourceId ? { sourceId: parsed.sourceId } : {}),
889
+ };
890
+ }),
891
+ }
892
+ : {}),
893
+ }));
825
894
  return ordinaryInventory;
826
895
  };
827
896
  let requestedPluginNames = options.pluginNames;
@@ -830,7 +899,6 @@ export async function runRebuildCommand(options) {
830
899
  const expectedOfflineCleanup = options.runtime?.expectedOfflineCleanupEvidence;
831
900
  let cleanupInventory;
832
901
  let cleanupLiveTargets = [];
833
- let cleanupHasDamagedExclusiveTarget = false;
834
902
  let liveTargets = [];
835
903
  let legacyLiveTargets = [];
836
904
  if (options.command === 'cleanup' && !options.all) {
@@ -863,48 +931,8 @@ export async function runRebuildCommand(options) {
863
931
  }
864
932
  cleanupLiveTargets = [liveTarget];
865
933
  }
866
- if (cleanupForce && !expectedOfflineCleanup) {
867
- const damagedTargets = (await Promise.all(requestedPluginNames.map(async (selector) => parsePluginId(selector).sourceId
868
- ? discoverDamagedExclusiveCleanupTargets({
869
- selector,
870
- platforms: selectedPlatforms(options.commandOptions.platform),
871
- scope: options.commandOptions.scope,
872
- cwd: options.commandOptions.cwd,
873
- env: runtimeEnv,
874
- })
875
- : []))).flat();
876
- cleanupHasDamagedExclusiveTarget = damagedTargets.length > 0;
877
- cleanupLiveTargets = [...new Map([
878
- ...damagedTargets,
879
- ...cleanupLiveTargets,
880
- ].map((target) => [[target.pluginId, target.platform, target.scope].join('\0'), target])).values()];
881
- }
882
934
  liveTargets = cleanupLiveTargets;
883
935
  }
884
- if (cleanupHasDamagedExclusiveTarget) {
885
- if (options.runtime?.previewDependencyPlanOnly) {
886
- return previewOfflineOwnedComponents({
887
- targets: cleanupLiveTargets,
888
- cwd: options.commandOptions.cwd,
889
- env: runtimeEnv,
890
- nativeCliRunner: options.runtime.nativeCliRunner,
891
- codexManagerPreviewer: options.runtime.codexManagerPreviewer,
892
- onPreviewStage: options.runtime.onPreviewStage,
893
- });
894
- }
895
- return cleanupOfflineOwnedComponents({
896
- targets: cleanupLiveTargets,
897
- cwd: options.commandOptions.cwd,
898
- env: runtimeEnv,
899
- force: true,
900
- removeObsoleteInstallationContracts: options.runtime?.removeObsoleteInstallationContracts,
901
- nativeCliRunner: options.runtime?.nativeCliRunner,
902
- codexManager: options.runtime?.codexManager,
903
- codexManagerResolver: options.runtime?.codexManagerResolver,
904
- managedCodexAuthorizer: options.runtime?.managedCodexAuthorizer,
905
- corruptExclusiveDirectoryOperations: options.runtime?.corruptExclusiveDirectoryOperations,
906
- });
907
- }
908
936
  if (options.all) {
909
937
  const inventory = await readOrdinaryInventory();
910
938
  if (options.command !== 'cleanup')
@@ -937,6 +965,10 @@ export async function runRebuildCommand(options) {
937
965
  ].join('\0'), target];
938
966
  })).values()];
939
967
  liveTargets = projectedTargets;
968
+ if (options.command === 'cleanup') {
969
+ cleanupInventory = inventory;
970
+ cleanupLiveTargets = projectedTargets;
971
+ }
940
972
  const identities = new Map();
941
973
  for (const reference of live) {
942
974
  if (!reference.sourceIdentity) {
@@ -959,6 +991,147 @@ export async function runRebuildCommand(options) {
959
991
  }
960
992
  return mergeRebuildCommandResults([]);
961
993
  }
994
+ if (options.command === 'cleanup') {
995
+ if (cleanupLiveTargets.some((target) => target.marketplaceIdentityUnresolved)) {
996
+ throw namespacedPreflightError('marketplaceIdentityUnavailable: Cannot uniquely recover the native marketplace name from live inventory or a canonical native registration path.', cleanupLiveTargets);
997
+ }
998
+ if (cleanupLiveTargets.length === 0) {
999
+ const configuredSnapshot = await readCatalogSourcesConfigSnapshot(runtimeEnv).catch(() => null);
1000
+ const configuredSources = new Set(configuredSnapshot?.config.sources.map((source) => source.id) ?? []);
1001
+ const requestedPlatforms = selectedPlatforms(options.commandOptions.platform);
1002
+ const primarySource = configuredSnapshot?.config.sources.find((source) => source.primary)?.id;
1003
+ const unresolvedTargets = requestedPluginNames.flatMap((selector) => {
1004
+ const parsed = parsePluginId(selector);
1005
+ const sourceId = parsed.sourceId ?? primarySource;
1006
+ return sourceId ? requestedPlatforms.map((platform) => ({
1007
+ pluginId: formatPluginId({ sourceId, pluginName: parsed.pluginName }),
1008
+ pluginName: parsed.pluginName,
1009
+ sourceId,
1010
+ sourceIdentity: 'unavailable',
1011
+ platform,
1012
+ scope: options.commandOptions.scope,
1013
+ })) : [];
1014
+ });
1015
+ const inventoryError = cleanupInventory?.errors.find((error) => error.relevance.global || unresolvedTargets.some((target) => !error.relevance.global
1016
+ && (error.relevance.sourceId === undefined || error.relevance.sourceId === target.sourceId)
1017
+ && (error.relevance.pluginName === undefined || error.relevance.pluginName === target.pluginName)
1018
+ && (error.relevance.platform === undefined || error.relevance.platform === target.platform)
1019
+ && (error.relevance.scope === undefined
1020
+ || error.relevance.scope === target.scope
1021
+ || target.platform === 'codex')));
1022
+ if (inventoryError) {
1023
+ throw namespacedPreflightError(`stateInventoryFailed: ${inventoryError.message}`, unresolvedTargets);
1024
+ }
1025
+ const missingTargets = requestedPluginNames.flatMap((selector) => {
1026
+ const parsed = parsePluginId(selector);
1027
+ return parsed.sourceId && !configuredSources.has(parsed.sourceId)
1028
+ ? requestedPlatforms.map((platform) => ({
1029
+ pluginId: selector,
1030
+ pluginName: parsed.pluginName,
1031
+ sourceId: parsed.sourceId,
1032
+ sourceIdentity: 'unavailable',
1033
+ platform,
1034
+ scope: options.commandOptions.scope,
1035
+ }))
1036
+ : [];
1037
+ });
1038
+ if (missingTargets.length > 0) {
1039
+ throw namespacedPreflightError('sourceMissing: cleanup target source is unavailable.', missingTargets);
1040
+ }
1041
+ const result = mergeRebuildCommandResults([]);
1042
+ if (cleanupForce && !options.runtime?.previewDependencyPlanOnly) {
1043
+ try {
1044
+ await (options.runtime?.removeObsoleteInstallationContracts ?? fs.remove)(path.join(resolveAwHome(runtimeEnv), 'installation-contracts'));
1045
+ }
1046
+ catch (error) {
1047
+ result.warnings.push(`obsoleteContractsCleanupFailed: ${error instanceof Error ? error.message : String(error)}`);
1048
+ }
1049
+ }
1050
+ return result;
1051
+ }
1052
+ const configuredSources = await readCatalogSourcesConfigSnapshot(runtimeEnv)
1053
+ .then((snapshot) => new Set(snapshot.config.sources.map((source) => source.id)))
1054
+ .catch(() => new Set());
1055
+ if (!cleanupForce) {
1056
+ await assertOfflineCleanupDependents({
1057
+ targets: cleanupLiveTargets,
1058
+ cwd: options.commandOptions.cwd,
1059
+ env: runtimeEnv,
1060
+ readInventory,
1061
+ });
1062
+ }
1063
+ const desiredIntent = await readPluginIntentState({
1064
+ scope: options.commandOptions.scope,
1065
+ cwd: options.commandOptions.cwd,
1066
+ env: runtimeEnv,
1067
+ });
1068
+ const desiredFlags = await Promise.all(cleanupLiveTargets.map((target) => cleanupTargetIsDesired({
1069
+ target,
1070
+ roots: desiredIntent.roots,
1071
+ inventory: cleanupInventory,
1072
+ env: runtimeEnv,
1073
+ })));
1074
+ const desiredTargets = cleanupLiveTargets.filter((_, index) => desiredFlags[index]);
1075
+ const removalTargets = cleanupLiveTargets.filter((_, index) => !desiredFlags[index]);
1076
+ if (removalTargets.length === 0) {
1077
+ const result = desiredCleanupNoopResult(desiredTargets, options.commandOptions.cwd);
1078
+ if (cleanupForce && !options.runtime?.previewDependencyPlanOnly) {
1079
+ try {
1080
+ await (options.runtime?.removeObsoleteInstallationContracts ?? fs.remove)(path.join(resolveAwHome(runtimeEnv), 'installation-contracts'));
1081
+ }
1082
+ catch (error) {
1083
+ result.warnings.push(`obsoleteContractsCleanupFailed: ${error instanceof Error ? error.message : String(error)}`);
1084
+ }
1085
+ }
1086
+ return leaseSafeRebuildResult(result);
1087
+ }
1088
+ const evidence = expectedOfflineCleanup
1089
+ ? [expectedOfflineCleanup]
1090
+ : removalTargets.flatMap((target) => {
1091
+ const value = offlineCleanupEvidenceFromInventory({
1092
+ reason: configuredSources.has(target.sourceId) ? 'bundleUnavailable' : 'sourceMissing',
1093
+ pluginId: target.pluginId,
1094
+ pluginName: target.pluginName,
1095
+ sourceId: target.sourceId,
1096
+ sourceIdentity: target.sourceIdentity,
1097
+ platform: target.platform,
1098
+ scope: target.scope,
1099
+ inventory: cleanupInventory,
1100
+ });
1101
+ return value ? [value] : [];
1102
+ });
1103
+ if (options.runtime?.previewDependencyPlanOnly) {
1104
+ const previewResult = await previewOfflineOwnedComponents({
1105
+ targets: removalTargets,
1106
+ evidence,
1107
+ cwd: options.commandOptions.cwd,
1108
+ env: runtimeEnv,
1109
+ onPreviewStage: options.runtime.onPreviewStage,
1110
+ });
1111
+ return desiredTargets.length > 0
1112
+ ? mergeRebuildCommandResults([
1113
+ desiredCleanupNoopResult(desiredTargets, options.commandOptions.cwd),
1114
+ previewResult,
1115
+ ])
1116
+ : previewResult;
1117
+ }
1118
+ const cleanupResult = await cleanupOfflineOwnedComponents({
1119
+ targets: removalTargets,
1120
+ evidence,
1121
+ cwd: options.commandOptions.cwd,
1122
+ env: runtimeEnv,
1123
+ inventoryErrors: cleanupInventory.errors,
1124
+ removeObsoleteInstallationContracts: options.runtime?.removeObsoleteInstallationContracts,
1125
+ nativeCliRunner: options.runtime?.nativeCliRunner,
1126
+ codexManager: options.runtime?.codexManager,
1127
+ });
1128
+ return desiredTargets.length > 0
1129
+ ? mergeRebuildCommandResults([
1130
+ desiredCleanupNoopResult(desiredTargets, options.commandOptions.cwd),
1131
+ cleanupResult,
1132
+ ])
1133
+ : cleanupResult;
1134
+ }
962
1135
  try {
963
1136
  if (options.runtime?.previewDependencyPlanOnly) {
964
1137
  await options.runtime.onPreviewStage?.('catalog');
@@ -967,31 +1140,18 @@ export async function runRebuildCommand(options) {
967
1140
  throw options.runtime.catalogBootstrapError;
968
1141
  }
969
1142
  const catalogOperation = options.runtime?.catalogOperationContext
970
- ?? (options.command === 'cleanup'
971
- ? undefined
972
- : await resolveCatalogOperationContext({
973
- cliVersion: resolveRuntimeCliVersion(),
974
- ...(sourceIds ? { sourceIds } : {}),
975
- env: runtimeEnv,
976
- ...(options.runtime?.catalogGenerationRuntime
977
- ? { runtime: options.runtime.catalogGenerationRuntime }
978
- : {}),
979
- ...(options.runtime?.awHomeMutationToken
980
- ? { bootstrapMissingActive: false }
981
- : {}),
982
- }));
1143
+ ?? await measureCommandPerformanceStage(options.runtime, 'catalog.load', () => resolveCatalogOperationContext({
1144
+ ...(sourceIds ? { sourceIds } : {}),
1145
+ env: runtimeEnv,
1146
+ }));
983
1147
  if (options.runtime?.expectedGenerationSetDigest
984
1148
  && catalogOperation?.generationSetDigest !== options.runtime.expectedGenerationSetDigest) {
985
1149
  throw new Error('operationReviewStale: Active Catalog generation changed before mutation.');
986
1150
  }
987
1151
  const resolvedResult = await withResolvedMultiSourceCatalog({
988
1152
  cliVersion: resolveRuntimeCliVersion(),
989
- freshnessPolicy: 'cacheFirst',
990
1153
  ...(sourceIds ? { sourceIds } : {}),
991
1154
  ...(catalogOperation ? { operationContext: catalogOperation } : {}),
992
- ...(options.command === 'cleanup' || options.runtime?.awHomeMutationToken
993
- ? { bootstrapMissingActive: false }
994
- : {}),
995
1155
  env: runtimeEnv,
996
1156
  consume: async (catalog) => {
997
1157
  const failedSources = catalog.sources.filter((source) => source.error);
@@ -1050,9 +1210,16 @@ export async function runRebuildCommand(options) {
1050
1210
  ? availableEntries.filter((candidate) => selectedEntries.some((selectedEntry) => candidate.source.sourceId === selectedEntry.source.sourceId
1051
1211
  && candidate.entry.dependencies.includes(selectedEntry.entry.name)))
1052
1212
  : [];
1213
+ const removalDesiredEntries = options.command === 'cleanup' || options.command === 'uninstall'
1214
+ ? entryClosure(selectedEntries)
1215
+ : [];
1053
1216
  const requiredEntryClosure = dependencyEnabled
1054
1217
  ? entryClosure(selectedEntries)
1055
- : [...new Map([...selectedEntries, ...removalConsumerEntries].map((candidate) => [candidate.pluginId, candidate])).values()];
1218
+ : [...new Map([
1219
+ ...selectedEntries,
1220
+ ...removalDesiredEntries,
1221
+ ...removalConsumerEntries,
1222
+ ].map((candidate) => [candidate.pluginId, candidate])).values()];
1056
1223
  if (dependencyEnabled) {
1057
1224
  for (const candidate of requiredEntryClosure) {
1058
1225
  const requiredCapabilities = candidate.entry.requires.aw.capabilities ?? [];
@@ -1061,15 +1228,17 @@ export async function runRebuildCommand(options) {
1061
1228
  throw new AwCapabilityUnsupportedError(candidate.pluginId, support.missing);
1062
1229
  }
1063
1230
  }
1064
- for (const item of catalog.sources) {
1065
- const source = item.resolution;
1066
- const names = requiredEntryClosure
1067
- .filter((candidate) => candidate.source.sourceId === source.sourceId)
1068
- .map((candidate) => candidate.entry.name);
1069
- if (names.length > 0) {
1070
- await validateCatalogSnapshotPluginEntriesComplete(source.rootDir, names);
1231
+ await measureCommandPerformanceStage(options.runtime, 'catalog.validation', async () => {
1232
+ for (const item of catalog.sources) {
1233
+ const source = item.resolution;
1234
+ const names = requiredEntryClosure
1235
+ .filter((candidate) => candidate.source.sourceId === source.sourceId)
1236
+ .map((candidate) => candidate.entry.name);
1237
+ if (names.length > 0) {
1238
+ await validateCatalogSnapshotPluginEntriesComplete(source.rootDir, names);
1239
+ }
1071
1240
  }
1072
- }
1241
+ });
1073
1242
  }
1074
1243
  const available = await Promise.all(requiredEntryClosure.map(async (candidate) => {
1075
1244
  const plugin = await readPluginManifest(path.join(candidate.source.rootDir, 'plugins', candidate.entry.name), { mode: 'bundle' });
@@ -1105,7 +1274,9 @@ export async function runRebuildCommand(options) {
1105
1274
  }
1106
1275
  const closureEntries = dependencyEnabled
1107
1276
  ? [...new Map([...closureEntriesByPlatform.values()].flat().map((entry) => [entry.pluginId, entry])).values()]
1108
- : selected;
1277
+ : options.command === 'cleanup' || options.command === 'uninstall'
1278
+ ? resolveClosureEntries(selected)
1279
+ : selected;
1109
1280
  const rawExecutionSelections = options.all
1110
1281
  ? liveTargets.flatMap((target) => {
1111
1282
  const entry = selected.find((candidate) => candidate.pluginId === target.pluginId);
@@ -1149,6 +1320,20 @@ export async function runRebuildCommand(options) {
1149
1320
  const stateInventory = await readOrdinaryInventory();
1150
1321
  if (options.command !== 'cleanup')
1151
1322
  assertCatalogSourceInventoryComplete(stateInventory);
1323
+ const hasCodexSelection = executionSelections.some((selection) => selection.platform === 'codex');
1324
+ const intentBeforeByScope = hasCodexSelection || dependencyEnabled
1325
+ ? {
1326
+ user: await readPluginIntentState({
1327
+ scope: 'user', cwd: options.commandOptions.cwd, env: runtimeEnv,
1328
+ }),
1329
+ project: await readPluginIntentState({
1330
+ scope: 'project', cwd: options.commandOptions.cwd, env: runtimeEnv,
1331
+ }),
1332
+ }
1333
+ : {
1334
+ user: createEmptyPluginIntentState(),
1335
+ project: createEmptyPluginIntentState(),
1336
+ };
1152
1337
  const commandProjectKey = await resolveCanonicalRepoProjectKey(options.commandOptions.cwd);
1153
1338
  const codexScopePathsAlias = codexProjectConfigAliasesUserHome(options.commandOptions.cwd, runtimeEnv);
1154
1339
  const selectedLegacy = selectedLegacyLiveReferences(stateInventory.references, new Set(executionSelections.map((selection) => selection.platform)), options.commandOptions.scope);
@@ -1236,10 +1421,27 @@ export async function runRebuildCommand(options) {
1236
1421
  context: executionContext,
1237
1422
  scriptTimeoutMs: options.commandOptions.scriptTimeoutMs,
1238
1423
  });
1424
+ const selectedRootsForPlan = (plan) => [...new Map(plan.targets.flatMap((target) => target.relation === 'requested'
1425
+ ? [[target.pluginId, {
1426
+ pluginId: target.pluginId,
1427
+ platforms: plan.targets.flatMap((candidate) => candidate.relation === 'requested'
1428
+ && candidate.pluginId === target.pluginId
1429
+ ? [candidate.platform]
1430
+ : []),
1431
+ }]]
1432
+ : [])).values()];
1239
1433
  const createPlan = async (inventory) => {
1240
1434
  if (options.command !== 'cleanup')
1241
1435
  assertCatalogSourceInventoryComplete(inventory);
1436
+ const codexActivationSnapshots = executionSelections.some((selection) => selection.platform === 'codex')
1437
+ ? Object.fromEntries(await Promise.all(['user', 'project'].map(async (scope) => [
1438
+ scope,
1439
+ (await readCodexConfigFile(resolveCodexPaths(options.commandOptions.cwd, scope, runtimeEnv).configTomlPath)).snapshot,
1440
+ ])))
1441
+ : undefined;
1242
1442
  const targets = [];
1443
+ const placementNodes = [];
1444
+ const blockingTargets = new Map();
1243
1445
  for (const [order, { entry, platform }] of executionSelections.entries()) {
1244
1446
  const relation = selectedIds.has(entry.pluginId) ? 'requested' : 'dependency';
1245
1447
  let resolvedScope = options.commandOptions.scope;
@@ -1292,7 +1494,54 @@ export async function runRebuildCommand(options) {
1292
1494
  ? Promise.resolve({ state: 'absent', components: [], errors: [] })
1293
1495
  : inspectScope(otherScope),
1294
1496
  ]);
1295
- let state = requestedInspection.state;
1497
+ const surface = platform === 'codex'
1498
+ ? analyzeCodexSurface(entry.plugin.manifest)
1499
+ : undefined;
1500
+ const codexPluginId = `${entry.plugin.manifest.name}@${marketplaceNameFor(entry, platform, resolvedScope)}`;
1501
+ const placementState = (inspected, scope) => {
1502
+ if (platform !== 'codex'
1503
+ || surface?.classification !== 'native'
1504
+ || options.command === 'cleanup'
1505
+ || options.command === 'uninstall')
1506
+ return inspected.state;
1507
+ const nativePresent = inspected.components.some((component) => component.component.kind === 'native-plugin' && component.state === 'present');
1508
+ return nativePresent
1509
+ && codexActivationSnapshots?.[scope].plugins[codexPluginId]?.enabled === true
1510
+ ? 'present'
1511
+ : 'absent';
1512
+ };
1513
+ const requestedPlacementState = placementState(requestedInspection, options.commandOptions.scope);
1514
+ let otherPlacementState = placementState(otherInspection, otherScope);
1515
+ if (surface?.classification === 'native'
1516
+ && requestedPlacementState === 'present'
1517
+ && otherPlacementState === 'present') {
1518
+ otherPlacementState = 'absent';
1519
+ }
1520
+ const allowedScopes = platform === 'codex'
1521
+ ? [
1522
+ ...(surface?.ok ? ['user'] : []),
1523
+ ...(surface?.ok && !codexScopePathsAlias ? ['project'] : []),
1524
+ ]
1525
+ : ['project', 'user'];
1526
+ if (allowedScopes.length === 0)
1527
+ allowedScopes.push(options.commandOptions.scope);
1528
+ placementNodes.push({
1529
+ pluginId: entry.pluginId,
1530
+ pluginName: entry.plugin.manifest.name,
1531
+ platform,
1532
+ relation,
1533
+ depth: order,
1534
+ allowedScopes,
1535
+ current: {
1536
+ project: options.commandOptions.scope === 'project'
1537
+ ? requestedPlacementState
1538
+ : otherPlacementState,
1539
+ user: options.commandOptions.scope === 'user'
1540
+ ? requestedPlacementState
1541
+ : otherPlacementState,
1542
+ },
1543
+ });
1544
+ let state = requestedPlacementState;
1296
1545
  let action = relation === 'requested'
1297
1546
  ? options.command
1298
1547
  : options.command === 'reinstall' ? 'reinstall' : 'upgrade';
@@ -1303,15 +1552,32 @@ export async function runRebuildCommand(options) {
1303
1552
  action = 'block';
1304
1553
  errorCode = 'artifactCollision';
1305
1554
  }
1555
+ else if (dependencyEnabled
1556
+ && platform === 'codex'
1557
+ && relation === 'requested'
1558
+ && surface?.scopeOverlap === 'aw-owned-conflict'
1559
+ && options.runtime?.reconcileIntentOnly !== true
1560
+ && !(options.command === 'install'
1561
+ && intentBeforeByScope[otherScope].roots.some((root) => root.pluginId === entry.pluginId && root.platforms.includes(platform)))
1562
+ && otherInspection.components.some((component) => component.state !== 'absent'
1563
+ && component.component.kind !== 'native-plugin'
1564
+ && !component.component.identity.artifactKey.startsWith('activation-')
1565
+ && !component.component.identity.artifactKey.startsWith('marketplace-'))) {
1566
+ action = 'block';
1567
+ errorCode = 'codexExtensionScopeConflict';
1568
+ }
1306
1569
  else if (dependencyEnabled && relation === 'requested'
1307
1570
  && (requestedInspection.state === 'error'
1308
- || (requestedInspection.state === 'partial' && options.command !== 'reinstall'))) {
1571
+ || (requestedInspection.state === 'partial'
1572
+ && platform !== 'codex'
1573
+ && options.command !== 'reinstall'
1574
+ && options.command !== 'cleanup'))) {
1309
1575
  action = 'block';
1310
1576
  errorCode = 'dependencyNotReady';
1311
1577
  }
1312
1578
  if (!hasForeignCollision && relation === 'dependency'
1313
- && requestedInspection.state === 'present'
1314
- && otherInspection.state === 'present') {
1579
+ && requestedPlacementState === 'present'
1580
+ && otherPlacementState === 'present') {
1315
1581
  action = 'block';
1316
1582
  state = 'partial';
1317
1583
  errorCode = 'dependencyScopeConflict';
@@ -1324,24 +1590,26 @@ export async function runRebuildCommand(options) {
1324
1590
  }
1325
1591
  else if (!hasForeignCollision && relation === 'dependency'
1326
1592
  && options.commandOptions.scope === 'project'
1327
- && (otherInspection.state === 'partial' || otherInspection.state === 'error')) {
1593
+ && (otherPlacementState === 'partial' || otherPlacementState === 'error')) {
1328
1594
  resolvedScope = 'user';
1329
- state = otherInspection.state;
1330
- if (otherInspection.state === 'error') {
1595
+ state = otherPlacementState;
1596
+ if (otherPlacementState === 'error') {
1331
1597
  action = 'block';
1332
1598
  errorCode = 'dependencyNotReady';
1333
1599
  }
1334
1600
  }
1335
1601
  else if (!hasForeignCollision && relation === 'dependency'
1336
1602
  && options.commandOptions.scope === 'user'
1337
- && otherInspection.state !== 'absent') {
1603
+ && otherPlacementState !== 'absent') {
1338
1604
  action = 'block';
1339
1605
  errorCode = 'dependencyScopeTooNarrow';
1340
1606
  }
1341
1607
  const resolvedInspection = resolvedScope === options.commandOptions.scope
1342
1608
  ? requestedInspection
1343
1609
  : otherInspection;
1344
- state = resolvedInspection.state;
1610
+ state = resolvedScope === options.commandOptions.scope
1611
+ ? requestedPlacementState
1612
+ : otherPlacementState;
1345
1613
  if (!hasForeignCollision && !errorCode
1346
1614
  && relation === 'dependency' && options.command === 'install') {
1347
1615
  action = state === 'present' ? 'keep' : state === 'partial' ? 'reinstall' : 'install';
@@ -1352,13 +1620,6 @@ export async function runRebuildCommand(options) {
1352
1620
  state = resolvedInspection.state;
1353
1621
  errorCode ??= 'dependencyNotReady';
1354
1622
  }
1355
- if (!hasForeignCollision && relation === 'dependency'
1356
- && platform === 'codex'
1357
- && resolvedScope === 'project'
1358
- && (entry.plugin.manifest.apps || !hasCodexProjectManagedArtifacts(entry.plugin))) {
1359
- action = 'block';
1360
- errorCode = 'dependencyTargetUnsupported';
1361
- }
1362
1623
  if (entry.plugin.manifest.config?.schema
1363
1624
  && (action === 'install' || action === 'upgrade' || action === 'reinstall')) {
1364
1625
  const effective = await resolveEffectivePluginConfig({
@@ -1381,6 +1642,12 @@ export async function runRebuildCommand(options) {
1381
1642
  action = 'block';
1382
1643
  errorCode = 'codexScopePathAlias';
1383
1644
  }
1645
+ if (dependencyEnabled && platform === 'codex' && !surface?.ok) {
1646
+ action = 'block';
1647
+ errorCode = surface?.errorCode ?? 'codexNativeKindUnsupported';
1648
+ }
1649
+ if (errorCode)
1650
+ blockingTargets.set(`${entry.pluginId}\0${platform}`, errorCode);
1384
1651
  targets.push({
1385
1652
  pluginId: entry.pluginId,
1386
1653
  pluginName: entry.plugin.manifest.name,
@@ -1398,6 +1665,46 @@ export async function runRebuildCommand(options) {
1398
1665
  ...(errorCode ? { errorCode } : {}),
1399
1666
  });
1400
1667
  }
1668
+ if (dependencyEnabled) {
1669
+ const placement = createDependencyPlacementPlan({
1670
+ requestedPluginIds: selected.map((entry) => entry.pluginId),
1671
+ nodes: placementNodes,
1672
+ roots: placementNodes.flatMap((node) => selectedIds.has(node.pluginId)
1673
+ ? [{ pluginId: node.pluginId, platform: node.platform, scope: options.commandOptions.scope }]
1674
+ : []),
1675
+ dependencyEdges: closureEntries.flatMap((entry) => (entry.plugin.manifest.dependencies ?? []).flatMap((dependency) => {
1676
+ const dependencyId = formatPluginId({
1677
+ sourceId: entry.source.sourceId,
1678
+ pluginName: dependency,
1679
+ });
1680
+ return ['codex', 'claude'].flatMap((platform) => placementNodes.some((node) => node.pluginId === entry.pluginId && node.platform === platform)
1681
+ && placementNodes.some((node) => node.pluginId === dependencyId && node.platform === platform)
1682
+ ? [{ from: entry.pluginId, to: dependencyId, platform }]
1683
+ : []);
1684
+ })),
1685
+ scopeOverrides: effectiveDependencyScopeOverrides({
1686
+ state: intentBeforeByScope[options.commandOptions.scope],
1687
+ explicit: options.commandOptions
1688
+ .dependencyScopeOverrides ?? [],
1689
+ nodes: placementNodes,
1690
+ ignoredPersistedRootIds: selectedIds,
1691
+ }),
1692
+ operation: options.command,
1693
+ });
1694
+ return createDependencyOperationPlan({
1695
+ requestedPluginIds: placement.requestedPluginIds,
1696
+ resolvedPluginIds: placement.resolvedPluginIds,
1697
+ dependencyEdges: placement.dependencyEdges,
1698
+ scopeOverrides: placement.scopeOverrides,
1699
+ claimRoots: placement.claimRoots,
1700
+ targets: placement.targets.map((target) => {
1701
+ const errorCode = blockingTargets.get(`${target.pluginId}\0${target.platform}`);
1702
+ return errorCode
1703
+ ? { ...target, action: 'block', errorCode }
1704
+ : target;
1705
+ }),
1706
+ });
1707
+ }
1401
1708
  return createDependencyOperationPlan({
1402
1709
  requestedPluginIds: selected.map((entry) => entry.pluginId),
1403
1710
  resolvedPluginIds: closureEntries.map((entry) => entry.pluginId),
@@ -1408,6 +1715,214 @@ export async function runRebuildCommand(options) {
1408
1715
  targets,
1409
1716
  });
1410
1717
  };
1718
+ const createCodexPlans = async (plan, activationSupport = 'observed') => {
1719
+ const codexTargets = plan.targets.filter((target) => target.platform === 'codex');
1720
+ const selectedRoots = selectedRootsForPlan(plan);
1721
+ const currentScope = options.commandOptions.scope;
1722
+ const otherScope = currentScope === 'user' ? 'project' : 'user';
1723
+ const reconciledCodexTargets = codexTargets;
1724
+ if (reconciledCodexTargets.length === 0)
1725
+ return [];
1726
+ const beforeByScope = intentBeforeByScope;
1727
+ const afterByScope = {
1728
+ user: beforeByScope.user,
1729
+ project: beforeByScope.project,
1730
+ };
1731
+ afterByScope[currentScope] = deriveDesiredIntentState({
1732
+ command: options.command,
1733
+ state: beforeByScope[currentScope],
1734
+ selectedRoots,
1735
+ plan,
1736
+ reconcileIntentOnly: options.runtime?.reconcileIntentOnly,
1737
+ });
1738
+ if (options.command === 'install' && options.runtime?.reconcileIntentOnly !== true) {
1739
+ let other = afterByScope[otherScope];
1740
+ for (const root of selectedRoots)
1741
+ other = removePluginIntentRootPlatforms(other, root);
1742
+ afterByScope[otherScope] = other;
1743
+ }
1744
+ const desiredScopesForEntry = (entry) => {
1745
+ const scopes = new Set();
1746
+ for (const intentScope of ['user', 'project']) {
1747
+ const intent = afterByScope[intentScope];
1748
+ for (const root of intent.roots.filter((candidate) => candidate.platforms.includes('codex'))) {
1749
+ const rootEntry = available.find((candidate) => candidate.pluginId === root.pluginId);
1750
+ if (!rootEntry || rootEntry.source.sourceId !== entry.source.sourceId)
1751
+ continue;
1752
+ const sourcePlugins = available
1753
+ .filter((candidate) => candidate.source.sourceId === entry.source.sourceId)
1754
+ .map((candidate) => candidate.plugin);
1755
+ let closure;
1756
+ try {
1757
+ closure = resolvePluginMetaDependencyClosure(sourcePlugins, [rootEntry.plugin.manifest.name]);
1758
+ }
1759
+ catch {
1760
+ closure = [rootEntry.plugin];
1761
+ }
1762
+ if (!closure.some((plugin) => plugin.manifest.name === entry.plugin.manifest.name)) {
1763
+ continue;
1764
+ }
1765
+ const currentPlannedScope = intentScope === currentScope
1766
+ && selectedRoots.some((selectedRoot) => selectedRoot.pluginId === root.pluginId)
1767
+ ? plan.targets.find((target) => target.pluginId === entry.pluginId && target.platform === 'codex')?.resolvedScope
1768
+ : undefined;
1769
+ const override = intent.dependencyScopeOverrides.find((candidate) => candidate.pluginId === entry.pluginId && candidate.platform === 'codex');
1770
+ const reusedUserDependency = intentScope === 'project'
1771
+ && root.pluginId !== entry.pluginId
1772
+ && scopes.has('user');
1773
+ scopes.add(currentPlannedScope
1774
+ ?? override?.scope
1775
+ ?? (reusedUserDependency ? 'user' : intentScope));
1776
+ }
1777
+ }
1778
+ return [...scopes].sort();
1779
+ };
1780
+ const intentScopesForEntry = (entry) => {
1781
+ const scopes = new Set();
1782
+ for (const intentScope of ['user', 'project']) {
1783
+ for (const root of afterByScope[intentScope].roots.filter((candidate) => candidate.platforms.includes('codex'))) {
1784
+ const rootEntry = available.find((candidate) => candidate.pluginId === root.pluginId);
1785
+ if (!rootEntry || rootEntry.source.sourceId !== entry.source.sourceId)
1786
+ continue;
1787
+ const sourcePlugins = available
1788
+ .filter((candidate) => candidate.source.sourceId === entry.source.sourceId)
1789
+ .map((candidate) => candidate.plugin);
1790
+ let closure;
1791
+ try {
1792
+ closure = resolvePluginMetaDependencyClosure(sourcePlugins, [rootEntry.plugin.manifest.name]);
1793
+ }
1794
+ catch {
1795
+ closure = [rootEntry.plugin];
1796
+ }
1797
+ if (closure.some((plugin) => plugin.manifest.name === entry.plugin.manifest.name)) {
1798
+ scopes.add(intentScope);
1799
+ }
1800
+ }
1801
+ }
1802
+ return [...scopes].sort();
1803
+ };
1804
+ const inspectBothScopes = options.command !== 'cleanup' && options.command !== 'uninstall';
1805
+ const readActivationConfig = async (scope) => {
1806
+ return readCodexConfigFile(resolveCodexPaths(options.commandOptions.cwd, scope, runtimeEnv).configTomlPath);
1807
+ };
1808
+ const [userConfig, projectConfig] = await Promise.all([
1809
+ readActivationConfig('user'),
1810
+ readActivationConfig('project'),
1811
+ ]);
1812
+ const catalogClosure = [];
1813
+ const liveEvidence = [];
1814
+ for (const planned of reconciledCodexTargets) {
1815
+ const entry = available.find((candidate) => candidate.pluginId === planned.pluginId);
1816
+ const surface = analyzeCodexSurface(entry.plugin.manifest);
1817
+ const marketplaceName = marketplaceNameFor(entry, 'codex', planned.resolvedScope);
1818
+ const codexPluginId = `${entry.plugin.manifest.name}@${marketplaceName}`;
1819
+ const contentDigest = await calculateCodexSharedNativePayloadDigest(entry.plugin.rootDir);
1820
+ const desiredScopes = desiredScopesForEntry(entry);
1821
+ catalogClosure.push({
1822
+ pluginId: entry.pluginId,
1823
+ pluginName: entry.plugin.manifest.name,
1824
+ codexPluginId,
1825
+ version: entry.plugin.manifest.version,
1826
+ payloadDigest: contentDigest,
1827
+ relation: planned.relation,
1828
+ resolvedScope: planned.resolvedScope,
1829
+ desiredScopes,
1830
+ intentScopes: intentScopesForEntry(entry),
1831
+ surface,
1832
+ });
1833
+ const managedBefore = beforeByScope.user.managed.codexPluginIds.includes(codexPluginId)
1834
+ || beforeByScope.project.managed.codexPluginIds.includes(codexPluginId);
1835
+ const sharedInspection = surface.nativeKinds.length > 0
1836
+ ? await inspectCodexSharedNativeLive({
1837
+ pluginName: entry.plugin.manifest.name,
1838
+ desiredVersion: entry.plugin.manifest.version,
1839
+ expectedPayloadDigest: contentDigest,
1840
+ cwd: options.commandOptions.cwd,
1841
+ env: runtimeEnv,
1842
+ managedByCurrentIntent: managedBefore,
1843
+ marketplaceName,
1844
+ marketplaceRoot: path.join(resolveAwHome(runtimeEnv), 'marketplaces', entry.source.sourceId),
1845
+ })
1846
+ : undefined;
1847
+ const sharedNative = sharedInspection?.sharedNative
1848
+ ?? { state: 'absent', nativeAction: 'install' };
1849
+ const projectionEntries = [];
1850
+ const extensions = [];
1851
+ for (const scope of (inspectBothScopes
1852
+ ? ['user', 'project']
1853
+ : [currentScope])) {
1854
+ const desired = await inspection.buildTarget({
1855
+ sourceId: entry.source.sourceId,
1856
+ sourceIdentity: entry.source.sourceIdentity,
1857
+ pluginId: entry.pluginId,
1858
+ marketplaceName,
1859
+ marketplaceRoot: path.join(resolveAwHome(runtimeEnv), 'marketplaces', entry.source.sourceId),
1860
+ plugin: entry.plugin,
1861
+ platform: 'codex',
1862
+ scope,
1863
+ cwd: options.commandOptions.cwd,
1864
+ command: options.command,
1865
+ catalog: toCatalogResolution(entry.source),
1866
+ includeHistoricalLifecycleArtifacts: false,
1867
+ readOnlyInspectionPlan: true,
1868
+ });
1869
+ const inspected = await inspectOwnedComponents({
1870
+ components: desired.ownedComponents,
1871
+ nativeProbe: inspection.driver.probeNative,
1872
+ verifyHealth: true,
1873
+ });
1874
+ const projected = inspected.components.filter((component) => component.component.kind !== 'native-plugin'
1875
+ && !component.component.identity.artifactKey.startsWith('activation-')
1876
+ && !component.component.identity.artifactKey.startsWith('marketplace-'));
1877
+ projectionEntries.push(...projected.map((component) => ({
1878
+ scope,
1879
+ artifactKey: component.component.identity.artifactKey,
1880
+ state: component.state,
1881
+ collision: component.collision?.reason,
1882
+ })));
1883
+ const projectionState = projected.some((component) => component.collision)
1884
+ ? planned.action === 'block' ? 'absent' : 'foreign'
1885
+ : projected.some((component) => component.state === 'partial'
1886
+ || component.state === 'error')
1887
+ ? 'drifted'
1888
+ : projected.some((component) => component.state === 'present')
1889
+ ? 'present'
1890
+ : 'absent';
1891
+ for (const component of surface.components.filter((candidate) => candidate.owner === 'aw')) {
1892
+ extensions.push({ componentKey: component.key, scope, state: projectionState });
1893
+ }
1894
+ }
1895
+ liveEvidence.push({
1896
+ codexPluginId,
1897
+ sharedNative,
1898
+ ...(sharedInspection?.actualPayloadDigest
1899
+ ? { sharedNativePayloadDigest: sharedInspection.actualPayloadDigest }
1900
+ : {}),
1901
+ ...(sharedInspection?.payloadPath
1902
+ ? { sharedNativePayloadPath: sharedInspection.payloadPath }
1903
+ : {}),
1904
+ ...(sharedNative.state === 'corrupt' ? { repairableSharedNative: true } : {}),
1905
+ activations: {
1906
+ user: userConfig?.snapshot.plugins[codexPluginId]?.enabled,
1907
+ project: projectConfig?.snapshot.plugins[codexPluginId]?.enabled,
1908
+ },
1909
+ extensions,
1910
+ projectionDigest: JSON.stringify(projectionEntries.sort((left, right) => `${left.scope}\0${left.artifactKey}`.localeCompare(`${right.scope}\0${right.artifactKey}`, 'en'))),
1911
+ });
1912
+ }
1913
+ return [createCodexReconcilePlan({
1914
+ scope: currentScope,
1915
+ cwd: options.commandOptions.cwd,
1916
+ operation: options.runtime?.reconcileIntentOnly ? 'sync' : options.command,
1917
+ intentBefore: beforeByScope[currentScope],
1918
+ intentAfter: afterByScope[currentScope],
1919
+ relatedIntentBefore: beforeByScope[otherScope],
1920
+ relatedIntentAfter: afterByScope[otherScope],
1921
+ catalogClosure,
1922
+ liveEvidence,
1923
+ activationSupport,
1924
+ })];
1925
+ };
1411
1926
  const operationNeedsNativeCli = async (platform, plan) => {
1412
1927
  for (const { entry, platform: selectedPlatform } of executionSelections) {
1413
1928
  if (selectedPlatform !== platform)
@@ -1422,14 +1937,6 @@ export async function runRebuildCommand(options) {
1422
1937
  entry.plugin.marketplaceName = marketplaceName;
1423
1938
  const marketplaceRoot = path.join(resolveAwHome(runtimeEnv), 'marketplaces', entry.source.sourceId);
1424
1939
  if (!dependencyEnabled && selectedPlatform === 'codex') {
1425
- if (scope === 'user' && await codexNativeRemovalRequired({
1426
- pluginName: entry.plugin.manifest.name,
1427
- cwd: options.commandOptions.cwd,
1428
- env: runtimeEnv,
1429
- marketplaceName,
1430
- marketplaceRoot,
1431
- }))
1432
- return true;
1433
1940
  continue;
1434
1941
  }
1435
1942
  const target = await inspection.buildTarget({
@@ -1449,7 +1956,7 @@ export async function runRebuildCommand(options) {
1449
1956
  includeHistoricalLifecycleArtifacts: false,
1450
1957
  readOnlyInspectionPlan: true,
1451
1958
  });
1452
- if (dependencyEnabled && target.requiresNative)
1959
+ if (dependencyEnabled && (target.writeNativeRequired ?? target.requiresNative))
1453
1960
  return true;
1454
1961
  const nativeComponents = target.ownedComponents.filter((component) => component.kind === 'native-plugin'
1455
1962
  || (component.kind === 'shared-config'
@@ -1468,11 +1975,36 @@ export async function runRebuildCommand(options) {
1468
1975
  };
1469
1976
  if (options.runtime?.previewDependencyPlanOnly) {
1470
1977
  await options.runtime.onPreviewStage?.('dependencyPlan');
1471
- const initialPlan = dependencyEnabled ? await createPlan(stateInventory) : undefined;
1978
+ const initialPlan = dependencyEnabled
1979
+ ? await measureCommandPerformanceStage(options.runtime, 'dependency.plan', () => createPlan(stateInventory))
1980
+ : undefined;
1981
+ const provisionalCodexPlans = initialPlan ? await createCodexPlans(initialPlan) : [];
1982
+ const previewNeedsNativeMaterialization = provisionalCodexPlans.some((plan) => plan.sharedNativeActions.some((action) => action.action === 'install'
1983
+ || action.action === 'replace'
1984
+ || action.action === 'repair'));
1985
+ const previewActivationOnlyProject = !previewNeedsNativeMaterialization
1986
+ && provisionalCodexPlans.some((plan) => plan.requirement.requiresProjectPluginContract
1987
+ && plan.postcheck.plugins.length > 0);
1988
+ const previewConsumer = previewActivationOnlyProject
1989
+ ? await (options.runtime?.codexOperationConsumerResolver ?? resolveCodexOperationConsumer)({
1990
+ cwd: options.commandOptions.cwd,
1991
+ env: runtimeEnv,
1992
+ runner: options.runtime?.nativeCliRunner ?? getDefaultNativeCliRunner(),
1993
+ operationRequirement: getCodexOperationRequirement('project'),
1994
+ requiresNativeMaterialization: false,
1995
+ })
1996
+ : undefined;
1997
+ const initialCodexPlans = initialPlan && previewConsumer?.support === 'assumed'
1998
+ ? await createCodexPlans(initialPlan, 'assumed')
1999
+ : provisionalCodexPlans;
1472
2000
  const preview = mergeRebuildCommandResults([]);
1473
2001
  if (initialPlan)
1474
2002
  preview.dependencyPlan = initialPlan;
1475
- if (await operationNeedsNativeCli('codex', initialPlan)) {
2003
+ if (initialCodexPlans.length > 0)
2004
+ preview.codexReconcilePlans = initialCodexPlans;
2005
+ if (initialCodexPlans.some((plan) => plan.sharedNativeActions.some((action) => action.action === 'install'
2006
+ || action.action === 'replace'
2007
+ || action.action === 'repair'))) {
1476
2008
  preview.codexManagerPreview = await (options.runtime.codexManagerPreviewer ?? previewCodexManager)({
1477
2009
  cwd: options.commandOptions.cwd,
1478
2010
  env: runtimeEnv,
@@ -1506,11 +2038,62 @@ export async function runRebuildCommand(options) {
1506
2038
  contentDigest: entry.source.manifest.entries.find((candidate) => candidate.name === entry.plugin.manifest.name)?.contentDigest,
1507
2039
  });
1508
2040
  }
1509
- const lockedPlan = await createPlan(stateInventory);
2041
+ const lockedPlan = await measureCommandPerformanceStage(options.runtime, 'dependency.plan', () => createPlan(stateInventory));
2042
+ const provisionalLockedCodexPlans = await createCodexPlans(lockedPlan);
2043
+ const codexManagerRequired = provisionalLockedCodexPlans.some((plan) => plan.sharedNativeActions.some((action) => action.action === 'install'
2044
+ || action.action === 'replace'
2045
+ || action.action === 'repair'));
2046
+ const activationOnlyProjectOperation = !codexManagerRequired
2047
+ && provisionalLockedCodexPlans.some((plan) => plan.requirement.requiresProjectPluginContract
2048
+ && plan.postcheck.plugins.length > 0);
2049
+ const activationConsumer = activationOnlyProjectOperation
2050
+ ? await (options.runtime?.codexOperationConsumerResolver ?? resolveCodexOperationConsumer)({
2051
+ cwd: options.commandOptions.cwd,
2052
+ env: runtimeEnv,
2053
+ runner: executionContext.nativeCliRunner,
2054
+ operationRequirement: getCodexOperationRequirement('project'),
2055
+ requiresNativeMaterialization: false,
2056
+ })
2057
+ : undefined;
2058
+ const lockedCodexPlans = activationConsumer?.support === 'assumed'
2059
+ ? await createCodexPlans(lockedPlan, 'assumed')
2060
+ : provisionalLockedCodexPlans;
2061
+ if (activationConsumer?.support === 'observed') {
2062
+ executionContext.codexManager = activationConsumer.manager;
2063
+ }
1510
2064
  if (options.runtime?.expectedOperationDependencyPlan
1511
2065
  && JSON.stringify(lockedPlan) !== JSON.stringify(options.runtime.expectedOperationDependencyPlan)) {
1512
2066
  throw namespacedPreflightError('operationReviewStale: dependency plan changed before mutation.', preflightTargets);
1513
2067
  }
2068
+ if (options.runtime?.expectedCodexReconcilePlans
2069
+ && JSON.stringify(lockedCodexPlans) !== JSON.stringify(options.runtime.expectedCodexReconcilePlans)) {
2070
+ throw namespacedPreflightError('operationReviewStale: Codex reconciliation evidence or plan changed before mutation.', preflightTargets);
2071
+ }
2072
+ if (options.runtime?.directCli
2073
+ && options.commandOptions.yes !== true
2074
+ && (options.command === 'install'
2075
+ || options.command === 'upgrade'
2076
+ || options.command === 'reinstall')) {
2077
+ const sharedNativeImpact = lockedCodexPlans.some((plan) => plan.review.requiresConfirmation);
2078
+ if (sharedNativeImpact) {
2079
+ throw namespacedPreflightError('sharedNativeImpactAuthorizationRequired: Replacing or force-reinstalling shared Codex native state requires --yes.', preflightTargets);
2080
+ }
2081
+ }
2082
+ let desiredIntent;
2083
+ if (options.runtime?.awHomeMutationToken
2084
+ && !lockedPlan.targets.some((target) => target.action === 'block')) {
2085
+ const selectedRoots = selectedRootsForPlan(lockedPlan);
2086
+ desiredIntent = await writeDesiredIntentSuperset({
2087
+ command: options.command,
2088
+ cwd: options.commandOptions.cwd,
2089
+ scope: options.commandOptions.scope,
2090
+ selectedRoots,
2091
+ plan: lockedPlan,
2092
+ codexPlan: lockedCodexPlans[0],
2093
+ reconcileIntentOnly: options.runtime?.reconcileIntentOnly,
2094
+ env: runtimeEnv,
2095
+ });
2096
+ }
1514
2097
  for (const pluginName of new Set(lockedPlan.targets.flatMap((target) => target.platform === 'claude'
1515
2098
  && (target.action === 'install'
1516
2099
  || target.action === 'upgrade'
@@ -1523,15 +2106,36 @@ export async function runRebuildCommand(options) {
1523
2106
  env: runtimeEnv,
1524
2107
  });
1525
2108
  }
1526
- const codexManagerRequired = await operationNeedsNativeCli('codex', lockedPlan);
1527
2109
  const claudeCliRequired = await operationNeedsNativeCli('claude', lockedPlan);
2110
+ const codexUserActivationDesired = new Set(lockedCodexPlans.flatMap((plan) => plan.activationActions.user.flatMap((action) => action.desired ? [action.codexPluginId] : [])));
2111
+ executionContext.codexUserActivationDesired = codexUserActivationDesired;
1528
2112
  if (codexManagerRequired) {
1529
- executionContext.codexManager = await (options.runtime?.codexManagerResolver ?? resolveCodexManager)({
1530
- cwd: options.commandOptions.cwd,
1531
- env: runtimeEnv,
1532
- runner: executionContext.nativeCliRunner,
1533
- authorizer: options.runtime?.managedCodexAuthorizer,
1534
- });
2113
+ const codexRequirementScope = lockedCodexPlans.some((plan) => plan.requirement.requiresProjectPluginContract)
2114
+ ? 'project'
2115
+ : 'user';
2116
+ if (options.runtime?.codexManagerResolver && codexManagerRequired) {
2117
+ executionContext.codexManager = await options.runtime.codexManagerResolver({
2118
+ cwd: options.commandOptions.cwd,
2119
+ env: runtimeEnv,
2120
+ runner: executionContext.nativeCliRunner,
2121
+ authorizer: options.runtime?.managedCodexAuthorizer,
2122
+ operationRequirement: getCodexOperationRequirement(codexRequirementScope),
2123
+ });
2124
+ }
2125
+ else {
2126
+ const consumer = await (options.runtime?.codexOperationConsumerResolver ?? resolveCodexOperationConsumer)({
2127
+ cwd: options.commandOptions.cwd,
2128
+ env: runtimeEnv,
2129
+ runner: executionContext.nativeCliRunner,
2130
+ authorizer: options.runtime?.managedCodexAuthorizer,
2131
+ operationRequirement: getCodexOperationRequirement(codexRequirementScope),
2132
+ requiresNativeMaterialization: codexManagerRequired,
2133
+ });
2134
+ if (consumer.support !== 'observed') {
2135
+ throw new Error('Codex native materialization requires an observed CLI consumer.');
2136
+ }
2137
+ executionContext.codexManager = consumer.manager;
2138
+ }
1535
2139
  }
1536
2140
  await assertClaudeVersion({
1537
2141
  platforms: claudeCliRequired ? ['claude'] : [],
@@ -1548,13 +2152,30 @@ export async function runRebuildCommand(options) {
1548
2152
  const lockedCollisionSelections = [];
1549
2153
  for (const { entry, platform } of executionSelections) {
1550
2154
  const planned = lockedPlan.targets.find((candidate) => candidate.pluginId === entry.pluginId && candidate.platform === platform);
2155
+ const desiredCleanup = platform === 'codex'
2156
+ && options.command === 'cleanup'
2157
+ && lockedCodexPlans.some((plan) => plan.desiredTargets.some((target) => target.pluginId === entry.pluginId && target.scopes.includes(planned.resolvedScope)));
2158
+ const reconcileObsolete = platform === 'codex'
2159
+ && options.runtime?.reconcileIntentOnly === true
2160
+ && lockedCodexPlans.some((plan) => plan.desiredTargets.some((target) => target.pluginId === entry.pluginId && !target.scopes.includes(planned.resolvedScope)));
2161
+ if (reconcileObsolete)
2162
+ continue;
1551
2163
  if (planned.action !== 'install'
1552
2164
  && planned.action !== 'upgrade'
1553
- && planned.action !== 'reinstall')
2165
+ && planned.action !== 'reinstall'
2166
+ && planned.action !== 'move'
2167
+ && !desiredCleanup)
1554
2168
  continue;
2169
+ const buildCommand = desiredCleanup
2170
+ ? 'install'
2171
+ : planned.action === 'move'
2172
+ ? 'install'
2173
+ : planned.relation === 'dependency'
2174
+ ? planned.action
2175
+ : options.command;
1555
2176
  const marketplaceName = marketplaceNameFor(entry, platform, planned.resolvedScope);
1556
2177
  try {
1557
- const target = await executionIntegration.buildTarget({
2178
+ const target = await measureCommandPerformanceStage(options.runtime, 'target.build', () => executionIntegration.buildTarget({
1558
2179
  sourceId: entry.source.sourceId,
1559
2180
  sourceIdentity: entry.source.sourceIdentity,
1560
2181
  pluginId: entry.pluginId,
@@ -1564,21 +2185,27 @@ export async function runRebuildCommand(options) {
1564
2185
  platform,
1565
2186
  scope: planned.resolvedScope,
1566
2187
  cwd: options.commandOptions.cwd,
1567
- command: planned.relation === 'dependency' ? planned.action : options.command,
2188
+ command: buildCommand,
1568
2189
  catalog: toCatalogResolution(entry.source),
1569
2190
  includeHistoricalLifecycleArtifacts: true,
1570
- });
2191
+ }));
2192
+ if (platform === 'codex') {
2193
+ const plannedNative = lockedCodexPlans.flatMap((plan) => plan.sharedNativeActions).find((action) => action.pluginId === entry.pluginId);
2194
+ if (plannedNative) {
2195
+ target.writeNativeRequired = plannedNative.action === 'install'
2196
+ || plannedNative.action === 'replace'
2197
+ || plannedNative.action === 'repair';
2198
+ }
2199
+ }
1571
2200
  preparedTargets.set(`${entry.pluginId}\0${platform}`, {
1572
2201
  target,
1573
2202
  driver: executionIntegration.driver,
1574
2203
  context: executionContext,
1575
2204
  });
1576
- if (selectedIds.has(entry.pluginId)) {
1577
- await rebuildCommandTestHooks?.afterTargetBuilt?.({
1578
- command: options.command,
1579
- identity: target.identity,
1580
- });
1581
- }
2205
+ await rebuildCommandTestHooks?.afterTargetBuilt?.({
2206
+ command: options.command,
2207
+ identity: target.identity,
2208
+ });
1582
2209
  lockedCollisionSelections.push({
1583
2210
  pluginId: entry.pluginId,
1584
2211
  targetKey: `${platform}/${target.identity.scope}`,
@@ -1601,7 +2228,9 @@ export async function runRebuildCommand(options) {
1601
2228
  throw namespacedPreflightError(`artifactCollision: ${lockedCollisions.map((collision) => `${collision.targetKey} (${collision.pluginIds.join(', ')})`).join('; ')}`, preflightTargets);
1602
2229
  }
1603
2230
  const removalRequiredConsumers = new Map();
1604
- if ((options.command === 'cleanup' && !cleanupForce) || options.command === 'uninstall') {
2231
+ if ((options.command === 'cleanup' && !cleanupForce)
2232
+ || options.command === 'uninstall'
2233
+ || options.runtime?.reconcileIntentOnly === true) {
1605
2234
  const removalStateInventory = await readInventory({
1606
2235
  cwd: options.commandOptions.cwd,
1607
2236
  env: runtimeEnv,
@@ -1745,6 +2374,32 @@ export async function runRebuildCommand(options) {
1745
2374
  scope: planned.resolvedScope,
1746
2375
  cwd: options.commandOptions.cwd,
1747
2376
  };
2377
+ const codexReconcilePlan = platform === 'codex'
2378
+ ? lockedCodexPlans.find((plan) => plan.desiredTargets.some((target) => target.pluginId === entry.pluginId))
2379
+ : undefined;
2380
+ const desiredInCurrentScope = codexReconcilePlan?.desiredTargets.some((target) => target.pluginId === entry.pluginId && target.scopes.includes(planned.resolvedScope)) === true;
2381
+ const reconcileObsolete = platform === 'codex'
2382
+ && options.runtime?.reconcileIntentOnly === true
2383
+ && !desiredInCurrentScope;
2384
+ const desiredCleanupMutation = codexReconcilePlan !== undefined && (codexReconcilePlan.sharedNativeActions.some((action) => action.pluginId === entry.pluginId
2385
+ && (action.action === 'install'
2386
+ || action.action === 'replace'
2387
+ || action.action === 'repair'))
2388
+ || [...codexReconcilePlan.activationActions.user,
2389
+ ...codexReconcilePlan.activationActions.project].some((action) => action.pluginId === entry.pluginId && action.action !== 'none')
2390
+ || [...codexReconcilePlan.extensionWrites,
2391
+ ...codexReconcilePlan.extensionRemovals].some((action) => action.pluginId === entry.pluginId));
2392
+ const plannedNativeAction = codexReconcilePlan?.sharedNativeActions.find((action) => action.pluginId === entry.pluginId);
2393
+ const hasCodexExtensionMutation = codexReconcilePlan !== undefined
2394
+ && [...codexReconcilePlan.extensionWrites, ...codexReconcilePlan.extensionRemovals]
2395
+ .some((action) => action.pluginId === entry.pluginId);
2396
+ const reconcileActivationOnly = platform === 'codex'
2397
+ && plannedNativeAction !== undefined
2398
+ && (plannedNativeAction.action === 'reuse'
2399
+ || plannedNativeAction.action === 'claim'
2400
+ || plannedNativeAction.action === 'none')
2401
+ && !hasCodexExtensionMutation
2402
+ && entry.plugin.manifest.scripts?.lifecycle?.uninstall === undefined;
1748
2403
  if (failedDependency || failedConsumer || planned.action === 'block') {
1749
2404
  const code = failedConsumer
1750
2405
  ? 'dependencyStillRequired'
@@ -1768,7 +2423,7 @@ export async function runRebuildCommand(options) {
1768
2423
  errorCode: code,
1769
2424
  message,
1770
2425
  }],
1771
- overallState: 'failed', nativePlugins: {}, repoManagedArtifacts: [],
2426
+ overallState: 'failed',
1772
2427
  receiptTargets: [{
1773
2428
  pluginName: identity.pluginName, pluginId: identity.pluginId,
1774
2429
  sourceId: identity.sourceId, platform, scope: planned.resolvedScope,
@@ -1777,7 +2432,51 @@ export async function runRebuildCommand(options) {
1777
2432
  dependencyStates.set(`${entry.pluginId}\0${platform}`, failedDependency || failedConsumer ? 'skipped' : 'failed');
1778
2433
  continue;
1779
2434
  }
1780
- if (planned.action === 'keep') {
2435
+ if (reconcileActivationOnly) {
2436
+ const preparedError = preparedTargetErrors.get(`${entry.pluginId}\0${platform}`);
2437
+ if (preparedError) {
2438
+ const message = preparedError instanceof Error
2439
+ ? preparedError.message
2440
+ : String(preparedError);
2441
+ const result = failedResult(options.command, identity, message);
2442
+ results.push({
2443
+ platforms: [platform], plugins: [], warnings: [], rebuildResults: [result],
2444
+ targetResults: [{
2445
+ target: identity,
2446
+ action: planned.action,
2447
+ state: 'failed',
2448
+ errorCode: message.split(':', 1)[0] ?? 'targetPreparationFailed',
2449
+ message,
2450
+ }],
2451
+ overallState: 'failed',
2452
+ receiptTargets: [{
2453
+ pluginName: identity.pluginName, pluginId: identity.pluginId,
2454
+ sourceId: identity.sourceId, platform, scope: planned.resolvedScope,
2455
+ }],
2456
+ });
2457
+ dependencyStates.set(`${entry.pluginId}\0${platform}`, 'failed');
2458
+ continue;
2459
+ }
2460
+ const result = {
2461
+ status: 'succeeded', command: options.command, target: identity, actions: [],
2462
+ cleanupAttempted: false, removed: [], absent: [], residual: [], quarantined: [], failures: [],
2463
+ };
2464
+ results.push({
2465
+ platforms: [platform], plugins: [identity.pluginName], warnings: [], rebuildResults: [result],
2466
+ targetResults: [{ target: identity, action: planned.action, state: 'completed' }],
2467
+ overallState: 'succeeded',
2468
+ receiptTargets: [{
2469
+ pluginName: identity.pluginName, pluginId: identity.pluginId,
2470
+ sourceId: identity.sourceId, platform, scope: planned.resolvedScope,
2471
+ }],
2472
+ });
2473
+ dependencyStates.set(`${entry.pluginId}\0${platform}`, 'completed');
2474
+ continue;
2475
+ }
2476
+ if (platform === 'codex'
2477
+ && desiredInCurrentScope
2478
+ && (options.command === 'uninstall'
2479
+ || (options.command === 'cleanup' && !desiredCleanupMutation))) {
1781
2480
  const result = {
1782
2481
  status: 'succeeded', command: options.command, target: identity, actions: [],
1783
2482
  cleanupAttempted: false, removed: [], absent: [], residual: [], quarantined: [], failures: [],
@@ -1785,7 +2484,24 @@ export async function runRebuildCommand(options) {
1785
2484
  results.push({
1786
2485
  platforms: [platform], plugins: [identity.pluginName], warnings: [], rebuildResults: [result],
1787
2486
  targetResults: [{ target: identity, action: 'keep', state: 'completed' }],
1788
- overallState: 'succeeded', nativePlugins: {}, repoManagedArtifacts: [],
2487
+ overallState: 'succeeded',
2488
+ receiptTargets: [{
2489
+ pluginName: identity.pluginName, pluginId: identity.pluginId,
2490
+ sourceId: identity.sourceId, platform, scope: planned.resolvedScope,
2491
+ }],
2492
+ });
2493
+ dependencyStates.set(`${entry.pluginId}\0${platform}`, 'kept');
2494
+ continue;
2495
+ }
2496
+ if (planned.action === 'keep' && !reconcileObsolete) {
2497
+ const result = {
2498
+ status: 'succeeded', command: options.command, target: identity, actions: [],
2499
+ cleanupAttempted: false, removed: [], absent: [], residual: [], quarantined: [], failures: [],
2500
+ };
2501
+ results.push({
2502
+ platforms: [platform], plugins: [identity.pluginName], warnings: [], rebuildResults: [result],
2503
+ targetResults: [{ target: identity, action: 'keep', state: 'completed' }],
2504
+ overallState: 'succeeded',
1789
2505
  receiptTargets: [{
1790
2506
  pluginName: identity.pluginName, pluginId: identity.pluginId,
1791
2507
  sourceId: identity.sourceId, platform, scope: planned.resolvedScope,
@@ -1802,11 +2518,20 @@ export async function runRebuildCommand(options) {
1802
2518
  marketplaceRoot: path.join(resolveAwHome(runtimeEnv), 'marketplaces', entry.source.sourceId),
1803
2519
  };
1804
2520
  entry.plugin.marketplaceName = marketplaceName;
2521
+ const reconcileDesiredCleanup = platform === 'codex'
2522
+ && options.command === 'cleanup'
2523
+ && desiredInCurrentScope;
1805
2524
  const executionResult = await runSingleSourceRebuildCommand({
1806
2525
  ...options,
1807
- command: planned.relation === 'dependency'
1808
- ? planned.action
1809
- : options.command,
2526
+ command: reconcileObsolete
2527
+ ? 'cleanup'
2528
+ : reconcileDesiredCleanup
2529
+ ? 'install'
2530
+ : planned.action === 'move'
2531
+ ? 'install'
2532
+ : planned.relation === 'dependency'
2533
+ ? planned.action
2534
+ : options.command,
1810
2535
  pluginNames: [entry.plugin.manifest.name],
1811
2536
  all: false,
1812
2537
  commandOptions: { ...options.commandOptions, platform, scope: planned.resolvedScope },
@@ -1842,11 +2567,115 @@ export async function runRebuildCommand(options) {
1842
2567
  },
1843
2568
  },
1844
2569
  });
2570
+ if (reconcileDesiredCleanup) {
2571
+ for (const result of executionResult.rebuildResults)
2572
+ result.command = 'cleanup';
2573
+ for (const result of executionResult.targetResults)
2574
+ result.action = 'cleanup';
2575
+ }
1845
2576
  results.push(executionResult);
1846
2577
  dependencyStates.set(`${entry.pluginId}\0${platform}`, executionResult.overallState === 'succeeded' ? 'completed' : 'failed');
1847
2578
  }
2579
+ if (lockedCodexPlans.length > 0
2580
+ && mergeRebuildCommandResults(results).overallState === 'succeeded') {
2581
+ try {
2582
+ await executeCodexReconcileActivations({
2583
+ plans: lockedCodexPlans,
2584
+ cwd: options.commandOptions.cwd,
2585
+ env: runtimeEnv,
2586
+ manager: executionContext.codexManager,
2587
+ runner: executionContext.nativeCliRunner,
2588
+ });
2589
+ }
2590
+ catch (error) {
2591
+ const selection = executionSelections.find((candidate) => candidate.platform === 'codex');
2592
+ if (!selection)
2593
+ throw error;
2594
+ const planned = lockedPlan.targets.find((target) => target.pluginId === selection.entry.pluginId && target.platform === 'codex');
2595
+ const identity = {
2596
+ sourceId: selection.entry.source.sourceId,
2597
+ sourceIdentity: selection.entry.source.sourceIdentity,
2598
+ pluginId: selection.entry.pluginId,
2599
+ pluginName: selection.entry.plugin.manifest.name,
2600
+ pluginVersion: selection.entry.plugin.manifest.version,
2601
+ platform: 'codex',
2602
+ scope: planned.resolvedScope,
2603
+ cwd: options.commandOptions.cwd,
2604
+ };
2605
+ const message = error instanceof Error ? error.message : String(error);
2606
+ const failed = failedResult(options.command, identity, message, 'postcheck.inspect');
2607
+ results.push({
2608
+ platforms: ['codex'],
2609
+ plugins: [],
2610
+ warnings: [],
2611
+ rebuildResults: [failed],
2612
+ targetResults: [{
2613
+ target: identity,
2614
+ action: options.command,
2615
+ state: 'failed',
2616
+ errorCode: message.split(':', 1)[0] ?? 'codexActivationPostcheckFailed',
2617
+ message,
2618
+ }],
2619
+ overallState: 'failed',
2620
+ receiptTargets: [{
2621
+ pluginName: identity.pluginName,
2622
+ pluginId: identity.pluginId,
2623
+ sourceId: identity.sourceId,
2624
+ platform: 'codex',
2625
+ scope: identity.scope,
2626
+ }],
2627
+ });
2628
+ }
2629
+ }
2630
+ if (options.command === 'install'
2631
+ && mergeRebuildCommandResults(results).overallState === 'succeeded') {
2632
+ for (const { entry, platform } of executionSelections) {
2633
+ const planned = lockedPlan.targets.find((target) => target.pluginId === entry.pluginId && target.platform === platform);
2634
+ if (planned.action !== 'move'
2635
+ || dependencyStates.get(`${entry.pluginId}\0${platform}`) !== 'completed')
2636
+ continue;
2637
+ const previousScope = planned.resolvedScope === 'user' ? 'project' : 'user';
2638
+ const marketplaceName = marketplaceNameFor(entry, platform, previousScope);
2639
+ entry.plugin.marketplaceName = marketplaceName;
2640
+ const migration = await runSingleSourceRebuildCommand({
2641
+ command: 'cleanup',
2642
+ pluginNames: [entry.plugin.manifest.name],
2643
+ commandOptions: { ...options.commandOptions, platform, scope: previousScope },
2644
+ runtime: {
2645
+ ...options.runtime,
2646
+ env: runtimeEnv,
2647
+ ...(executionContext.codexManager
2648
+ ? { codexManager: executionContext.codexManager }
2649
+ : {}),
2650
+ catalogResolution: toCatalogResolution(entry.source),
2651
+ resolvedCatalogPlugins: available
2652
+ .filter((candidate) => candidate.source.sourceId === entry.source.sourceId)
2653
+ .map((candidate) => candidate.plugin),
2654
+ namespacedSource: {
2655
+ sourceId: entry.source.sourceId,
2656
+ sourceIdentity: entry.source.sourceIdentity,
2657
+ marketplaceName,
2658
+ marketplaceRoot: path.join(resolveAwHome(runtimeEnv), 'marketplaces', entry.source.sourceId),
2659
+ },
2660
+ dependencyReviewPreflightHandled: true,
2661
+ bypassDependencyPlan: true,
2662
+ },
2663
+ });
2664
+ results.push(migration);
2665
+ if (migration.overallState !== 'succeeded') {
2666
+ dependencyStates.set(`${entry.pluginId}\0${platform}`, 'failed');
2667
+ }
2668
+ }
2669
+ }
1848
2670
  const merged = mergeRebuildCommandResults(results);
1849
2671
  merged.dependencyPlan = lockedPlan;
2672
+ if (lockedCodexPlans.length > 0)
2673
+ merged.codexReconcilePlans = lockedCodexPlans;
2674
+ if (desiredIntent && merged.overallState === 'succeeded') {
2675
+ for (const intent of desiredIntent) {
2676
+ await writePluginIntentState(intent.location, convergePluginIntentManagedCodexOwnership(intent.superset, intent.convergedCodexPluginIds));
2677
+ }
2678
+ }
1850
2679
  return leaseSafeRebuildResult(merged);
1851
2680
  },
1852
2681
  });
@@ -1865,54 +2694,16 @@ export async function runRebuildCommand(options) {
1865
2694
  const projected = error;
1866
2695
  error = namespacedPreflightError(error instanceof Error ? error.message : String(error), projected.namespacedTargets ?? liveTargets, projected.legacyTargets ?? legacyLiveTargets);
1867
2696
  }
1868
- if (options.command !== 'cleanup' && options.command !== 'uninstall')
2697
+ if (options.command !== 'uninstall')
1869
2698
  throw error;
1870
2699
  const message = error instanceof Error ? error.message : String(error);
1871
- const catalogUnavailable = message.startsWith('Cannot resolve required catalog sources:')
1872
- || message.includes('does not exist in the required catalog source set')
1873
- || (message.includes('Catalog source')
1874
- && (message.includes('does not exist')
1875
- || message.includes('is not configured')
1876
- || message.includes('has no Active generation')));
1877
- if (options.command === 'cleanup'
1878
- && (catalogUnavailable || message.includes('sourceChanged'))
1879
- && cleanupInventory
1880
- && cleanupLiveTargets.length > 0) {
1881
- if (!cleanupForce || expectedOfflineCleanup) {
1882
- await assertOfflineCleanupDependents({
1883
- targets: cleanupLiveTargets,
1884
- cwd: options.commandOptions.cwd,
1885
- env: runtimeEnv,
1886
- readInventory,
1887
- });
1888
- }
1889
- if (options.runtime?.previewDependencyPlanOnly) {
1890
- return previewOfflineOwnedComponents({
1891
- targets: cleanupLiveTargets,
1892
- cwd: options.commandOptions.cwd,
1893
- env: runtimeEnv,
1894
- nativeCliRunner: options.runtime.nativeCliRunner,
1895
- codexManagerPreviewer: options.runtime.codexManagerPreviewer,
1896
- onPreviewStage: options.runtime.onPreviewStage,
1897
- });
1898
- }
1899
- return cleanupOfflineOwnedComponents({
1900
- targets: cleanupLiveTargets,
1901
- cwd: options.commandOptions.cwd,
1902
- env: runtimeEnv,
1903
- force: cleanupForce && !expectedOfflineCleanup,
1904
- removeObsoleteInstallationContracts: options.runtime?.removeObsoleteInstallationContracts,
1905
- nativeCliRunner: options.runtime?.nativeCliRunner,
1906
- codexManager: options.runtime?.codexManager,
1907
- codexManagerResolver: options.runtime?.codexManagerResolver,
1908
- managedCodexAuthorizer: options.runtime?.managedCodexAuthorizer,
1909
- corruptExclusiveDirectoryOperations: options.runtime?.corruptExclusiveDirectoryOperations,
1910
- });
1911
- }
2700
+ const catalogUnavailable = isOfflineCleanupCatalogGenerationError(error)
2701
+ || message.startsWith('Cannot resolve required catalog sources:')
2702
+ || message.includes('does not exist in the required catalog source set');
1912
2703
  if (options.command === 'uninstall' && catalogUnavailable) {
1913
2704
  throw namespacedPreflightError(`catalogSourceUnavailable: ${message} Restore the source to run lifecycle uninstall, or use cleanup; use cleanup --force only for exclusively owned damaged output.`, liveTargets, legacyLiveTargets);
1914
2705
  }
1915
- if ((message.includes('Catalog source') && message.includes('does not exist'))
2706
+ if (catalogGenerationErrorCode(error) === 'catalogGenerationSourceMissing'
1916
2707
  || message.includes('does not exist in the required catalog source set')) {
1917
2708
  throw namespacedPreflightError(`sourceMissing: ${message} Run cleanup for owner recovery, or cleanup --force for an exclusively owned damaged target.`, liveTargets, legacyLiveTargets);
1918
2709
  }