@lesliechan721/aw 0.1.0-beta.10 → 0.1.0-beta.11

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 (218) hide show
  1. package/dist/authoring/assets/aw-config.d.mts +1 -50
  2. package/dist/authoring/assets/aw-config.mjs +58 -928
  3. package/dist/authoring/assets/aw-config.mjs.map +1 -1
  4. package/dist/authoring/scripts/init-plugin.mjs +28 -33
  5. package/dist/authoring/scripts/init-plugin.mjs.map +1 -1
  6. package/dist/authoring/scripts/lib/plugin-version-check.d.mts +1 -0
  7. package/dist/authoring/scripts/lib/plugin-version-check.mjs +229 -8
  8. package/dist/authoring/scripts/lib/plugin-version-check.mjs.map +1 -1
  9. package/dist/authoring/scripts/validate-plugin.mjs +165 -45
  10. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
  11. package/dist/scripts/bundle-plugins.js +18 -4
  12. package/dist/scripts/bundle-plugins.js.map +1 -1
  13. package/dist/scripts/test-plugin-scripts.d.ts +2 -0
  14. package/dist/scripts/test-plugin-scripts.js +21 -5
  15. package/dist/scripts/test-plugin-scripts.js.map +1 -1
  16. package/dist/src/commands/aw-capabilities.d.ts +1 -1
  17. package/dist/src/commands/aw-capabilities.js +1 -1
  18. package/dist/src/commands/aw-capabilities.js.map +1 -1
  19. package/dist/src/commands/catalog-dev-source.d.ts +6 -3
  20. package/dist/src/commands/catalog-dev-source.js +96 -35
  21. package/dist/src/commands/catalog-dev-source.js.map +1 -1
  22. package/dist/src/commands/catalog-publication-inspection.d.ts +1 -1
  23. package/dist/src/commands/catalog-publication-inspection.js +54 -8
  24. package/dist/src/commands/catalog-publication-inspection.js.map +1 -1
  25. package/dist/src/commands/catalog-publication-journal.js +1 -1
  26. package/dist/src/commands/catalog-publication-journal.js.map +1 -1
  27. package/dist/src/commands/catalog-source.d.ts +19 -4
  28. package/dist/src/commands/catalog-source.js +121 -12
  29. package/dist/src/commands/catalog-source.js.map +1 -1
  30. package/dist/src/commands/catalog-store.d.ts +26 -7
  31. package/dist/src/commands/catalog-store.js +361 -42
  32. package/dist/src/commands/catalog-store.js.map +1 -1
  33. package/dist/src/commands/console.js +28 -17
  34. package/dist/src/commands/console.js.map +1 -1
  35. package/dist/src/commands/operation-plan.d.ts +2 -1
  36. package/dist/src/commands/operation-plan.js +24 -0
  37. package/dist/src/commands/operation-plan.js.map +1 -1
  38. package/dist/src/commands/operation-planner.d.ts +2 -2
  39. package/dist/src/commands/operation-planner.js +11 -19
  40. package/dist/src/commands/operation-planner.js.map +1 -1
  41. package/dist/src/commands/options.d.ts +2 -2
  42. package/dist/src/commands/options.js +18 -4
  43. package/dist/src/commands/options.js.map +1 -1
  44. package/dist/src/commands/plugin-capabilities.js +2 -2
  45. package/dist/src/commands/plugin-capabilities.js.map +1 -1
  46. package/dist/src/commands/rebuild-command.d.ts +7 -0
  47. package/dist/src/commands/rebuild-command.js +769 -279
  48. package/dist/src/commands/rebuild-command.js.map +1 -1
  49. package/dist/src/commands/rebuild-integration.d.ts +24 -47
  50. package/dist/src/commands/rebuild-integration.js +1074 -1011
  51. package/dist/src/commands/rebuild-integration.js.map +1 -1
  52. package/dist/src/commands/rebuild-operations.d.ts +50 -12
  53. package/dist/src/commands/rebuild-operations.js +207 -101
  54. package/dist/src/commands/rebuild-operations.js.map +1 -1
  55. package/dist/src/commands/status-v6.d.ts +21 -2
  56. package/dist/src/commands/status-v6.js +163 -31
  57. package/dist/src/commands/status-v6.js.map +1 -1
  58. package/dist/src/commands/sync-command.d.ts +17 -0
  59. package/dist/src/commands/sync-command.js +45 -37
  60. package/dist/src/commands/sync-command.js.map +1 -1
  61. package/dist/src/commands/write-commands.d.ts +5 -0
  62. package/dist/src/commands/write-commands.js +50 -3
  63. package/dist/src/commands/write-commands.js.map +1 -1
  64. package/dist/src/generators/local-plugin.js +14 -4
  65. package/dist/src/generators/local-plugin.js.map +1 -1
  66. package/dist/src/index.js +216 -85
  67. package/dist/src/index.js.map +1 -1
  68. package/dist/src/output/command-receipt.js +1 -3
  69. package/dist/src/output/command-receipt.js.map +1 -1
  70. package/dist/src/output/rebuild-progress.js +1 -3
  71. package/dist/src/output/rebuild-progress.js.map +1 -1
  72. package/dist/src/portable/materializer.js +6 -6
  73. package/dist/src/portable/materializer.js.map +1 -1
  74. package/dist/src/tui/catalog-generation.js +41 -1
  75. package/dist/src/tui/catalog-generation.js.map +1 -1
  76. package/dist/src/tui/catalog-sources/session.d.ts +1 -0
  77. package/dist/src/tui/catalog-sources/session.js +16 -3
  78. package/dist/src/tui/catalog-sources/session.js.map +1 -1
  79. package/dist/src/tui/catalog-sources/types.d.ts +3 -2
  80. package/dist/src/tui/components.d.ts +4 -0
  81. package/dist/src/tui/components.js +36 -24
  82. package/dist/src/tui/components.js.map +1 -1
  83. package/dist/src/tui/console-app-v4.d.ts +13 -4
  84. package/dist/src/tui/console-app-v4.js +2067 -839
  85. package/dist/src/tui/console-app-v4.js.map +1 -1
  86. package/dist/src/tui/controller-runtime.js +585 -33
  87. package/dist/src/tui/controller-runtime.js.map +1 -1
  88. package/dist/src/tui/controller.d.ts +152 -6
  89. package/dist/src/tui/controller.js +1168 -156
  90. package/dist/src/tui/controller.js.map +1 -1
  91. package/dist/src/tui/diagnostic-projection.d.ts +22 -0
  92. package/dist/src/tui/diagnostic-projection.js +437 -0
  93. package/dist/src/tui/diagnostic-projection.js.map +1 -0
  94. package/dist/src/tui/diagnostics.d.ts +1 -1
  95. package/dist/src/tui/diagnostics.js +6 -1
  96. package/dist/src/tui/diagnostics.js.map +1 -1
  97. package/dist/src/tui/glossary.d.ts +23 -0
  98. package/dist/src/tui/glossary.js +42 -0
  99. package/dist/src/tui/glossary.js.map +1 -0
  100. package/dist/src/tui/index.d.ts +4 -1
  101. package/dist/src/tui/index.js +4 -1
  102. package/dist/src/tui/index.js.map +1 -1
  103. package/dist/src/tui/keys.d.ts +44 -0
  104. package/dist/src/tui/keys.js +43 -15
  105. package/dist/src/tui/keys.js.map +1 -1
  106. package/dist/src/tui/lifecycle-action-view.d.ts +6 -0
  107. package/dist/src/tui/lifecycle-action-view.js +34 -0
  108. package/dist/src/tui/lifecycle-action-view.js.map +1 -0
  109. package/dist/src/tui/model.d.ts +12 -2
  110. package/dist/src/tui/model.js +128 -52
  111. package/dist/src/tui/model.js.map +1 -1
  112. package/dist/src/tui/operation-diagnostic-screens.d.ts +21 -0
  113. package/dist/src/tui/operation-diagnostic-screens.js +101 -0
  114. package/dist/src/tui/operation-diagnostic-screens.js.map +1 -0
  115. package/dist/src/tui/operation-history.d.ts +80 -0
  116. package/dist/src/tui/operation-history.js +130 -0
  117. package/dist/src/tui/operation-history.js.map +1 -0
  118. package/dist/src/tui/operation-review-planner.js +306 -86
  119. package/dist/src/tui/operation-review-planner.js.map +1 -1
  120. package/dist/src/tui/router.d.ts +82 -17
  121. package/dist/src/tui/router.js +186 -38
  122. package/dist/src/tui/router.js.map +1 -1
  123. package/dist/src/tui/screen-registry.d.ts +546 -0
  124. package/dist/src/tui/screen-registry.js +179 -0
  125. package/dist/src/tui/screen-registry.js.map +1 -0
  126. package/dist/src/tui/session-console-app.d.ts +6 -0
  127. package/dist/src/tui/session-console-app.js +13 -0
  128. package/dist/src/tui/session-console-app.js.map +1 -0
  129. package/dist/src/tui/session-host.d.ts +44 -0
  130. package/dist/src/tui/session-host.js +197 -0
  131. package/dist/src/tui/session-host.js.map +1 -0
  132. package/dist/src/types.d.ts +64 -3
  133. package/dist/src/types.js +1 -1
  134. package/dist/src/types.js.map +1 -1
  135. package/dist/src/utils/agent-version.js +1 -1
  136. package/dist/src/utils/agent-version.js.map +1 -1
  137. package/dist/src/utils/artifact-renderer.d.ts +2 -1
  138. package/dist/src/utils/artifact-renderer.js +52 -52
  139. package/dist/src/utils/artifact-renderer.js.map +1 -1
  140. package/dist/src/utils/bundle-layout.js +4 -4
  141. package/dist/src/utils/bundle-layout.js.map +1 -1
  142. package/dist/src/utils/catalog-capability-projection.d.ts +8 -0
  143. package/dist/src/utils/catalog-capability-projection.js +14 -0
  144. package/dist/src/utils/catalog-capability-projection.js.map +1 -0
  145. package/dist/src/utils/catalog-generation-resolver.d.ts +14 -2
  146. package/dist/src/utils/catalog-generation-resolver.js +49 -27
  147. package/dist/src/utils/catalog-generation-resolver.js.map +1 -1
  148. package/dist/src/utils/catalog-generation-service.d.ts +8 -1
  149. package/dist/src/utils/catalog-generation-service.js +81 -47
  150. package/dist/src/utils/catalog-generation-service.js.map +1 -1
  151. package/dist/src/utils/catalog-generation-store.d.ts +1 -0
  152. package/dist/src/utils/catalog-generation-store.js +60 -5
  153. package/dist/src/utils/catalog-generation-store.js.map +1 -1
  154. package/dist/src/utils/catalog-operation-context.js +1 -0
  155. package/dist/src/utils/catalog-operation-context.js.map +1 -1
  156. package/dist/src/utils/catalog-public-error.js +4 -0
  157. package/dist/src/utils/catalog-public-error.js.map +1 -1
  158. package/dist/src/utils/catalog-source-impact.d.ts +3 -2
  159. package/dist/src/utils/catalog-source-impact.js +142 -20
  160. package/dist/src/utils/catalog-source-impact.js.map +1 -1
  161. package/dist/src/utils/catalog-source-references.js +95 -9
  162. package/dist/src/utils/catalog-source-references.js.map +1 -1
  163. package/dist/src/utils/catalog-sources.d.ts +1 -1
  164. package/dist/src/utils/catalog-sources.js +19 -3
  165. package/dist/src/utils/catalog-sources.js.map +1 -1
  166. package/dist/src/utils/codex-manager.js +8 -0
  167. package/dist/src/utils/codex-manager.js.map +1 -1
  168. package/dist/src/utils/codex-native-observation.d.ts +34 -1
  169. package/dist/src/utils/codex-native-observation.js +132 -0
  170. package/dist/src/utils/codex-native-observation.js.map +1 -1
  171. package/dist/src/utils/codex-reconciler.d.ts +0 -2
  172. package/dist/src/utils/codex-reconciler.js.map +1 -1
  173. package/dist/src/utils/codex-shared-native.d.ts +2 -26
  174. package/dist/src/utils/codex-shared-native.js +8 -82
  175. package/dist/src/utils/codex-shared-native.js.map +1 -1
  176. package/dist/src/utils/git-catalog-provider.d.ts +3 -0
  177. package/dist/src/utils/git-catalog-provider.js +40 -1
  178. package/dist/src/utils/git-catalog-provider.js.map +1 -1
  179. package/dist/src/utils/local-marketplace.d.ts +1 -0
  180. package/dist/src/utils/local-marketplace.js +69 -73
  181. package/dist/src/utils/local-marketplace.js.map +1 -1
  182. package/dist/src/utils/multi-source-catalog.js +1 -0
  183. package/dist/src/utils/multi-source-catalog.js.map +1 -1
  184. package/dist/src/utils/native-cli.d.ts +9 -4
  185. package/dist/src/utils/native-cli.js +280 -34
  186. package/dist/src/utils/native-cli.js.map +1 -1
  187. package/dist/src/utils/path-catalog-provider.js +2 -0
  188. package/dist/src/utils/path-catalog-provider.js.map +1 -1
  189. package/dist/src/utils/plugin-config-write.d.ts +15 -13
  190. package/dist/src/utils/plugin-config-write.js +418 -198
  191. package/dist/src/utils/plugin-config-write.js.map +1 -1
  192. package/dist/src/utils/plugin-config.d.ts +48 -4
  193. package/dist/src/utils/plugin-config.js +289 -38
  194. package/dist/src/utils/plugin-config.js.map +1 -1
  195. package/dist/src/utils/plugin-scripts.d.ts +20 -12
  196. package/dist/src/utils/plugin-scripts.js +376 -109
  197. package/dist/src/utils/plugin-scripts.js.map +1 -1
  198. package/dist/src/utils/process-tree.d.ts +2 -2
  199. package/dist/src/utils/process-tree.js +1 -1
  200. package/dist/src/utils/process-tree.js.map +1 -1
  201. package/dist/src/utils/project-boundary.d.ts +11 -1
  202. package/dist/src/utils/project-boundary.js +17 -8
  203. package/dist/src/utils/project-boundary.js.map +1 -1
  204. package/dist/src/utils/safe-operation-diagnostic.d.ts +15 -0
  205. package/dist/src/utils/safe-operation-diagnostic.js +33 -0
  206. package/dist/src/utils/safe-operation-diagnostic.js.map +1 -0
  207. package/dist/src/utils/sensitive-content.d.ts +1 -0
  208. package/dist/src/utils/sensitive-content.js +15 -0
  209. package/dist/src/utils/sensitive-content.js.map +1 -0
  210. package/dist/src/utils/windows-claude-shim.d.ts +9 -0
  211. package/dist/src/utils/windows-claude-shim.js +141 -0
  212. package/dist/src/utils/windows-claude-shim.js.map +1 -0
  213. package/dist/vitest.config.js +91 -10
  214. package/dist/vitest.config.js.map +1 -1
  215. package/package.json +9 -9
  216. package/dist/authoring/templates/aw-config-runtime.d.mts +0 -2
  217. package/dist/authoring/templates/aw-config-runtime.mjs +0 -407
  218. package/dist/authoring/templates/aw-config-runtime.mjs.map +0 -1
@@ -1,5 +1,4 @@
1
1
  import crypto from 'node:crypto';
2
- import os from 'node:os';
3
2
  import path from 'node:path';
4
3
  import fs from 'fs-extra';
5
4
  import { generateAgentArtifacts } from '../generators/agents.js';
@@ -11,7 +10,7 @@ import { canonicalizePathThroughExistingAncestor } from '../utils/state-paths.js
11
10
  import { createCodexSkillExpectedTree, createCodexSkillStagingTreeContentDigest, } from '../utils/codex-skill-staging.js';
12
11
  import { createDynamicSkillExpectedTree, createDynamicSkillTreeContentDigest, readDynamicSkillTree, validateSkillDirectory, writeDynamicSkillTree, } from '../utils/dynamic-skill-tree.js';
13
12
  import { parseCodexSharedHooksFile, serializeTrackedHookGroupIdentity, } from '../utils/hooks-contract.js';
14
- import { buildCodexMarketplaceAddInvocation, buildCodexMarketplaceListInvocation, buildCodexPluginAddInvocation, buildCodexPluginListInvocation, buildCodexPluginListJsonInvocation, buildCodexProjectPluginListInvocations, buildCodexPluginRemoveInvocation, CodexProjectConfigNotEffectiveError, parseCodexPluginListJson, postcheckCodexProjectPluginActivation, runNativeCli, runNativeCliChecked, SYSTEM_CODEX_MANAGER, } from '../utils/native-cli.js';
13
+ import { buildCodexMarketplaceAddInvocation, buildCodexMarketplaceRemoveInvocation, buildCodexPluginAddInvocation, buildCodexPluginRemoveInvocation, CodexProjectConfigNotEffectiveError, runNativeCliChecked, SYSTEM_CODEX_MANAGER, } from '../utils/native-cli.js';
15
14
  import { OWNED_TEXT_HEADER_PREFIX, createSharedOwnerToken, createSkillOwnerMarkerContent, parseSharedOwnerToken, parseOwnedTextHeader, readSkillOwnerMarker, renderOwnedTextFile, inspectOwnedComponents, writeOwnedTextFile, } from '../utils/ownership.js';
16
15
  import { resolveArtifactRenderConfig, resolveArtifactRendererTarget, runArtifactRenderer, } from '../utils/artifact-renderer.js';
17
16
  import { createArtifactConfigDigest, validateArtifactPlan, } from '../utils/artifact-plan.js';
@@ -19,7 +18,8 @@ import { resolveAwDeliveredArtifactIds } from '../utils/artifact-request.js';
19
18
  import { createAwPluginRequirementsReceipt, } from '../utils/manifest.js';
20
19
  import { resolvePlatformEntries } from '../utils/platform-entries.js';
21
20
  import { resolveAwHome, resolveClaudePaths, resolveCodexPaths, resolveRepoRoot, getCodexHome, } from '../utils/paths.js';
22
- import { runPostApplyScript } from '../utils/plugin-scripts.js';
21
+ import { assertNoSensitiveConfigInFiles, collectSensitivePluginConfigValues, runPreApplyScript, } from '../utils/plugin-scripts.js';
22
+ import { findSensitiveTokenPath } from '../utils/sensitive-content.js';
23
23
  import { atomicRemoveConfigFile, atomicReplaceConfigFile, withConfigFileMutation, } from '../utils/plugin-config.js';
24
24
  import { readCatalogSourcesConfigSnapshot } from '../utils/catalog-sources.js';
25
25
  import { getNamespacedMarketplaceName, projectMarketplaceManifestForPlatform, readStrictNamespacedMarketplaceRoot, replayNamespacedCodexSkillOverlays, } from '../utils/local-marketplace.js';
@@ -28,8 +28,8 @@ import { readClaudeSettings, writeClaudeSettings } from '../adapters/helpers.js'
28
28
  import { analyzeCodexSurface } from '../utils/codex-surface-analysis.js';
29
29
  import { CODEX_NATIVE_RENDER_RECEIPT_PATH, codexNativeRenderProjectionsEqual, createCodexNativeRenderProjection, createCodexNativeRenderReceipt, parseCodexNativeRenderReceipt, serializeCodexNativeRenderReceipt, } from '../utils/codex-native-render-receipt.js';
30
30
  import { inspectCodexSharedNative, } from '../utils/codex-shared-native.js';
31
- import { calculateCodexNativeRenderPayloadDigest, calculateCodexSharedNativePayloadDigest, } from '../utils/catalog-digests.js';
32
- import { combineCodexNativePluginObservations, observeCodexNativePlugin, } from '../utils/codex-native-observation.js';
31
+ import { calculateCodexNativeRenderPayloadDigest } from '../utils/catalog-digests.js';
32
+ import { createCodexNativeObservationSession, } from '../utils/codex-native-observation.js';
33
33
  import { isSafeCodexMarketplaceRoot } from '../utils/codex-marketplace-path.js';
34
34
  import { assertCanonicalProjectManagedRoots } from '../utils/project-boundary.js';
35
35
  import { findCodexRoleNameConflict, resolveCodexRoleNamespace, } from '../utils/codex-roles.js';
@@ -48,13 +48,34 @@ export class CodexActivationPostcheckError extends Error {
48
48
  this.codexPluginIds = [...new Set(typeof codexPluginIds === 'string' ? [codexPluginIds] : codexPluginIds)];
49
49
  }
50
50
  }
51
+ function codexObservationScope(context, marketplaceName, fallbackScope) {
52
+ return context.codexObservationScopeByMarketplace?.get(marketplaceName)
53
+ ?? (fallbackScope === 'user' ? 'user' : 'both');
54
+ }
55
+ function codexMarketplaceFromPluginId(pluginId) {
56
+ return pluginId.slice(pluginId.lastIndexOf('@') + 1);
57
+ }
58
+ function requireCodexObservationCoverage(registration, scope, operation) {
59
+ if (!registration.observations.user
60
+ || (scope === 'both' && !registration.observations.project)) {
61
+ throw new Error(`codexNativeObservationUnavailable: ${operation} requires ${scope === 'user' ? 'neutral' : 'exact and neutral'} Codex plugin list evidence.`);
62
+ }
63
+ }
51
64
  function requireCodexManager(manager) {
52
65
  if (!manager) {
53
66
  throw new Error('codexManagerRequired: Codex native operation has no resolved manager.');
54
67
  }
55
68
  return manager;
56
69
  }
57
- export async function executeCodexReconcileActivations(options) {
70
+ async function runCodexMutationWithInvalidation(options) {
71
+ try {
72
+ return await options.mutation();
73
+ }
74
+ finally {
75
+ options.observationSession?.invalidate(options.marketplaceName);
76
+ }
77
+ }
78
+ async function executeCodexReconcileActivationsInSession(options) {
58
79
  const byScope = {
59
80
  user: { upserts: {}, removals: new Set() },
60
81
  project: { upserts: {}, removals: new Set() },
@@ -87,23 +108,35 @@ export async function executeCodexReconcileActivations(options) {
87
108
  }
88
109
  }
89
110
  }
111
+ const recordActivationMutations = (pluginIds) => {
112
+ if (!options.mutationMarketplaces)
113
+ return;
114
+ for (const pluginId of pluginIds) {
115
+ options.mutationMarketplaces.add(pluginId.slice(pluginId.lastIndexOf('@') + 1));
116
+ }
117
+ };
90
118
  const observedActivationPluginIds = [...new Set(options.plans.flatMap((plan) => plan.postcheck.support === 'observed'
91
119
  ? plan.postcheck.plugins.map((plugin) => plugin.codexPluginId)
92
120
  : []))];
93
- if (observedActivationPluginIds.length > 0) {
121
+ if (!options.preflightCompleted && observedActivationPluginIds.length > 0) {
94
122
  const manager = requireCodexManager(options.manager);
95
123
  for (const pluginId of observedActivationPluginIds) {
124
+ const marketplaceName = codexMarketplaceFromPluginId(pluginId);
125
+ const observationScope = options.observationScopeByMarketplace?.get(marketplaceName)
126
+ ?? (options.plans.some((plan) => plan.requirement.requiresProjectPluginContract)
127
+ ? 'both'
128
+ : 'user');
96
129
  const registration = await inspectCodexPluginRegistrationLive({
97
130
  pluginId,
98
131
  cwd: options.cwd,
99
132
  env: options.env,
100
133
  nativeCliRunner: options.runner,
101
134
  manager,
135
+ scope: observationScope,
136
+ observationSession: options.observationSession,
102
137
  });
103
138
  const observedState = requireObservedCodexNativeState(registration.observation, 'activation precheck');
104
- if (!registration.observations.user || !registration.observations.project) {
105
- throw new Error('codexNativeObservationUnavailable: activation precheck requires exact and neutral Codex plugin list evidence.');
106
- }
139
+ requireCodexObservationCoverage(registration, observationScope, 'activation precheck');
107
140
  if (observedState !== 'installed') {
108
141
  throw new Error('codexNativeObservationFailed: activation precheck did not observe the installed Codex plugin.');
109
142
  }
@@ -115,7 +148,9 @@ export async function executeCodexReconcileActivations(options) {
115
148
  if (Object.keys(enables).length === 0)
116
149
  continue;
117
150
  const configPath = resolveCodexPaths(options.cwd, scope, options.env).configTomlPath;
118
- await applyCodexConfigTransactionFile(configPath, { upsertPluginActivations: enables });
151
+ const update = await applyCodexConfigTransactionFile(configPath, { upsertPluginActivations: enables });
152
+ if (update.wrote)
153
+ recordActivationMutations(Object.keys(enables));
119
154
  const enabledConfig = await readCodexConfigFile(configPath);
120
155
  const missingEnables = Object.keys(enables).filter((pluginId) => enabledConfig.snapshot.plugins[pluginId]?.enabled !== true);
121
156
  if (missingEnables.length > 0) {
@@ -128,7 +163,7 @@ export async function executeCodexReconcileActivations(options) {
128
163
  .filter(([, desired]) => !desired));
129
164
  if (Object.keys(disables).length === 0 && transaction.removals.size === 0)
130
165
  continue;
131
- await applyCodexConfigTransactionFile(resolveCodexPaths(options.cwd, scope, options.env).configTomlPath, {
166
+ const update = await applyCodexConfigTransactionFile(resolveCodexPaths(options.cwd, scope, options.env).configTomlPath, {
132
167
  ...(transaction.removals.size > 0
133
168
  ? { removePluginActivations: [...transaction.removals].sort() }
134
169
  : {}),
@@ -136,6 +171,12 @@ export async function executeCodexReconcileActivations(options) {
136
171
  ? { upsertPluginActivations: disables }
137
172
  : {}),
138
173
  });
174
+ if (update.wrote) {
175
+ recordActivationMutations([
176
+ ...Object.keys(disables),
177
+ ...transaction.removals,
178
+ ]);
179
+ }
139
180
  }
140
181
  const [userConfig, projectConfig] = await Promise.all([
141
182
  readCodexConfigFile(resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath),
@@ -172,45 +213,87 @@ export async function executeCodexReconcileActivations(options) {
172
213
  : []);
173
214
  if (observedActivationChecks.length === 0)
174
215
  return;
175
- const manager = requireCodexManager(options.manager);
176
- const neutralCwd = await fs.mkdtemp(path.join(os.tmpdir(), 'aw-codex-postcheck-'));
216
+ const ineffectivePlugins = [];
217
+ const ineffectiveDetails = [];
218
+ for (const plugin of observedActivationChecks) {
219
+ const userActive = userConfig.snapshot.plugins[plugin.codexPluginId]?.enabled === true;
220
+ const projectActive = projectConfig.snapshot.plugins[plugin.codexPluginId]?.enabled === true;
221
+ if ((userActive || projectActive) !== (plugin.expectedUserEnabled || plugin.expectedProjectEnabled)
222
+ || userActive !== plugin.expectedUserEnabled) {
223
+ ineffectivePlugins.push(plugin.codexPluginId);
224
+ ineffectiveDetails.push(new CodexProjectConfigNotEffectiveError(plugin.codexPluginId).message);
225
+ }
226
+ }
227
+ if (ineffectivePlugins.length > 0) {
228
+ throw new CodexActivationPostcheckError(ineffectivePlugins, ineffectiveDetails.join(' '));
229
+ }
230
+ }
231
+ export async function executeCodexReconcileActivations(options) {
232
+ const ownedSession = options.observationSession ? undefined
233
+ : await createCodexNativeObservationSession({
234
+ exactCwd: options.cwd,
235
+ env: options.env,
236
+ runner: options.runner,
237
+ manager: options.manager,
238
+ });
177
239
  try {
178
- const invocations = buildCodexProjectPluginListInvocations(manager, {
240
+ await executeCodexReconcileActivationsInSession({
241
+ ...options,
242
+ observationSession: options.observationSession ?? ownedSession,
243
+ });
244
+ }
245
+ finally {
246
+ await ownedSession?.dispose();
247
+ }
248
+ }
249
+ export async function postcheckCodexReconcileActivations(options) {
250
+ const checks = options.plans.flatMap((plan) => plan.postcheck.support === 'observed' ? plan.postcheck.plugins : []);
251
+ if (checks.length === 0)
252
+ return;
253
+ const ownedSession = options.observationSession ? undefined
254
+ : await createCodexNativeObservationSession({
179
255
  exactCwd: options.cwd,
180
- neutralCwd,
181
256
  env: options.env,
257
+ runner: options.runner,
258
+ manager: options.manager,
182
259
  });
183
- const [exactResult, neutralResult] = await Promise.all([
184
- runNativeCliChecked(invocations.exact, options.runner),
185
- runNativeCliChecked(invocations.neutral, options.runner),
186
- ]);
187
- const ineffectivePlugins = [];
188
- const ineffectiveDetails = [];
189
- for (const plugin of observedActivationChecks) {
260
+ const session = options.observationSession ?? ownedSession;
261
+ try {
262
+ const failedPluginIds = [];
263
+ const failureDetails = [];
264
+ for (const check of checks) {
190
265
  try {
191
- postcheckCodexProjectPluginActivation({
192
- pluginId: plugin.codexPluginId,
193
- userEnabled: plugin.expectedUserEnabled,
194
- projectEnabled: plugin.expectedProjectEnabled,
195
- exactResult,
196
- neutralResult,
266
+ const marketplaceName = codexMarketplaceFromPluginId(check.codexPluginId);
267
+ const observationScope = options.observationScopeByMarketplace?.get(marketplaceName)
268
+ ?? (options.plans.some((plan) => plan.requirement.requiresProjectPluginContract)
269
+ ? 'both'
270
+ : 'user');
271
+ const registration = await inspectCodexPluginRegistrationLive({
272
+ pluginId: check.codexPluginId,
273
+ cwd: options.cwd,
274
+ env: options.env,
275
+ nativeCliRunner: options.runner,
276
+ manager: options.manager,
277
+ scope: observationScope,
278
+ observationSession: session,
197
279
  });
280
+ const state = requireObservedCodexNativeState(registration.observation, 'activation postcheck');
281
+ requireCodexObservationCoverage(registration, observationScope, 'activation postcheck');
282
+ if (state !== 'installed') {
283
+ throw new Error(`codexNativeObservationFailed: ${check.codexPluginId} was not observed after Codex activation mutation.`);
284
+ }
198
285
  }
199
286
  catch (error) {
200
- if (error instanceof CodexProjectConfigNotEffectiveError) {
201
- ineffectivePlugins.push(plugin.codexPluginId);
202
- ineffectiveDetails.push(error.message);
203
- continue;
204
- }
205
- throw error;
287
+ failedPluginIds.push(check.codexPluginId);
288
+ failureDetails.push(error instanceof Error ? error.message : String(error));
206
289
  }
207
290
  }
208
- if (ineffectivePlugins.length > 0) {
209
- throw new CodexActivationPostcheckError(ineffectivePlugins, ineffectiveDetails.join(' '));
291
+ if (failedPluginIds.length > 0) {
292
+ throw new CodexActivationPostcheckError(failedPluginIds, failureDetails.join(' '));
210
293
  }
211
294
  }
212
295
  finally {
213
- await fs.remove(neutralCwd);
296
+ await ownedSession?.dispose();
214
297
  }
215
298
  }
216
299
  const CODEX_REMOVE_RECOVERY = 'Codex residue was retained. Retry cleanup or uninstall, upgrade AW, or install a compatible official Codex CLI.';
@@ -718,7 +801,8 @@ async function scanOwnedSkills(options) {
718
801
  }));
719
802
  continue;
720
803
  }
721
- pending.push(skillRoot);
804
+ if (options.recursive !== false)
805
+ pending.push(skillRoot);
722
806
  }
723
807
  }
724
808
  return output;
@@ -845,8 +929,9 @@ async function resolveDynamicArtifacts(options) {
845
929
  platform: options.platform,
846
930
  scope: options.scope,
847
931
  });
848
- if (requestedArtifactIds.length === 0)
849
- return { artifacts: [], sourceDirs: () => [] };
932
+ if (requestedArtifactIds.length === 0) {
933
+ return { artifacts: [], sourceDirs: () => [], sensitiveValues: [] };
934
+ }
850
935
  const render = await runArtifactRenderer({
851
936
  event: options.event,
852
937
  plugin: options.plugin,
@@ -861,6 +946,7 @@ async function resolveDynamicArtifacts(options) {
861
946
  const config = await resolveArtifactRenderConfig({
862
947
  sourceId: options.plugin.sourceId,
863
948
  sourceIdentity: options.plugin.sourceIdentity,
949
+ logicalIdentity: options.plugin.catalogLogicalIdentity,
864
950
  pluginName: options.plugin.manifest.name,
865
951
  manifest: options.plugin.manifest,
866
952
  scope: options.scope,
@@ -880,6 +966,7 @@ async function resolveDynamicArtifacts(options) {
880
966
  return {
881
967
  artifacts,
882
968
  sourceDirs: (artifact) => render.context.artifactSources.find((entry) => entry.id === artifact.id && entry.target === artifact.target)?.sourceDirs ?? [],
969
+ sensitiveValues: collectSensitivePluginConfigValues(options.plugin.manifest, config.values),
883
970
  };
884
971
  }
885
972
  function dynamicSkillWrite(options) {
@@ -1053,6 +1140,7 @@ async function buildClaudeWrites(options) {
1053
1140
  requirementsReceipt: requirementsReceipt(options.plugin),
1054
1141
  sourceRequirementsReceiptPolicy: 'allow',
1055
1142
  });
1143
+ assertNoSensitiveConfigInFiles(tree.files, dynamic.sensitiveValues, 'artifactRendererSensitiveConfigExposed: Final dynamic Skill contains sensitive config.');
1056
1144
  writes.push(dynamicSkillWrite({
1057
1145
  ...options,
1058
1146
  platform: 'claude',
@@ -1129,6 +1217,7 @@ async function buildCodexWrites(options) {
1129
1217
  requirementsReceipt: requirementsReceipt(options.plugin),
1130
1218
  sourceRequirementsReceiptPolicy: 'allow',
1131
1219
  });
1220
+ assertNoSensitiveConfigInFiles(tree.files, dynamic.sensitiveValues, 'artifactRendererSensitiveConfigExposed: Final dynamic Skill contains sensitive config.');
1132
1221
  writes.push(dynamicSkillWrite({
1133
1222
  ...options,
1134
1223
  platform: 'codex',
@@ -1636,119 +1725,43 @@ async function buildHistoricalComponents(options) {
1636
1725
  }
1637
1726
  return components;
1638
1727
  }
1639
- function isAbsentCodexResiduePath(error) {
1728
+ function isAbsentPath(error) {
1640
1729
  const code = error.code;
1641
1730
  return code === 'ENOENT';
1642
1731
  }
1643
- async function lstatCodexResidueIfPresent(filePath) {
1732
+ async function lstatIfPresent(filePath) {
1644
1733
  return fs.lstat(filePath).catch((error) => {
1645
- if (isAbsentCodexResiduePath(error))
1734
+ if (isAbsentPath(error))
1646
1735
  return null;
1647
1736
  throw error;
1648
1737
  });
1649
1738
  }
1650
- async function readdirCodexResidueIfPresent(directory) {
1651
- const stats = await lstatCodexResidueIfPresent(directory);
1652
- if (!stats)
1653
- return [];
1654
- if (!stats.isDirectory() || stats.isSymbolicLink()) {
1655
- throw new Error(`Codex residue root is not a canonical directory: ${directory}`);
1656
- }
1657
- return fs.readdir(directory, { withFileTypes: true });
1658
- }
1659
- export async function inspectCodexNativeResidue(options) {
1660
- const marketplaceName = options.marketplaceName ?? MARKETPLACE_NAME;
1661
- const marketplaceRoot = options.marketplaceRoot ?? resolveAwHome(options.env);
1662
- const pluginId = `${options.pluginName}@${marketplaceName}`;
1663
- const configPath = resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath;
1664
- const config = await readCodexConfigFile(configPath);
1665
- const marketplace = config.snapshot.marketplaces[marketplaceName];
1666
- const canonicalMarketplace = marketplace?.sourceType === 'local'
1667
- && !marketplace.sourcePointsToManifest
1668
- && path.normalize(marketplace.source ?? '') === path.normalize(marketplaceRoot);
1669
- const cacheRoot = path.join(getCodexHome(options.env), 'plugins', 'cache', marketplaceName, options.pluginName);
1670
- const installedRoot = path.join(getCodexHome(options.env), 'plugins', pluginId);
1671
- const installedManifest = path.join(installedRoot, 'manifest.json');
1672
- const labels = [];
1673
- const foreignLabels = [];
1674
- const versions = new Set();
1675
- const installedVersions = new Set();
1676
- const cacheVersions = new Set();
1677
- const configEntry = config.snapshot.plugins[pluginId];
1678
- if (configEntry) {
1679
- (canonicalMarketplace ? labels : foreignLabels).push(`${configPath}#plugins.${pluginId}`);
1680
- }
1681
- const inspectManifest = async (manifestPath, kind, directoryVersion) => {
1682
- const stats = await lstatCodexResidueIfPresent(manifestPath);
1683
- if (!stats)
1684
- return;
1685
- if (!stats.isFile() || stats.isSymbolicLink()) {
1686
- foreignLabels.push(manifestPath);
1687
- return;
1688
- }
1689
- const manifest = await fs.readJson(manifestPath);
1690
- const identity = manifest?.id ?? manifest?.name;
1691
- const version = typeof manifest?.version === 'string' ? manifest.version : null;
1692
- if (!canonicalMarketplace
1693
- || (identity !== options.pluginName && identity !== pluginId)
1694
- || !version
1695
- || (directoryVersion !== undefined && version !== directoryVersion)) {
1696
- foreignLabels.push(manifestPath);
1697
- return;
1698
- }
1699
- labels.push(manifestPath);
1700
- versions.add(version);
1701
- (kind === 'installed' ? installedVersions : cacheVersions).add(version);
1702
- };
1703
- for (const entry of await readdirCodexResidueIfPresent(cacheRoot)) {
1704
- if (!entry.isDirectory() || entry.isSymbolicLink()) {
1705
- foreignLabels.push(path.join(cacheRoot, entry.name));
1706
- continue;
1707
- }
1708
- const versionRoot = path.join(cacheRoot, entry.name);
1709
- const candidates = [
1710
- path.join(versionRoot, 'plugin.json'),
1711
- path.join(versionRoot, '.codex-plugin', 'plugin.json'),
1712
- ];
1713
- const existing = [];
1714
- for (const candidate of candidates) {
1715
- if (await lstatCodexResidueIfPresent(candidate))
1716
- existing.push(candidate);
1717
- }
1718
- if (existing.length === 0)
1719
- foreignLabels.push(versionRoot);
1720
- for (const candidate of existing)
1721
- await inspectManifest(candidate, 'cache', entry.name);
1722
- }
1723
- const installedStats = await lstatCodexResidueIfPresent(installedRoot);
1724
- if (installedStats && (!installedStats.isDirectory() || installedStats.isSymbolicLink())) {
1725
- foreignLabels.push(installedRoot);
1726
- }
1727
- else if (installedStats) {
1728
- await inspectManifest(installedManifest, 'installed');
1729
- }
1730
- return foreignLabels.length > 0
1731
- ? {
1732
- state: 'foreign', labels: [...labels, ...foreignLabels], versions: [...versions],
1733
- installedVersions: [...installedVersions], cacheVersions: [...cacheVersions],
1734
- enabled: configEntry?.enabled === true,
1735
- }
1736
- : labels.length > 0
1737
- ? {
1738
- state: 'present', labels, versions: [...versions],
1739
- installedVersions: [...installedVersions], cacheVersions: [...cacheVersions],
1740
- enabled: configEntry?.enabled === true,
1741
- }
1742
- : {
1743
- state: 'absent', labels: [], versions: [],
1744
- installedVersions: [], cacheVersions: [], enabled: false,
1745
- };
1739
+ async function lstatCodexResidueIfPresent(filePath) {
1740
+ return fs.lstat(filePath).catch((error) => {
1741
+ if (isAbsentPath(error))
1742
+ return null;
1743
+ throw error;
1744
+ });
1746
1745
  }
1747
1746
  function isPathWithin(root, candidate) {
1748
1747
  const relative = path.relative(path.resolve(root), path.resolve(candidate));
1749
1748
  return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
1750
1749
  }
1751
- async function codexPayloadPathSafe(payloadRoot) {
1750
+ async function codexReadPathSafe(root, candidate) {
1751
+ if (!isPathWithin(root, candidate))
1752
+ return false;
1753
+ const rootStats = await lstatCodexResidueIfPresent(root);
1754
+ if (rootStats && (!rootStats.isDirectory() || rootStats.isSymbolicLink()))
1755
+ return false;
1756
+ const [physicalRoot, physicalCandidate] = await Promise.all([
1757
+ canonicalizePathThroughExistingAncestor(root),
1758
+ canonicalizePathThroughExistingAncestor(candidate),
1759
+ ]);
1760
+ return isPathWithin(physicalRoot, physicalCandidate);
1761
+ }
1762
+ async function codexPayloadPathSafe(payloadRoot, allowedRoot = payloadRoot) {
1763
+ if (!await codexReadPathSafe(allowedRoot, payloadRoot))
1764
+ return false;
1752
1765
  const rootStats = await lstatCodexResidueIfPresent(payloadRoot);
1753
1766
  if (!rootStats?.isDirectory() || rootStats.isSymbolicLink())
1754
1767
  return false;
@@ -1757,12 +1770,9 @@ async function codexPayloadPathSafe(payloadRoot) {
1757
1770
  const current = pending.pop();
1758
1771
  for (const entry of await fs.readdir(current, { withFileTypes: true })) {
1759
1772
  const entryPath = path.join(current, entry.name);
1760
- if (entry.isSymbolicLink()) {
1761
- const target = path.resolve(path.dirname(entryPath), await fs.readlink(entryPath));
1762
- if (!isPathWithin(payloadRoot, target))
1763
- return false;
1764
- }
1765
- else if (entry.isDirectory()) {
1773
+ if (entry.isSymbolicLink())
1774
+ return false;
1775
+ if (entry.isDirectory()) {
1766
1776
  pending.push(entryPath);
1767
1777
  }
1768
1778
  else if (!entry.isFile()) {
@@ -1772,6 +1782,23 @@ async function codexPayloadPathSafe(payloadRoot) {
1772
1782
  }
1773
1783
  return true;
1774
1784
  }
1785
+ async function resolveCodexSkillsRoot(payloadRoot, rawSkillsRoot) {
1786
+ const normalizedSeparators = rawSkillsRoot.replace(/\\/gu, '/');
1787
+ const relativeRoot = path.posix.normalize(normalizedSeparators.replace(/^\.\//u, ''));
1788
+ if (!relativeRoot
1789
+ || relativeRoot === '.'
1790
+ || relativeRoot === '..'
1791
+ || relativeRoot.startsWith('../')
1792
+ || path.posix.isAbsolute(normalizedSeparators)
1793
+ || path.win32.isAbsolute(rawSkillsRoot)) {
1794
+ throw new Error('codexNativeSkillsRootInvalid: Codex native Skills root is invalid.');
1795
+ }
1796
+ const root = path.join(payloadRoot, ...relativeRoot.split('/'));
1797
+ if (!await codexReadPathSafe(payloadRoot, root)) {
1798
+ throw new Error('codexNativeSkillsRootInvalid: Codex native Skills root is unsafe.');
1799
+ }
1800
+ return { relativeRoot, root };
1801
+ }
1775
1802
  function codexNativeRenderTargets(plugin) {
1776
1803
  return (plugin.manifest.artifacts?.targets ?? []).filter((target) => target.platform === 'codex'
1777
1804
  && target.target === 'codex.skill'
@@ -1779,7 +1806,7 @@ function codexNativeRenderTargets(plugin) {
1779
1806
  }
1780
1807
  async function readCodexNativeRenderReceiptEvidence(payloadRoot) {
1781
1808
  const receiptPath = path.join(payloadRoot, CODEX_NATIVE_RENDER_RECEIPT_PATH);
1782
- const stats = await lstatCodexResidueIfPresent(receiptPath);
1809
+ const stats = await lstatIfPresent(receiptPath);
1783
1810
  if (!stats)
1784
1811
  return { state: 'absent' };
1785
1812
  if (!stats.isFile() || stats.isSymbolicLink())
@@ -1801,6 +1828,7 @@ async function resolveCodexNativeRenderExpectedEvidence(options) {
1801
1828
  throw new Error('Codex native render projection requires namespaced source identity.');
1802
1829
  }
1803
1830
  const marketplace = await readStrictNamespacedMarketplaceRoot({
1831
+ trustedMarketplaceRoot: path.join(resolveAwHome(options.env), 'marketplaces'),
1804
1832
  rootDir: options.marketplaceRoot,
1805
1833
  sourceId: options.plugin.sourceId,
1806
1834
  marketplaceName: options.marketplaceName,
@@ -1808,7 +1836,13 @@ async function resolveCodexNativeRenderExpectedEvidence(options) {
1808
1836
  if (!marketplace) {
1809
1837
  throw new Error('Codex native render canonical marketplace generation is missing.');
1810
1838
  }
1839
+ if (marketplace.config.sourceIdentity !== options.plugin.sourceIdentity) {
1840
+ return { state: 'foreign' };
1841
+ }
1811
1842
  const payloadRoot = path.join(options.marketplaceRoot, 'plugins', options.plugin.manifest.name);
1843
+ if (!await codexPayloadPathSafe(payloadRoot, options.marketplaceRoot)) {
1844
+ throw new Error('Codex native render canonical marketplace payload path is unsafe.');
1845
+ }
1812
1846
  const receiptEvidence = await readCodexNativeRenderReceiptEvidence(payloadRoot);
1813
1847
  if (receiptEvidence.state !== 'present') {
1814
1848
  throw new Error('Codex native render canonical marketplace receipt is missing or unreadable.');
@@ -1824,7 +1858,7 @@ async function resolveCodexNativeRenderExpectedEvidence(options) {
1824
1858
  throw new Error('Codex native render canonical marketplace payload does not match its receipt.');
1825
1859
  }
1826
1860
  const sourceManifestPath = path.join(payloadRoot, '.agents-plugin', 'plugin.json');
1827
- const sourceManifestStats = await lstatCodexResidueIfPresent(sourceManifestPath);
1861
+ const sourceManifestStats = await lstatIfPresent(sourceManifestPath);
1828
1862
  const sourceManifest = sourceManifestStats?.isFile() && !sourceManifestStats.isSymbolicLink()
1829
1863
  ? await fs.readJson(sourceManifestPath).catch(() => null)
1830
1864
  : null;
@@ -1833,7 +1867,7 @@ async function resolveCodexNativeRenderExpectedEvidence(options) {
1833
1867
  throw new Error('Codex native render canonical marketplace plugin identity is invalid.');
1834
1868
  }
1835
1869
  const codexManifestPath = path.join(payloadRoot, '.codex-plugin', 'plugin.json');
1836
- const codexManifestStats = await lstatCodexResidueIfPresent(codexManifestPath);
1870
+ const codexManifestStats = await lstatIfPresent(codexManifestPath);
1837
1871
  const codexManifest = codexManifestStats?.isFile() && !codexManifestStats.isSymbolicLink()
1838
1872
  ? await fs.readJson(codexManifestPath).catch(() => null)
1839
1873
  : null;
@@ -1843,19 +1877,14 @@ async function resolveCodexNativeRenderExpectedEvidence(options) {
1843
1877
  const rawSkillsRoot = typeof codexManifest.skills === 'string' && codexManifest.skills.trim() !== ''
1844
1878
  ? codexManifest.skills
1845
1879
  : './codex/skills';
1846
- const skillsRelativeRoot = path.posix.normalize(rawSkillsRoot.replace(/^\.\//u, ''));
1847
- if (skillsRelativeRoot === '..'
1848
- || skillsRelativeRoot.startsWith('../')
1849
- || path.posix.isAbsolute(skillsRelativeRoot)) {
1850
- throw new Error('Codex native render canonical marketplace skills root is invalid.');
1851
- }
1880
+ const { relativeRoot: skillsRelativeRoot } = await resolveCodexSkillsRoot(payloadRoot, rawSkillsRoot);
1852
1881
  const emptyArtifactDigest = createCodexSkillStagingTreeContentDigest({
1853
1882
  skillKey: 'empty',
1854
1883
  files: {},
1855
1884
  });
1856
1885
  for (const receiptTarget of receipt.targets) {
1857
1886
  const skillRoot = path.join(payloadRoot, ...skillsRelativeRoot.split('/'), receiptTarget.skillKey);
1858
- if (await lstatCodexResidueIfPresent(path.join(skillRoot, '.agent-workflows-owner.json'))) {
1887
+ if (await lstatIfPresent(path.join(skillRoot, '.agent-workflows-owner.json'))) {
1859
1888
  throw new Error('Codex native render canonical marketplace Skill has an AW owner marker.');
1860
1889
  }
1861
1890
  const tree = await readDynamicSkillTree(skillRoot, 'codex.skill');
@@ -1871,7 +1900,7 @@ async function resolveCodexNativeRenderExpectedEvidence(options) {
1871
1900
  if (targets.length === 0
1872
1901
  || targets.length !== receipt.targets.length
1873
1902
  || targets.some((target) => receiptTargets.get(target.id)?.skillKey !== target.skillKey)) {
1874
- return { payloadDigest };
1903
+ return { state: 'present', payloadDigest, sourceProjection: 'drifted' };
1875
1904
  }
1876
1905
  const renderer = await resolveArtifactRendererTarget(options.plugin);
1877
1906
  if (renderer.noOp || !renderer.rendererDigest) {
@@ -1880,6 +1909,7 @@ async function resolveCodexNativeRenderExpectedEvidence(options) {
1880
1909
  const config = await resolveArtifactRenderConfig({
1881
1910
  sourceId: options.plugin.sourceId,
1882
1911
  sourceIdentity: options.plugin.sourceIdentity,
1912
+ logicalIdentity: options.plugin.catalogLogicalIdentity,
1883
1913
  pluginName: options.plugin.manifest.name,
1884
1914
  manifest: options.plugin.manifest,
1885
1915
  scope: 'user',
@@ -1911,10 +1941,38 @@ async function resolveCodexNativeRenderExpectedEvidence(options) {
1911
1941
  })),
1912
1942
  });
1913
1943
  return {
1944
+ state: 'present',
1914
1945
  projection,
1915
1946
  payloadDigest,
1947
+ sourceProjection: codexNativeRenderProjectionsEqual(receipt, projection)
1948
+ ? 'current'
1949
+ : 'drifted',
1916
1950
  };
1917
1951
  }
1952
+ async function assertNoSensitiveValuesInCanonicalCodexNativePayload(options) {
1953
+ if (options.sensitiveValues.length === 0
1954
+ || !pluginHasCodexNativeSkillSurface(options.plugin))
1955
+ return;
1956
+ const sourceId = options.plugin.sourceId;
1957
+ if (!sourceId) {
1958
+ throw new Error('Codex native sensitive scan requires namespaced source identity.');
1959
+ }
1960
+ const awHome = resolveAwHome(options.env);
1961
+ if (!await isSafeCodexMarketplaceRoot({
1962
+ awHome,
1963
+ sourceId,
1964
+ marketplaceRoot: options.marketplaceRoot,
1965
+ })) {
1966
+ throw new Error('Codex native sensitive scan canonical marketplace path is unsafe.');
1967
+ }
1968
+ const payloadRoot = path.join(options.marketplaceRoot, 'plugins', options.plugin.manifest.name);
1969
+ await assertNoSensitiveValuesInCodexNativePayload({
1970
+ plugin: options.plugin,
1971
+ payloadRoot,
1972
+ allowedRoot: options.marketplaceRoot,
1973
+ sensitiveValues: options.sensitiveValues,
1974
+ });
1975
+ }
1918
1976
  function observedCodexNativeState(observation, operation) {
1919
1977
  if (observation.state === 'observed') {
1920
1978
  return observation.value === 'installed' ? 'present' : 'absent';
@@ -1939,47 +1997,32 @@ export async function inspectCodexPluginRegistrationLive(options) {
1939
1997
  activations: {},
1940
1998
  };
1941
1999
  }
1942
- const manager = options.manager ?? SYSTEM_CODEX_MANAGER;
1943
- const readEntries = async (cwd) => {
1944
- try {
1945
- const result = await runNativeCli(buildCodexPluginListJsonInvocation(manager, cwd, options.env), options.nativeCliRunner);
1946
- if (result.exitCode !== 0) {
1947
- return { observation: { state: 'failed', reason: 'unavailable' } };
1948
- }
1949
- const entries = parseCodexPluginListJson(result.stdout);
1950
- return { entries, observation: observeCodexNativePlugin(options.pluginId, entries) };
1951
- }
1952
- catch (error) {
1953
- return {
1954
- observation: error.code === 'ENOENT'
1955
- ? { state: 'unobserved' }
1956
- : { state: 'failed', reason: 'unavailable' },
1957
- };
1958
- }
1959
- };
1960
- const [exactEntries, neutralEntries] = await Promise.all([
1961
- readEntries(options.cwd),
1962
- readEntries(os.tmpdir()),
1963
- ]);
1964
- const enabled = (values) => values?.find((entry) => entry.pluginId === options.pluginId)?.enabled;
1965
- return {
1966
- observation: combineCodexNativePluginObservations([
1967
- exactEntries.observation,
1968
- neutralEntries.observation,
1969
- ]),
1970
- observations: {
1971
- user: neutralEntries.entries !== undefined,
1972
- project: exactEntries.entries !== undefined,
1973
- },
1974
- activations: {
1975
- user: enabled(neutralEntries.entries),
1976
- project: enabled(exactEntries.entries),
1977
- },
1978
- };
2000
+ const ownedSession = options.observationSession ? undefined
2001
+ : await createCodexNativeObservationSession({
2002
+ exactCwd: options.cwd,
2003
+ env: options.env,
2004
+ runner: options.nativeCliRunner,
2005
+ manager: options.manager ?? SYSTEM_CODEX_MANAGER,
2006
+ });
2007
+ const session = options.observationSession ?? ownedSession;
2008
+ try {
2009
+ const inspected = await session.inspectPlugin({
2010
+ pluginId: options.pluginId,
2011
+ scope: options.scope ?? 'both',
2012
+ });
2013
+ return {
2014
+ observation: inspected.observation,
2015
+ observations: inspected.observations,
2016
+ activations: inspected.activations,
2017
+ };
2018
+ }
2019
+ finally {
2020
+ await ownedSession?.dispose();
2021
+ }
1979
2022
  }
1980
2023
  /**
1981
- * Read-only filesystem projection for the payload-aware shared-native classifier.
1982
- * It does not invoke Codex or create native/config state.
2024
+ * Read-only shared-native inspection. Official Codex list evidence owns installation
2025
+ * state. Filesystem validation is limited to the AW-owned marketplace source.
1983
2026
  */
1984
2027
  export async function inspectCodexSharedNativeLive(options) {
1985
2028
  const inspectionMode = options.inspectionMode ?? 'payload';
@@ -1992,151 +2035,28 @@ export async function inspectCodexSharedNativeLive(options) {
1992
2035
  }
1993
2036
  const marketplaceName = options.marketplaceName ?? MARKETPLACE_NAME;
1994
2037
  const marketplaceRoot = options.marketplaceRoot ?? path.join(resolveAwHome(options.env), 'marketplaces', options.sourceId);
2038
+ const marketplacePathSafe = await isSafeCodexMarketplaceRoot({
2039
+ awHome: resolveAwHome(options.env),
2040
+ sourceId: options.sourceId,
2041
+ marketplaceRoot,
2042
+ });
1995
2043
  const pluginId = `${options.pluginName}@${marketplaceName}`;
1996
2044
  const config = await readCodexConfigFile(resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath);
1997
- const cacheRoot = path.join(getCodexHome(options.env), 'plugins', 'cache', marketplaceName, options.pluginName);
1998
- const installedManifestPath = path.join(getCodexHome(options.env), 'plugins', pluginId, 'manifest.json');
1999
- const cacheRootStats = await lstatCodexResidueIfPresent(cacheRoot);
2000
- let unsafeCachePath = Boolean(cacheRootStats
2001
- && (!cacheRootStats.isDirectory() || cacheRootStats.isSymbolicLink()));
2002
- const candidates = [];
2003
- if (cacheRootStats?.isDirectory() && !cacheRootStats.isSymbolicLink()) {
2004
- for (const entry of await fs.readdir(cacheRoot, { withFileTypes: true })) {
2005
- const payloadPath = path.join(cacheRoot, entry.name);
2006
- if (!entry.isDirectory() || entry.isSymbolicLink()) {
2007
- unsafeCachePath = true;
2008
- continue;
2009
- }
2010
- const manifestPaths = [
2011
- path.join(payloadPath, 'plugin.json'),
2012
- path.join(payloadPath, '.codex-plugin', 'plugin.json'),
2013
- ];
2014
- const existingManifestPaths = [];
2015
- for (const manifestPath of manifestPaths) {
2016
- const stats = await lstatCodexResidueIfPresent(manifestPath);
2017
- if (stats)
2018
- existingManifestPaths.push(manifestPath);
2019
- }
2020
- if (existingManifestPaths.length !== 1) {
2021
- candidates.push({
2022
- version: entry.name,
2023
- manifest: existingManifestPaths.length === 0 ? { state: 'absent' } : { state: 'unreadable' },
2024
- payloadPath,
2025
- projectionReceipt: { state: 'absent' },
2026
- });
2027
- continue;
2028
- }
2029
- const manifestPath = existingManifestPaths[0];
2030
- const manifestStats = await lstatCodexResidueIfPresent(manifestPath);
2031
- if (!manifestStats?.isFile() || manifestStats.isSymbolicLink()) {
2032
- candidates.push({
2033
- version: entry.name,
2034
- manifest: { state: 'unreadable' },
2035
- payloadPath,
2036
- projectionReceipt: { state: 'absent' },
2037
- });
2038
- continue;
2039
- }
2040
- let manifest;
2041
- try {
2042
- const value = await fs.readJson(manifestPath);
2043
- if (!value || typeof value !== 'object' || Array.isArray(value))
2044
- throw new Error('invalid manifest');
2045
- manifest = value;
2046
- }
2047
- catch {
2048
- candidates.push({
2049
- version: entry.name,
2050
- manifest: { state: 'unreadable' },
2051
- payloadPath,
2052
- projectionReceipt: { state: 'absent' },
2053
- });
2054
- continue;
2055
- }
2056
- const projectionReceipt = await readCodexNativeRenderReceiptEvidence(payloadPath);
2057
- const pathSafe = inspectionMode === 'shallow'
2058
- ? true
2059
- : await codexPayloadPathSafe(payloadPath);
2060
- let payloadDigest;
2061
- if (pathSafe && inspectionMode === 'payload') {
2062
- try {
2063
- payloadDigest = dynamicProjectionExpected || projectionReceipt.state !== 'absent'
2064
- ? await calculateCodexNativeRenderPayloadDigest(payloadPath)
2065
- : await calculateCodexSharedNativePayloadDigest(payloadPath);
2066
- }
2067
- catch {
2068
- candidates.push({
2069
- version: entry.name,
2070
- manifest: { state: 'unreadable' },
2071
- payloadPath,
2072
- projectionReceipt,
2073
- });
2074
- continue;
2075
- }
2076
- }
2077
- candidates.push({
2078
- version: typeof manifest.version === 'string' ? manifest.version : entry.name,
2079
- payloadPath,
2080
- projectionReceipt,
2081
- manifest: {
2082
- state: 'present',
2083
- identity: typeof manifest.id === 'string'
2084
- ? manifest.id
2085
- : typeof manifest.name === 'string' ? manifest.name : '',
2086
- version: typeof manifest.version === 'string' ? manifest.version : '',
2087
- payloadDigest,
2088
- payloadPath,
2089
- pathSafe,
2090
- },
2091
- });
2092
- }
2093
- }
2094
- let installedVersion;
2095
- const installedStats = await lstatCodexResidueIfPresent(installedManifestPath);
2096
- if (installedStats?.isFile() && !installedStats.isSymbolicLink()) {
2097
- try {
2098
- const installed = await fs.readJson(installedManifestPath);
2099
- if (typeof installed.version === 'string')
2100
- installedVersion = installed.version;
2101
- }
2102
- catch {
2103
- installedVersion = undefined;
2104
- }
2105
- }
2106
- const selected = candidates.find((candidate) => candidate.version === installedVersion)
2107
- ?? candidates.find((candidate) => candidate.version === options.desiredVersion)
2108
- ?? (candidates.length === 1 ? candidates[0] : undefined);
2109
2045
  const pluginList = options.pluginRegistration ?? await inspectCodexPluginRegistrationLive({
2110
2046
  pluginId,
2111
2047
  cwd: options.cwd,
2112
2048
  env: options.env,
2113
2049
  nativeCliRunner: options.nativeCliRunner,
2114
2050
  manager: options.manager,
2051
+ scope: options.observationScope,
2052
+ observationSession: options.observationSession,
2115
2053
  });
2116
- const manifest = unsafeCachePath
2117
- ? {
2118
- state: 'present',
2119
- identity: pluginId,
2120
- version: options.desiredVersion,
2121
- ...(options.expectedPayloadDigest ? { payloadDigest: options.expectedPayloadDigest } : {}),
2122
- payloadPath: cacheRoot,
2123
- pathSafe: false,
2124
- }
2125
- : selected?.manifest ?? { state: 'absent' };
2126
- const projectionReceipt = selected?.projectionReceipt;
2127
- let expectedProjection;
2128
2054
  let expectedPayloadDigest = dynamicProjectionExpected ? undefined : options.expectedPayloadDigest;
2129
- const expectedLogicalPluginId = options.nativeRenderPlugin?.pluginId
2130
- ?? (options.nativeRenderPlugin?.sourceId
2131
- ? `${options.nativeRenderPlugin.sourceId}/${options.nativeRenderPlugin.manifest.name}`
2132
- : undefined);
2133
- const expectedProjectionIdentity = dynamicProjectionExpected && expectedLogicalPluginId
2134
- ? {
2135
- logicalPluginId: expectedLogicalPluginId,
2136
- strategy: 'shared-user',
2137
- }
2138
- : undefined;
2139
- if (dynamicProjectionExpected && options.nativeRenderPlugin) {
2055
+ let sourceProjection;
2056
+ let expectedMarketplaceForeign = false;
2057
+ const installed = pluginList.observation.state === 'observed'
2058
+ && pluginList.observation.value === 'installed';
2059
+ if (installed && dynamicProjectionExpected && options.nativeRenderPlugin) {
2140
2060
  try {
2141
2061
  const expected = await resolveCodexNativeRenderExpectedEvidence({
2142
2062
  plugin: options.nativeRenderPlugin,
@@ -2144,181 +2064,36 @@ export async function inspectCodexSharedNativeLive(options) {
2144
2064
  marketplaceName,
2145
2065
  env: options.env,
2146
2066
  });
2147
- expectedProjection = expected.projection;
2148
- expectedPayloadDigest = expected.payloadDigest;
2067
+ if (expected.state === 'foreign') {
2068
+ expectedMarketplaceForeign = true;
2069
+ }
2070
+ else {
2071
+ expectedPayloadDigest = expected.payloadDigest;
2072
+ sourceProjection = expected.sourceProjection;
2073
+ }
2149
2074
  }
2150
2075
  catch {
2151
- // Canonical marketplace evidence is weak state. Missing evidence is replaceable damage.
2076
+ sourceProjection = 'drifted';
2152
2077
  }
2153
2078
  }
2154
- const sharedNative = inspectCodexSharedNative({
2155
- pluginName: options.pluginName,
2156
- pluginId,
2157
- marketplaceName,
2158
- canonicalMarketplaceRoot: marketplaceRoot,
2159
- desiredVersion: options.desiredVersion,
2160
- expectedPayloadDigest,
2161
- marketplaceRegistration: config.snapshot.marketplaces[marketplaceName],
2162
- marketplacePathSafe: await isSafeCodexMarketplaceRoot({
2163
- awHome: resolveAwHome(options.env),
2164
- sourceId: options.sourceId,
2165
- marketplaceRoot,
2166
- }),
2167
- observation: pluginList.observation,
2168
- manifest,
2169
- ...(dynamicProjectionExpected ? { expectedProjectionRequired: true } : {}),
2170
- ...(expectedProjectionIdentity ? { expectedProjectionIdentity } : {}),
2171
- ...(expectedProjection ? { expectedProjection } : {}),
2172
- ...(projectionReceipt ? { projectionReceipt } : {}),
2173
- });
2079
+ const sharedNative = expectedMarketplaceForeign
2080
+ ? { state: 'blocked', reason: 'foreign', diagnostic: 'marketplaceRegistrationForeign' }
2081
+ : inspectCodexSharedNative({
2082
+ pluginName: options.pluginName,
2083
+ pluginId,
2084
+ marketplaceName,
2085
+ canonicalMarketplaceRoot: marketplaceRoot,
2086
+ desiredVersion: options.desiredVersion,
2087
+ expectedPayloadDigest,
2088
+ marketplaceRegistration: config.snapshot.marketplaces[marketplaceName],
2089
+ marketplacePathSafe,
2090
+ observation: pluginList.observation,
2091
+ ...(sourceProjection ? { sourceProjection } : {}),
2092
+ });
2174
2093
  return {
2175
2094
  sharedNative,
2176
2095
  ...(expectedPayloadDigest ? { expectedPayloadDigest } : {}),
2177
- ...(expectedProjection ? { expectedProjection } : {}),
2178
- activationObservations: pluginList.observations,
2179
- activations: pluginList.activations,
2180
- ...((sharedNative.state === 'missing' || sharedNative.state === 'unobserved')
2181
- && sharedNative.staleCacheObserved
2182
- ? { staleCacheObserved: true }
2183
- : {}),
2184
- ...(manifest.state === 'present' ? {
2185
- ...(manifest.payloadDigest ? { actualPayloadDigest: manifest.payloadDigest } : {}),
2186
- payloadPath: manifest.payloadPath,
2187
- } : {}),
2188
- };
2189
- }
2190
- export async function codexNativeRemovalRequired(options) {
2191
- const residue = await inspectCodexNativeResidue(options);
2192
- if (residue.state === 'present')
2193
- return true;
2194
- if (residue.state === 'foreign')
2195
- return false;
2196
- const marketplaceName = options.marketplaceName ?? MARKETPLACE_NAME;
2197
- const marketplaceRoot = options.marketplaceRoot ?? resolveAwHome(options.env);
2198
- const config = await readCodexConfigFile(resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath);
2199
- const marketplace = config.snapshot.marketplaces[marketplaceName];
2200
- const canonical = marketplace?.sourceType === 'local'
2201
- && !marketplace.sourcePointsToManifest
2202
- && path.normalize(marketplace.source ?? '') === path.normalize(marketplaceRoot);
2203
- if (!canonical)
2204
- return false;
2205
- return (await discoverCodexNativeResidueNames(options)).length === 0;
2206
- }
2207
- export async function discoverCodexNativeResidueNames(options) {
2208
- const marketplaceName = options.marketplaceName ?? MARKETPLACE_NAME;
2209
- const marketplaceRoot = options.marketplaceRoot ?? resolveAwHome(options.env);
2210
- const names = new Set();
2211
- const config = await readCodexConfigFile(resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath);
2212
- const marketplace = config.snapshot.marketplaces[marketplaceName];
2213
- const canonicalMarketplace = marketplace?.sourceType === 'local'
2214
- && !marketplace.sourcePointsToManifest
2215
- && path.normalize(marketplace.source ?? '') === path.normalize(marketplaceRoot);
2216
- const probeFailure = (location, cause, pluginName) => {
2217
- if (cause instanceof CodexNativeResidueProbeError)
2218
- throw cause;
2219
- throw new CodexNativeResidueProbeError(location, cause, pluginName);
2220
2096
  };
2221
- for (const pluginId of canonicalMarketplace ? Object.keys(config.snapshot.plugins) : []) {
2222
- const suffix = `@${marketplaceName}`;
2223
- const match = pluginId.endsWith(suffix)
2224
- ? /^([a-z0-9]+(?:-[a-z0-9]+)*)$/u.exec(pluginId.slice(0, -suffix.length))
2225
- : null;
2226
- if (match)
2227
- names.add(match[1]);
2228
- }
2229
- const cacheRoot = path.join(getCodexHome(options.env), 'plugins', 'cache', marketplaceName);
2230
- const cacheEntries = await readdirCodexResidueIfPresent(cacheRoot).catch((error) => probeFailure(cacheRoot, error));
2231
- for (const entry of cacheEntries) {
2232
- if (entry.isFile())
2233
- continue;
2234
- const entryPath = path.join(cacheRoot, entry.name);
2235
- const pluginName = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(entry.name)
2236
- ? entry.name
2237
- : probeFailure(entryPath, new Error('Codex cache residue has an invalid plugin identity.'));
2238
- if (!entry.isDirectory() || entry.isSymbolicLink()) {
2239
- probeFailure(entryPath, new Error('Codex cache residue is not a canonical plugin directory.'), pluginName);
2240
- }
2241
- let versionEntries;
2242
- try {
2243
- versionEntries = await fs.readdir(entryPath, { withFileTypes: true });
2244
- }
2245
- catch (error) {
2246
- throw new CodexNativeResidueProbeError(entryPath, error, pluginName);
2247
- }
2248
- if (versionEntries.length === 0) {
2249
- if (names.has(pluginName)) {
2250
- probeFailure(entryPath, new Error('Codex cache residue manifest is missing.'), pluginName);
2251
- }
2252
- continue;
2253
- }
2254
- let residue;
2255
- try {
2256
- residue = await inspectCodexNativeResidue({ ...options, pluginName: entry.name });
2257
- }
2258
- catch (error) {
2259
- throw new CodexNativeResidueProbeError(entryPath, error, pluginName);
2260
- }
2261
- if (residue.state !== 'present') {
2262
- probeFailure(entryPath, new Error('Codex cache residue has foreign or incomplete manifest evidence.'), pluginName);
2263
- }
2264
- names.add(pluginName);
2265
- }
2266
- const pluginsRoot = path.join(getCodexHome(options.env), 'plugins');
2267
- const pluginEntries = await readdirCodexResidueIfPresent(pluginsRoot).catch((error) => probeFailure(pluginsRoot, error));
2268
- for (const entry of pluginEntries) {
2269
- const suffix = `@${marketplaceName}`;
2270
- const match = entry.name.endsWith(suffix)
2271
- ? /^([a-z0-9]+(?:-[a-z0-9]+)*)$/u.exec(entry.name.slice(0, -suffix.length))
2272
- : null;
2273
- if (!match)
2274
- continue;
2275
- const installedRoot = path.join(pluginsRoot, entry.name);
2276
- if (!entry.isDirectory() || entry.isSymbolicLink()) {
2277
- probeFailure(installedRoot, new Error('Codex installed residue is not a canonical plugin directory.'), match[1]);
2278
- }
2279
- const installedStats = await lstatCodexResidueIfPresent(installedRoot);
2280
- if (!installedStats || !installedStats.isDirectory() || installedStats.isSymbolicLink()) {
2281
- probeFailure(installedRoot, new Error('Codex installed residue is not a canonical plugin directory.'), match[1]);
2282
- }
2283
- const manifestPath = path.join(pluginsRoot, entry.name, 'manifest.json');
2284
- const stats = await lstatCodexResidueIfPresent(manifestPath);
2285
- if (!stats?.isFile() || stats.isSymbolicLink()) {
2286
- probeFailure(manifestPath, new Error('Codex installed residue manifest is missing or nonregular.'), match[1]);
2287
- }
2288
- let rawManifest;
2289
- try {
2290
- rawManifest = await fs.readJson(manifestPath);
2291
- }
2292
- catch (error) {
2293
- throw new CodexNativeResidueProbeError(manifestPath, error, match[1]);
2294
- }
2295
- if (!rawManifest || typeof rawManifest !== 'object' || Array.isArray(rawManifest)) {
2296
- probeFailure(manifestPath, new Error('Codex installed residue manifest must be an object.'), match[1]);
2297
- }
2298
- const manifest = rawManifest;
2299
- const identity = manifest.id ?? manifest.name;
2300
- if (!canonicalMarketplace
2301
- || (identity !== match[1] && identity !== entry.name)
2302
- || typeof manifest.version !== 'string'
2303
- || manifest.version.trim() === '') {
2304
- probeFailure(manifestPath, new Error('Codex installed residue manifest has foreign identity or version.'), match[1]);
2305
- }
2306
- names.add(match[1]);
2307
- }
2308
- return [...names];
2309
- }
2310
- export class CodexNativeResidueProbeError extends Error {
2311
- code = 'codexNativeResidueProbeFailed';
2312
- location;
2313
- pluginName;
2314
- constructor(location, cause, pluginName) {
2315
- super(`codexNativeResidueProbeFailed: cannot classify Codex native residue at ${location}.`, {
2316
- cause,
2317
- });
2318
- this.name = 'CodexNativeResidueProbeError';
2319
- this.location = location;
2320
- this.pluginName = pluginName;
2321
- }
2322
2097
  }
2323
2098
  async function ensureCodexActiveSkillsManifest(options) {
2324
2099
  const relativeSkillsRoot = await resolveCodexSkillsRelativeRoot(options.plugin);
@@ -2358,124 +2133,6 @@ function createCodexMarketplaceComponent(options) {
2358
2133
  },
2359
2134
  };
2360
2135
  }
2361
- async function assertCodexNativeInstallState(options) {
2362
- const marketplaceName = options.marketplaceName ?? MARKETPLACE_NAME;
2363
- const pluginId = `${options.pluginName}@${marketplaceName}`;
2364
- const configPath = resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath;
2365
- const config = await readCodexConfigFile(configPath);
2366
- const marketplace = config.snapshot.marketplaces[marketplaceName];
2367
- if (marketplace?.sourceType !== 'local'
2368
- || marketplace.sourcePointsToManifest
2369
- || path.normalize(marketplace.source ?? '') !== path.normalize(options.marketplaceRoot)) {
2370
- throw new Error(`Codex marketplace aw 未指向当前 AW_HOME:${options.marketplaceRoot}`);
2371
- }
2372
- if (config.snapshot.plugins[pluginId]?.enabled !== true) {
2373
- throw new Error(`Codex enabled config 缺少 ${pluginId}。`);
2374
- }
2375
- const cacheRoot = path.join(getCodexHome(options.env), 'plugins', 'cache', marketplaceName, options.pluginName, options.pluginVersion);
2376
- const candidates = [
2377
- path.join(cacheRoot, 'plugin.json'),
2378
- path.join(cacheRoot, '.codex-plugin', 'plugin.json'),
2379
- ];
2380
- const expectedRoot = path.join(options.marketplaceRoot, 'plugins', options.pluginName);
2381
- const assertExpectedPayload = async () => {
2382
- const collectFiles = async (root) => {
2383
- const output = [];
2384
- const pending = [''];
2385
- while (pending.length > 0) {
2386
- const relativeRoot = pending.pop();
2387
- for (const entry of await fs.readdir(path.join(root, relativeRoot), { withFileTypes: true })) {
2388
- const relativePath = path.join(relativeRoot, entry.name);
2389
- if (entry.isSymbolicLink()) {
2390
- throw new Error(`Codex native payload 不能包含符号链接:${path.join(root, relativePath)}`);
2391
- }
2392
- if (entry.isDirectory())
2393
- pending.push(relativePath);
2394
- else if (entry.isFile())
2395
- output.push(relativePath);
2396
- else
2397
- throw new Error(`Codex native payload 包含不支持的节点:${path.join(root, relativePath)}`);
2398
- }
2399
- }
2400
- return output;
2401
- };
2402
- const manifestPaths = new Set(['plugin.json', path.join('.codex-plugin', 'plugin.json')]);
2403
- const allowedMetadata = new Set(['metadata.json', '.metadata.json']);
2404
- const [expectedFiles, actualFiles] = await Promise.all([
2405
- collectFiles(expectedRoot),
2406
- collectFiles(cacheRoot),
2407
- ]);
2408
- const expectedSet = new Set(expectedFiles);
2409
- for (const relativePath of expectedFiles) {
2410
- if (manifestPaths.has(relativePath))
2411
- continue;
2412
- const expectedPath = path.join(expectedRoot, relativePath);
2413
- const actualPath = path.join(cacheRoot, relativePath);
2414
- const actualStats = await fs.lstat(actualPath).catch(() => null);
2415
- if (!actualStats?.isFile() || actualStats.isSymbolicLink()) {
2416
- throw new Error(`Codex native cache 缺少 expected payload:${actualPath}`);
2417
- }
2418
- const [expectedContent, actualContent] = await Promise.all([
2419
- fs.readFile(expectedPath),
2420
- fs.readFile(actualPath),
2421
- ]);
2422
- if (!expectedContent.equals(actualContent)) {
2423
- throw new Error(`Codex native cache expected payload digest 不匹配:${actualPath}`);
2424
- }
2425
- }
2426
- for (const relativePath of actualFiles) {
2427
- if (expectedSet.has(relativePath)
2428
- || manifestPaths.has(relativePath)
2429
- || allowedMetadata.has(relativePath))
2430
- continue;
2431
- throw new Error(`Codex native cache 包含 unexpected loadable payload:${path.join(cacheRoot, relativePath)}`);
2432
- }
2433
- };
2434
- let matchedManifest = false;
2435
- for (const candidate of candidates) {
2436
- if (!await fs.pathExists(candidate))
2437
- continue;
2438
- const manifest = await fs.readJson(candidate).catch(() => null);
2439
- if (!manifest) {
2440
- throw new Error(`Codex native cache manifest 无法解析:${candidate}`);
2441
- }
2442
- const identity = manifest.id ?? manifest.name;
2443
- const matches = ((identity === options.pluginName || identity === pluginId)
2444
- && manifest.version === options.pluginVersion
2445
- && (options.skillsRelativeRoot === null
2446
- ? typeof manifest.skills !== 'string'
2447
- : typeof manifest.skills === 'string'
2448
- && path.posix.normalize(manifest.skills.replace(/^\.\//u, '')) === options.skillsRelativeRoot));
2449
- if (!matches) {
2450
- throw new Error(`Codex native cache manifest 不匹配:${candidate}`);
2451
- }
2452
- matchedManifest = true;
2453
- }
2454
- if (!matchedManifest) {
2455
- throw new Error(`Codex native cache 缺少匹配 ${pluginId}@${options.pluginVersion} 的 manifest:${cacheRoot}`);
2456
- }
2457
- for (const skillKey of options.requiredSkillKeys) {
2458
- const skillPath = path.join(cacheRoot, ...options.skillsRelativeRoot.split('/'), skillKey, 'SKILL.md');
2459
- if (!await fs.pathExists(skillPath)) {
2460
- throw new Error(`Codex native cache 缺少 required Skill:${skillPath}`);
2461
- }
2462
- }
2463
- await assertExpectedPayload();
2464
- if (options.nativeRender) {
2465
- const actualPayloadDigest = await calculateCodexNativeRenderPayloadDigest(cacheRoot);
2466
- if (actualPayloadDigest !== options.nativeRender.payloadDigest) {
2467
- throw new Error(`Codex native cache render payload digest 不匹配:${cacheRoot}`);
2468
- }
2469
- const receiptEvidence = await readCodexNativeRenderReceiptEvidence(cacheRoot);
2470
- if (receiptEvidence.state !== 'present'
2471
- || receiptEvidence.receipt.payloadDigest !== options.nativeRender.payloadDigest
2472
- || !codexNativeRenderProjectionsEqual(receiptEvidence.receipt, options.nativeRender.projection)
2473
- || receiptEvidence.receipt.targets.map((target) => target.skillKey).sort().join('\0')
2474
- !== options.nativeRender.targetSkillKeys.join('\0')) {
2475
- throw new Error(`Codex native cache render receipt 不匹配:${cacheRoot}`);
2476
- }
2477
- }
2478
- }
2479
2136
  async function readJsonObjectIfExists(filePath, label) {
2480
2137
  let value;
2481
2138
  try {
@@ -2793,31 +2450,125 @@ function createClaudeMarketplaceComponent(options) {
2793
2450
  },
2794
2451
  };
2795
2452
  }
2796
- async function materializeCodexNativeRender(options) {
2797
- const targets = codexNativeRenderTargets(options.plugin);
2798
- if (targets.length === 0)
2799
- return null;
2800
- const byScope = new Map();
2801
- for (const target of targets) {
2802
- const requestScope = target.scopes.includes('user') ? 'user' : 'project';
2803
- const group = byScope.get(requestScope) ?? [];
2804
- group.push(target);
2805
- byScope.set(requestScope, group);
2453
+ const codexNativeRenderPreparationCaches = new WeakMap();
2454
+ function codexNativeRenderPreparationCache(context) {
2455
+ let cache = codexNativeRenderPreparationCaches.get(context);
2456
+ if (!cache) {
2457
+ cache = new WeakMap();
2458
+ codexNativeRenderPreparationCaches.set(context, cache);
2806
2459
  }
2807
- const renderedArtifacts = [];
2808
- let rendererDigest;
2809
- for (const scope of ['user', 'project']) {
2810
- const group = byScope.get(scope);
2811
- if (!group || group.length === 0)
2812
- continue;
2813
- const rendered = await runArtifactRenderer({
2814
- event: 'install',
2815
- plugin: options.plugin,
2816
- platform: 'codex',
2817
- scope,
2818
- configScope: 'user',
2819
- cwd: options.plugin.rootDir,
2820
- commandEnv: options.env,
2460
+ return cache;
2461
+ }
2462
+ function pluginHasCodexNativeSkillSurface(plugin) {
2463
+ return plugin.manifest.skills !== undefined
2464
+ || codexNativeRenderTargets(plugin).length > 0;
2465
+ }
2466
+ async function resolveSensitiveUserConfigValues(plugin, env) {
2467
+ const hasSensitiveFields = Object.values(plugin.manifest.config?.fields ?? {}).some((field) => field.sensitive === true
2468
+ || (field.type === 'object'
2469
+ && (Object.values(field.properties ?? {}).some((child) => child.sensitive === true)
2470
+ || (field.additionalProperties !== false
2471
+ && field.additionalProperties?.sensitive === true))));
2472
+ if (!hasSensitiveFields)
2473
+ return [];
2474
+ const config = await resolveArtifactRenderConfig({
2475
+ sourceId: plugin.sourceId,
2476
+ sourceIdentity: plugin.sourceIdentity,
2477
+ pluginName: plugin.manifest.name,
2478
+ manifest: plugin.manifest,
2479
+ scope: 'user',
2480
+ cwd: plugin.rootDir,
2481
+ env,
2482
+ });
2483
+ return collectSensitivePluginConfigValues(plugin.manifest, config.values);
2484
+ }
2485
+ async function assertNoSensitiveValuesInCodexNativePayload(options) {
2486
+ if (options.sensitiveValues.length === 0)
2487
+ return;
2488
+ if (!await codexPayloadPathSafe(options.payloadRoot, options.allowedRoot)) {
2489
+ throw new Error('Codex native sensitive scan payload path is unsafe.');
2490
+ }
2491
+ const codexManifestPath = path.join(options.payloadRoot, '.codex-plugin', 'plugin.json');
2492
+ const codexManifestStats = await lstatCodexResidueIfPresent(codexManifestPath);
2493
+ if (!codexManifestStats?.isFile()) {
2494
+ throw new Error('Codex native sensitive scan manifest is missing or unsafe.');
2495
+ }
2496
+ const codexManifest = await fs.readJson(codexManifestPath).catch(() => null);
2497
+ if (!codexManifest
2498
+ || codexManifest.name !== options.plugin.manifest.name
2499
+ || codexManifest.version !== options.plugin.manifest.version) {
2500
+ throw new Error('Codex native sensitive scan manifest identity is invalid.');
2501
+ }
2502
+ const rawSkillsRoot = codexManifest.skills;
2503
+ if (rawSkillsRoot !== undefined) {
2504
+ if (typeof rawSkillsRoot !== 'string' || rawSkillsRoot.trim() === '') {
2505
+ throw new Error('Codex native sensitive scan Skills root is invalid.');
2506
+ }
2507
+ const { root: skillsRoot } = await resolveCodexSkillsRoot(options.payloadRoot, rawSkillsRoot);
2508
+ const skillsRootStats = await lstatCodexResidueIfPresent(skillsRoot);
2509
+ if (!skillsRootStats?.isDirectory()) {
2510
+ throw new Error('Codex native sensitive scan Skills root is missing or unsafe.');
2511
+ }
2512
+ for (const entry of await fs.readdir(skillsRoot, { withFileTypes: true })) {
2513
+ if (!entry.isDirectory())
2514
+ continue;
2515
+ const skillRoot = path.join(skillsRoot, entry.name);
2516
+ const skillFileStats = await lstatCodexResidueIfPresent(path.join(skillRoot, 'SKILL.md'));
2517
+ if (!skillFileStats)
2518
+ continue;
2519
+ if (!skillFileStats.isFile()) {
2520
+ throw new Error('Codex native sensitive scan Skill entry is unsafe.');
2521
+ }
2522
+ const tree = await readDynamicSkillTree(skillRoot, 'codex.skill');
2523
+ if (!tree)
2524
+ continue;
2525
+ const leakedPath = findSensitiveTokenPath(tree.files, options.sensitiveValues);
2526
+ if (leakedPath) {
2527
+ throw new Error(`codexNativeRenderSensitiveValue: Codex native Skill "${entry.name}" exposes ` +
2528
+ `sensitive config in "${leakedPath}".`);
2529
+ }
2530
+ }
2531
+ }
2532
+ const receiptPath = path.join(options.payloadRoot, CODEX_NATIVE_RENDER_RECEIPT_PATH);
2533
+ const receiptStats = await lstatCodexResidueIfPresent(receiptPath);
2534
+ if (receiptStats) {
2535
+ if (!receiptStats.isFile()) {
2536
+ throw new Error('Codex native sensitive scan render receipt path is unsafe.');
2537
+ }
2538
+ const receiptLeak = findSensitiveTokenPath({ [CODEX_NATIVE_RENDER_RECEIPT_PATH]: await fs.readFile(receiptPath) }, options.sensitiveValues);
2539
+ if (receiptLeak) {
2540
+ throw new Error(`codexNativeRenderSensitiveValue: Codex native render receipt exposes sensitive config in ` +
2541
+ `"${receiptLeak}".`);
2542
+ }
2543
+ }
2544
+ }
2545
+ async function prepareCodexNativeRender(options) {
2546
+ const targets = codexNativeRenderTargets(options.plugin);
2547
+ if (targets.length === 0)
2548
+ return null;
2549
+ const byScope = new Map();
2550
+ for (const target of targets) {
2551
+ const requestScope = target.scopes.includes('user') ? 'user' : 'project';
2552
+ const group = byScope.get(requestScope) ?? [];
2553
+ group.push(target);
2554
+ byScope.set(requestScope, group);
2555
+ }
2556
+ const renderedArtifacts = [];
2557
+ const receiptTargets = [];
2558
+ const presentSkillKeys = [];
2559
+ let rendererDigest;
2560
+ for (const scope of ['user', 'project']) {
2561
+ const group = byScope.get(scope);
2562
+ if (!group || group.length === 0)
2563
+ continue;
2564
+ const rendered = await runArtifactRenderer({
2565
+ event: 'install',
2566
+ plugin: options.plugin,
2567
+ platform: 'codex',
2568
+ scope,
2569
+ configScope: 'user',
2570
+ cwd: options.plugin.rootDir,
2571
+ commandEnv: options.env,
2821
2572
  previousArtifacts: [],
2822
2573
  requestedArtifactIds: group.map((target) => target.id),
2823
2574
  scriptTimeoutMs: options.scriptTimeoutMs,
@@ -2834,9 +2585,31 @@ async function materializeCodexNativeRender(options) {
2834
2585
  manifestDigest: rendered.target.manifestDigest,
2835
2586
  });
2836
2587
  for (const artifact of validated.artifacts) {
2588
+ if (artifact.target !== 'codex.skill') {
2589
+ throw new Error(`Codex native renderer returned non-Skill artifact "${artifact.id}".`);
2590
+ }
2591
+ const sourceDirs = rendered.context.artifactSources.find((entry) => entry.id === artifact.id && entry.target === artifact.target)?.sourceDirs ?? [];
2592
+ const tree = await createCodexSkillExpectedTree({
2593
+ artifact,
2594
+ sourceDirs,
2595
+ requireReadme: true,
2596
+ requirementsReceipt: requirementsReceipt(options.plugin),
2597
+ sourceRequirementsReceiptPolicy: 'allow',
2598
+ });
2599
+ assertNoSensitiveConfigInFiles(tree.files, collectSensitivePluginConfigValues(options.plugin.manifest, rendered.context.config.values), 'artifactRendererSensitiveConfigExposed: Final dynamic Skill contains sensitive config.');
2600
+ const skillKey = artifact.skillKey;
2837
2601
  renderedArtifacts.push({
2838
2602
  artifact,
2839
- sourceDirs: rendered.context.artifactSources.find((entry) => entry.id === artifact.id && entry.target === artifact.target)?.sourceDirs ?? [],
2603
+ skillKey,
2604
+ tree,
2605
+ });
2606
+ if (artifact.state === 'present')
2607
+ presentSkillKeys.push(skillKey);
2608
+ receiptTargets.push({
2609
+ id: artifact.id,
2610
+ skillKey,
2611
+ configDigest: artifact.configDigest,
2612
+ artifactDigest: createCodexSkillStagingTreeContentDigest(tree),
2840
2613
  });
2841
2614
  }
2842
2615
  }
@@ -2844,72 +2617,87 @@ async function materializeCodexNativeRender(options) {
2844
2617
  throw new Error('Codex native renderer did not produce the complete declared target set.');
2845
2618
  }
2846
2619
  const skillsRelativeRoot = await resolveCodexSkillsRelativeRoot(options.plugin);
2847
- const skillsRoot = path.join(options.stagingRoot, 'plugins', options.plugin.manifest.name, ...skillsRelativeRoot.split('/'));
2848
- const receiptTargets = [];
2849
- const presentSkillKeys = [];
2850
- for (const { artifact, sourceDirs } of renderedArtifacts) {
2851
- if (artifact.target !== 'codex.skill') {
2852
- throw new Error(`Codex native renderer returned non-Skill artifact "${artifact.id}".`);
2853
- }
2854
- const skillKey = artifact.skillKey;
2855
- const tree = await createCodexSkillExpectedTree({
2856
- artifact,
2857
- sourceDirs,
2858
- requireReadme: true,
2859
- requirementsReceipt: requirementsReceipt(options.plugin),
2860
- sourceRequirementsReceiptPolicy: 'allow',
2620
+ const logicalPluginId = options.plugin.pluginId
2621
+ ?? (options.plugin.sourceId ? `${options.plugin.sourceId}/${options.plugin.manifest.name}` : undefined);
2622
+ if (!logicalPluginId || !options.plugin.sourceIdentity) {
2623
+ throw new Error('Codex native render materialization requires namespaced source identity.');
2624
+ }
2625
+ const projection = createCodexNativeRenderProjection({
2626
+ logicalPluginId,
2627
+ sourceIdentity: options.plugin.sourceIdentity,
2628
+ pluginVersion: options.plugin.manifest.version,
2629
+ rendererDigest,
2630
+ targets: receiptTargets,
2631
+ });
2632
+ return {
2633
+ logicalPluginId,
2634
+ sourceIdentity: options.plugin.sourceIdentity,
2635
+ rendererDigest,
2636
+ skillsRelativeRoot,
2637
+ renderedArtifacts,
2638
+ receiptTargets,
2639
+ projection,
2640
+ presentSkillKeys: presentSkillKeys.sort(),
2641
+ targetSkillKeys: targets.map((target) => target.skillKey).sort(),
2642
+ };
2643
+ }
2644
+ function prepareCodexNativeRenderCached(options) {
2645
+ const cache = codexNativeRenderPreparationCache(options.context);
2646
+ let preparation = cache.get(options.plugin);
2647
+ if (!preparation) {
2648
+ preparation = prepareCodexNativeRender({
2649
+ plugin: options.plugin,
2650
+ env: options.context.env,
2651
+ scriptTimeoutMs: options.scriptTimeoutMs,
2861
2652
  });
2653
+ cache.set(options.plugin, preparation);
2654
+ }
2655
+ return preparation;
2656
+ }
2657
+ async function requirePreparedCodexNativeRender(options) {
2658
+ if (codexNativeRenderTargets(options.plugin).length === 0)
2659
+ return null;
2660
+ const preparation = codexNativeRenderPreparationCache(options.context).get(options.plugin);
2661
+ if (!preparation) {
2662
+ throw new Error('codexNativeRenderPreflightMissing: Codex native renderer was not prepared.');
2663
+ }
2664
+ return preparation;
2665
+ }
2666
+ async function materializeCodexNativeRender(options) {
2667
+ const skillsRoot = path.join(options.stagingRoot, 'plugins', options.plugin.manifest.name, ...options.prepared.skillsRelativeRoot.split('/'));
2668
+ for (const { artifact, skillKey, tree } of options.prepared.renderedArtifacts) {
2862
2669
  const skillRoot = path.join(skillsRoot, skillKey);
2863
2670
  if (artifact.state === 'present') {
2864
2671
  await writeDynamicSkillTree(skillRoot, tree);
2865
- presentSkillKeys.push(skillKey);
2866
2672
  }
2867
2673
  else {
2868
2674
  await fs.remove(skillRoot);
2869
2675
  }
2870
- receiptTargets.push({
2871
- id: artifact.id,
2872
- skillKey,
2873
- configDigest: artifact.configDigest,
2874
- artifactDigest: createCodexSkillStagingTreeContentDigest(tree),
2875
- });
2876
2676
  }
2877
2677
  await ensureCodexActiveSkillsManifest({
2878
2678
  plugin: options.plugin,
2879
2679
  marketplaceRoot: options.stagingRoot,
2880
- hasRequiredSkills: options.hasStaticSkills || presentSkillKeys.length > 0,
2881
- });
2882
- const logicalPluginId = options.plugin.pluginId
2883
- ?? (options.plugin.sourceId ? `${options.plugin.sourceId}/${options.plugin.manifest.name}` : undefined);
2884
- if (!logicalPluginId || !options.plugin.sourceIdentity) {
2885
- throw new Error('Codex native render materialization requires namespaced source identity.');
2886
- }
2887
- const projection = createCodexNativeRenderProjection({
2888
- logicalPluginId,
2889
- sourceIdentity: options.plugin.sourceIdentity,
2890
- pluginVersion: options.plugin.manifest.version,
2891
- rendererDigest,
2892
- targets: receiptTargets,
2680
+ hasRequiredSkills: options.hasStaticSkills || options.prepared.presentSkillKeys.length > 0,
2893
2681
  });
2894
2682
  const pluginRoot = path.join(options.stagingRoot, 'plugins', options.plugin.manifest.name);
2895
2683
  const payloadDigest = await calculateCodexNativeRenderPayloadDigest(pluginRoot);
2896
2684
  const receipt = createCodexNativeRenderReceipt({
2897
- logicalPluginId,
2898
- sourceIdentity: options.plugin.sourceIdentity,
2685
+ logicalPluginId: options.prepared.logicalPluginId,
2686
+ sourceIdentity: options.prepared.sourceIdentity,
2899
2687
  pluginVersion: options.plugin.manifest.version,
2900
- rendererDigest,
2901
- targets: receiptTargets,
2688
+ rendererDigest: options.prepared.rendererDigest,
2689
+ targets: options.prepared.receiptTargets,
2902
2690
  payloadDigest,
2903
2691
  });
2904
2692
  await fs.outputFile(path.join(pluginRoot, CODEX_NATIVE_RENDER_RECEIPT_PATH), serializeCodexNativeRenderReceipt(receipt), 'utf8');
2905
2693
  return {
2906
2694
  payloadDigest,
2907
- projection,
2908
- presentSkillKeys: presentSkillKeys.sort(),
2909
- targetSkillKeys: targets.map((target) => target.skillKey).sort(),
2695
+ projection: options.prepared.projection,
2696
+ presentSkillKeys: options.prepared.presentSkillKeys,
2697
+ targetSkillKeys: options.prepared.targetSkillKeys,
2910
2698
  };
2911
2699
  }
2912
- async function mirrorCatalog(catalog, env, target, prepareStaging) {
2700
+ async function mirrorCatalog(catalog, env, target, logicalIdentity, prepareStaging) {
2913
2701
  if (!target?.sourceId || !target.marketplaceRoot) {
2914
2702
  const rootDir = resolveAwHome(env);
2915
2703
  await fs.ensureDir(rootDir);
@@ -2957,6 +2745,7 @@ async function mirrorCatalog(catalog, env, target, prepareStaging) {
2957
2745
  schemaVersion: 2,
2958
2746
  sourceId: target.sourceId,
2959
2747
  sourceIdentity: target.sourceIdentity,
2748
+ logicalIdentity,
2960
2749
  marketplaceName,
2961
2750
  }, { spaces: 2 });
2962
2751
  const destinationExists = await fs.lstat(destination).then(() => true, (error) => {
@@ -3146,8 +2935,53 @@ async function applyPlannedOperationAction(action) {
3146
2935
  function createNativeDriver(options) {
3147
2936
  const runner = options.context.nativeCliRunner;
3148
2937
  const currentCodexManager = () => options.context.codexManager;
3149
- const mirrorTransactions = new WeakMap();
2938
+ const nativeGroupPreparations = new Map();
2939
+ const nativeTargetPreparations = new WeakMap();
2940
+ const codexNativeStates = new Map();
2941
+ const pendingCodexNativeStates = new Map();
2942
+ const codexMutationMarketplaces = new Set();
3150
2943
  const completedNativeSkillWrites = new WeakSet();
2944
+ const committedNativeGroups = new Set();
2945
+ let nativeMarketplaceEligibleTargets;
2946
+ let deferredPostchecks = [];
2947
+ const nativeGroupKey = (target) => [
2948
+ target.identity.sourceId ?? '',
2949
+ target.identity.sourceIdentity ?? '',
2950
+ target.identity.marketplaceName ?? MARKETPLACE_NAME,
2951
+ path.normalize(path.resolve(target.identity.marketplaceRoot ?? resolveAwHome(options.context.env))),
2952
+ target.identity.platform,
2953
+ ].join('\0');
2954
+ const observationScopeFor = (target) => codexObservationScope(options.context, target.identity.marketplaceName ?? MARKETPLACE_NAME, target.identity.scope);
2955
+ const groupTargets = (target) => {
2956
+ if (!options.operationScopedNativePhases)
2957
+ return [target];
2958
+ const key = nativeGroupKey(target);
2959
+ return options.targets.filter((candidate) => nativeGroupKey(candidate) === key
2960
+ && (nativeMarketplaceEligibleTargets?.has(candidate) ?? true));
2961
+ };
2962
+ const nativeSkillOverlay = async (target) => {
2963
+ const canonicalMarketplaceRoot = await canonicalizePathThroughExistingAncestor(target.identity.marketplaceRoot ?? resolveAwHome(options.context.env));
2964
+ const nativePluginRoot = await canonicalizePathThroughExistingAncestor(path.join(canonicalMarketplaceRoot, 'plugins', target.identity.pluginName));
2965
+ const desiredSkillPaths = new Set((target.desiredOwnedComponents ?? [])
2966
+ .filter((component) => component.kind === 'skill-directory')
2967
+ .map((component) => path.resolve(component.path)));
2968
+ const enabledSkillPaths = new Set(target.artifacts
2969
+ .filter((artifact) => artifact.kind === 'skill' && artifact.state !== 'disabled')
2970
+ .map((artifact) => path.resolve(artifact.path)));
2971
+ return {
2972
+ marketplaceRoot: canonicalMarketplaceRoot,
2973
+ writes: (target.operationActions ?? []).filter((action) => action.kind === 'writeOwnedArtifact'
2974
+ && action.artifactKind === 'skill-directory'
2975
+ && desiredSkillPaths.has(path.resolve(action.path))
2976
+ && enabledSkillPaths.has(path.resolve(action.path))
2977
+ && target.identity.scope === 'user'
2978
+ && !path.relative(nativePluginRoot, action.path).startsWith('..')),
2979
+ };
2980
+ };
2981
+ const asNativeGroupFailure = (error) => {
2982
+ const result = error instanceof Error ? error : new Error(String(error));
2983
+ return Object.assign(result, { nativeMarketplaceGroupFailure: true });
2984
+ };
3151
2985
  const findTarget = (component) => {
3152
2986
  const target = options.targets.find((entry) => entry.identity.pluginName === component.identity.plugin
3153
2987
  && entry.identity.sourceId === component.identity.sourceId
@@ -3157,13 +2991,143 @@ function createNativeDriver(options) {
3157
2991
  throw new Error('Ownership executor cannot resolve its desired target.');
3158
2992
  return target;
3159
2993
  };
2994
+ const codexStateKey = (target, pluginId) => `${nativeGroupKey(target)}\0${pluginId}`;
2995
+ const runObservedCodexMutation = async (options_) => runCodexMutationWithInvalidation({
2996
+ ...options_,
2997
+ ...(options.operationScopedNativePhases
2998
+ ? {}
2999
+ : { observationSession: options.context.codexObservationSession }),
3000
+ });
3001
+ const prepareNativeGroup = async (target) => {
3002
+ const key = nativeGroupKey(target);
3003
+ let preparation = options.operationScopedNativePhases
3004
+ ? nativeGroupPreparations.get(key)
3005
+ : nativeTargetPreparations.get(target);
3006
+ if (!preparation) {
3007
+ preparation = (async () => {
3008
+ const members = groupTargets(target);
3009
+ const materializing = members.filter((candidate) => candidate.writeNativeRequired ?? candidate.requiresNative);
3010
+ const overlayTargets = members.filter((candidate) => candidate.nativeOverlayDesired ?? candidate.requiresNative);
3011
+ if (target.identity.platform === 'codex') {
3012
+ const observationScope = codexObservationScope(options.context, target.identity.marketplaceName ?? MARKETPLACE_NAME, members.some((candidate) => candidate.identity.scope === 'project') ? 'project' : 'user');
3013
+ const nativePluginIds = [...new Set(members.flatMap((candidate) => candidate.ownedComponents.flatMap((component) => component.kind === 'native-plugin' ? [component.pluginId] : [])))].sort();
3014
+ for (const pluginId of nativePluginIds) {
3015
+ const member = members.find((candidate) => candidate.ownedComponents.some((component) => component.kind === 'native-plugin' && component.pluginId === pluginId));
3016
+ const registration = await inspectCodexPluginRegistrationLive({
3017
+ pluginId,
3018
+ cwd: member.identity.cwd,
3019
+ env: options.context.env,
3020
+ nativeCliRunner: runner,
3021
+ manager: currentCodexManager(),
3022
+ scope: observationScope,
3023
+ observationSession: options.context.codexObservationSession,
3024
+ });
3025
+ codexNativeStates.set(codexStateKey(member, pluginId), requireObservedCodexNativeState(registration.observation, 'native group precheck') === 'installed' ? 'present' : 'absent');
3026
+ }
3027
+ }
3028
+ if (materializing.length === 0)
3029
+ return;
3030
+ const first = materializing[0];
3031
+ if (!first.catalog)
3032
+ throw new Error('Missing rebuild target catalog data.');
3033
+ const catalogRoot = path.resolve(first.catalog.rootDir);
3034
+ if (overlayTargets.some((candidate) => !candidate.catalog || path.resolve(candidate.catalog.rootDir) !== catalogRoot)) {
3035
+ throw new Error('nativeMarketplaceGroupInvalid: grouped targets do not share one Catalog root.');
3036
+ }
3037
+ const materializationTargets = [...overlayTargets].sort((left, right) => [
3038
+ left.identity.pluginId ?? left.identity.pluginName,
3039
+ left.identity.scope,
3040
+ left.identity.cwd,
3041
+ ].join('\0').localeCompare([
3042
+ right.identity.pluginId ?? right.identity.pluginName,
3043
+ right.identity.scope,
3044
+ right.identity.cwd,
3045
+ ].join('\0'), 'en'));
3046
+ const nativeSkillOverlays = new Map(await Promise.all(materializationTargets.map(async (candidate) => [
3047
+ candidate,
3048
+ await nativeSkillOverlay(candidate),
3049
+ ])));
3050
+ const transaction = await mirrorCatalog(first.catalog, options.context.env, first.identity, first.plugin.catalogLogicalIdentity, first.identity.platform === 'codex' ? async (stagingRoot) => {
3051
+ for (const candidate of materializationTargets) {
3052
+ const overlay = nativeSkillOverlays.get(candidate);
3053
+ const requiredSkillWrites = overlay.writes;
3054
+ for (const action of requiredSkillWrites) {
3055
+ await applyPlannedOperationAction({
3056
+ ...action,
3057
+ path: path.join(stagingRoot, path.relative(overlay.marketplaceRoot, action.path)),
3058
+ allowedRoot: path.join(stagingRoot, path.relative(overlay.marketplaceRoot, action.allowedRoot)),
3059
+ });
3060
+ }
3061
+ const hasStaticSkills = candidate.plugin.manifest.skills !== undefined
3062
+ || requiredSkillWrites.length > 0;
3063
+ const preparedNativeRender = await requirePreparedCodexNativeRender({
3064
+ context: options.context,
3065
+ plugin: candidate.plugin,
3066
+ });
3067
+ const nativeRender = preparedNativeRender
3068
+ ? await materializeCodexNativeRender({
3069
+ plugin: candidate.plugin,
3070
+ stagingRoot,
3071
+ prepared: preparedNativeRender,
3072
+ hasStaticSkills,
3073
+ })
3074
+ : null;
3075
+ if (!nativeRender) {
3076
+ await ensureCodexActiveSkillsManifest({
3077
+ plugin: candidate.plugin,
3078
+ marketplaceRoot: stagingRoot,
3079
+ hasRequiredSkills: hasStaticSkills,
3080
+ });
3081
+ }
3082
+ if (pluginHasCodexNativeSkillSurface(candidate.plugin)) {
3083
+ await assertNoSensitiveValuesInCodexNativePayload({
3084
+ plugin: candidate.plugin,
3085
+ payloadRoot: path.join(stagingRoot, 'plugins', candidate.identity.pluginName),
3086
+ allowedRoot: stagingRoot,
3087
+ sensitiveValues: await resolveSensitiveUserConfigValues(candidate.plugin, options.context.env),
3088
+ });
3089
+ }
3090
+ }
3091
+ } : undefined);
3092
+ try {
3093
+ await transaction.commit();
3094
+ }
3095
+ catch (error) {
3096
+ if (error.durableMaterializeCommit !== true) {
3097
+ try {
3098
+ await transaction.rollback();
3099
+ }
3100
+ catch (rollbackError) {
3101
+ throw new AggregateError([error, rollbackError], 'nativeMarketplaceGroupCommitFailed: marketplace commit and rollback failed.');
3102
+ }
3103
+ }
3104
+ throw error;
3105
+ }
3106
+ for (const candidate of materializationTargets) {
3107
+ for (const action of nativeSkillOverlays.get(candidate)?.writes ?? []) {
3108
+ completedNativeSkillWrites.add(action);
3109
+ }
3110
+ }
3111
+ committedNativeGroups.add(key);
3112
+ })().catch((error) => {
3113
+ throw asNativeGroupFailure(error);
3114
+ });
3115
+ if (options.operationScopedNativePhases) {
3116
+ nativeGroupPreparations.set(key, preparation);
3117
+ }
3118
+ else {
3119
+ nativeTargetPreparations.set(target, preparation);
3120
+ }
3121
+ }
3122
+ await preparation;
3123
+ };
3160
3124
  const inspectCodexMarketplace = async (component) => {
3161
3125
  if (component.locator.kind !== 'codex-marketplace')
3162
3126
  throw new Error('Invalid Codex marketplace locator.');
3163
3127
  const target = findTarget(component);
3164
3128
  const configPath = resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath;
3165
- const marketplace = (await readCodexConfigFile(configPath))
3166
- .snapshot.marketplaces[component.locator.marketplaceName];
3129
+ const config = await readCodexConfigFile(configPath);
3130
+ const marketplace = config.snapshot.marketplaces[component.locator.marketplaceName];
3167
3131
  const canonical = !marketplace
3168
3132
  ? 'absent'
3169
3133
  : marketplace.sourceType === 'local'
@@ -3171,29 +3135,47 @@ function createNativeDriver(options) {
3171
3135
  && path.normalize(marketplace.source ?? '') === path.normalize(component.locator.marketplaceRoot)
3172
3136
  ? 'present'
3173
3137
  : 'foreign';
3174
- const plugin = await inspectCodexNativeResidue({
3175
- pluginName: component.identity.plugin,
3176
- cwd: target.identity.cwd,
3177
- env: options.context.env,
3178
- marketplaceName: component.locator.marketplaceName,
3179
- marketplaceRoot: component.locator.marketplaceRoot,
3180
- });
3181
- if (plugin.state === 'foreign')
3182
- return 'foreign';
3183
3138
  if (component.locator.desired) {
3184
- return canonical === 'present' && plugin.state === 'present' && plugin.enabled
3185
- ? 'present'
3186
- : 'absent';
3139
+ if (canonical !== 'present')
3140
+ return canonical;
3141
+ const pluginId = `${component.identity.plugin}@${component.locator.marketplaceName}`;
3142
+ return config.snapshot.plugins[pluginId]?.enabled === true ? 'present' : 'absent';
3187
3143
  }
3188
3144
  if (canonical !== 'present')
3189
3145
  return canonical;
3190
- const remaining = await discoverCodexNativeResidueNames({
3191
- cwd: target.identity.cwd,
3192
- env: options.context.env,
3193
- marketplaceName: component.locator.marketplaceName,
3194
- marketplaceRoot: component.locator.marketplaceRoot,
3195
- });
3196
- return remaining.some((name) => name !== component.identity.plugin) ? 'absent' : 'present';
3146
+ const suffix = `@${component.locator.marketplaceName}`;
3147
+ const currentPluginId = `${component.identity.plugin}${suffix}`;
3148
+ const projectConfig = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'project', options.context.env).configTomlPath);
3149
+ const configuredPluginIds = [
3150
+ ...Object.keys(config.snapshot.plugins),
3151
+ ...Object.keys(projectConfig.snapshot.plugins),
3152
+ ].filter((pluginId) => pluginId.endsWith(suffix));
3153
+ if (configuredPluginIds.some((pluginId) => pluginId !== currentPluginId))
3154
+ return 'absent';
3155
+ const ownedSession = options.context.codexObservationSession ? undefined
3156
+ : await createCodexNativeObservationSession({
3157
+ exactCwd: target.identity.cwd,
3158
+ env: options.context.env,
3159
+ runner,
3160
+ manager: currentCodexManager() ?? SYSTEM_CODEX_MANAGER,
3161
+ });
3162
+ const session = options.context.codexObservationSession ?? ownedSession;
3163
+ try {
3164
+ const observationScope = observationScopeFor(target);
3165
+ const snapshot = await session.snapshot(component.locator.marketplaceName, observationScope);
3166
+ if (!snapshot.neutral || (observationScope === 'both' && !snapshot.exact))
3167
+ return 'absent';
3168
+ const officialPluginIds = new Set([
3169
+ ...(snapshot.exact?.keys() ?? []),
3170
+ ...snapshot.neutral.keys(),
3171
+ ]);
3172
+ return [...officialPluginIds].some((pluginId) => pluginId !== currentPluginId)
3173
+ ? 'absent'
3174
+ : 'present';
3175
+ }
3176
+ finally {
3177
+ await ownedSession?.dispose();
3178
+ }
3197
3179
  };
3198
3180
  const inspectClaudeMarketplace = async (component) => {
3199
3181
  if (component.locator.kind !== 'claude-marketplace')
@@ -3335,6 +3317,15 @@ function createNativeDriver(options) {
3335
3317
  }) ? { removeAgentRegistrations: [locator.roleKey] } : null);
3336
3318
  return;
3337
3319
  case 'codex-marketplace': {
3320
+ if (locator.desired)
3321
+ return;
3322
+ const target = findTarget(component);
3323
+ await prepareNativeGroup(target);
3324
+ codexMutationMarketplaces.add(locator.marketplaceName);
3325
+ await runObservedCodexMutation({
3326
+ marketplaceName: locator.marketplaceName,
3327
+ mutation: () => runNativeCliChecked(buildCodexMarketplaceRemoveInvocation(currentCodexManager() ?? SYSTEM_CODEX_MANAGER, locator.marketplaceName, target.identity.cwd, options.context.env), runner),
3328
+ });
3338
3329
  return;
3339
3330
  }
3340
3331
  case 'claude-marketplace': {
@@ -3384,7 +3375,113 @@ function createNativeDriver(options) {
3384
3375
  };
3385
3376
  return {
3386
3377
  ownership,
3378
+ setNativeMarketplaceEligibleTargets: (eligibleTargets) => {
3379
+ nativeMarketplaceEligibleTargets = new Set(eligibleTargets);
3380
+ },
3381
+ prepareNativeMarketplaceGroup: prepareNativeGroup,
3382
+ selectRecoveryCleanupComponents: (target, components) => {
3383
+ if (!committedNativeGroups.has(nativeGroupKey(target)))
3384
+ return components;
3385
+ const marketplaceRoot = path.resolve(target.identity.marketplaceRoot ?? resolveAwHome(options.context.env));
3386
+ return components.filter((component) => {
3387
+ if (!('path' in component))
3388
+ return true;
3389
+ const relative = path.relative(marketplaceRoot, path.resolve(component.path));
3390
+ return relative === '..'
3391
+ || relative.startsWith(`..${path.sep}`)
3392
+ || path.isAbsolute(relative);
3393
+ });
3394
+ },
3395
+ ...(options.operationScopedNativePhases ? {
3396
+ deferPostcheck: (target, postcheck) => {
3397
+ if (target.identity.platform !== 'codex')
3398
+ return false;
3399
+ deferredPostchecks.push({
3400
+ target,
3401
+ postcheck: async () => {
3402
+ if (target.requiresNative) {
3403
+ const marketplaceName = target.identity.marketplaceName ?? MARKETPLACE_NAME;
3404
+ const pluginId = `${target.identity.pluginName}@${marketplaceName}`;
3405
+ const registration = await inspectCodexPluginRegistrationLive({
3406
+ pluginId,
3407
+ cwd: target.identity.cwd,
3408
+ env: options.context.env,
3409
+ nativeCliRunner: runner,
3410
+ manager: currentCodexManager(),
3411
+ scope: observationScopeFor(target),
3412
+ observationSession: options.context.codexObservationSession,
3413
+ });
3414
+ const state = requireObservedCodexNativeState(registration.observation, 'native install postcheck');
3415
+ requireCodexObservationCoverage(registration, observationScopeFor(target), 'native install postcheck');
3416
+ if (state !== 'installed') {
3417
+ throw new Error(`codexNativeInstallIncomplete: ${pluginId} is absent from Codex plugin list.`);
3418
+ }
3419
+ const installedEntry = registration.observation.state === 'observed'
3420
+ && registration.observation.value === 'installed'
3421
+ ? registration.observation.entry
3422
+ : undefined;
3423
+ if (installedEntry?.version !== undefined
3424
+ && installedEntry.version !== target.identity.pluginVersion) {
3425
+ throw new Error(`codexNativeInstallIncomplete: ${pluginId} version is ${installedEntry.version}; expected ${target.identity.pluginVersion}.`);
3426
+ }
3427
+ }
3428
+ await postcheck();
3429
+ },
3430
+ });
3431
+ return true;
3432
+ },
3433
+ completePostchecks: async () => {
3434
+ const pending = deferredPostchecks;
3435
+ deferredPostchecks = [];
3436
+ const failures = [];
3437
+ for (const entry of pending) {
3438
+ try {
3439
+ await entry.postcheck();
3440
+ }
3441
+ catch (error) {
3442
+ failures.push({ target: entry.target, error });
3443
+ }
3444
+ }
3445
+ return failures;
3446
+ },
3447
+ } : {}),
3448
+ captureCodexPostMutationSnapshots: async (marketplaceNames = []) => {
3449
+ for (const marketplaceName of marketplaceNames) {
3450
+ codexMutationMarketplaces.add(marketplaceName);
3451
+ }
3452
+ if (codexMutationMarketplaces.size === 0)
3453
+ return;
3454
+ const session = options.context.codexObservationSession;
3455
+ if (!session) {
3456
+ throw new Error('codexNativeObservationUnavailable: grouped postcheck requires an observation session.');
3457
+ }
3458
+ const marketplaces = [...codexMutationMarketplaces].sort();
3459
+ codexMutationMarketplaces.clear();
3460
+ for (const marketplaceName of marketplaces)
3461
+ session.invalidate(marketplaceName);
3462
+ await Promise.all(marketplaces.map((marketplaceName) => session.snapshot(marketplaceName, options.context.codexObservationScopeByMarketplace?.get(marketplaceName)
3463
+ ?? (options.targets.some((target) => (nativeMarketplaceEligibleTargets?.has(target) ?? true)
3464
+ && target.identity.platform === 'codex'
3465
+ && (target.identity.marketplaceName ?? MARKETPLACE_NAME) === marketplaceName
3466
+ && target.identity.scope === 'project')
3467
+ ? 'both'
3468
+ : 'user'))));
3469
+ },
3387
3470
  preflightMutation: async (target) => {
3471
+ if (!target.codexNativeObservationExpectation
3472
+ && !target.codexNativeSensitivePreflightRequired)
3473
+ return;
3474
+ const sensitiveValues = pluginHasCodexNativeSkillSurface(target.plugin)
3475
+ ? await resolveSensitiveUserConfigValues(target.plugin, options.context.env)
3476
+ : [];
3477
+ if (sensitiveValues.length > 0) {
3478
+ await assertNoSensitiveValuesInCanonicalCodexNativePayload({
3479
+ plugin: target.plugin,
3480
+ marketplaceRoot: target.identity.marketplaceRoot ?? resolveAwHome(options.context.env),
3481
+ env: options.context.env,
3482
+ sensitiveValues,
3483
+ });
3484
+ }
3388
3485
  if (!target.codexNativeObservationExpectation)
3389
3486
  return;
3390
3487
  const pluginId = `${target.identity.pluginName}@${target.identity.marketplaceName ?? MARKETPLACE_NAME}`;
@@ -3394,6 +3491,8 @@ function createNativeDriver(options) {
3394
3491
  env: options.context.env,
3395
3492
  nativeCliRunner: runner,
3396
3493
  manager: currentCodexManager(),
3494
+ scope: observationScopeFor(target),
3495
+ observationSession: options.context.codexObservationSession,
3397
3496
  });
3398
3497
  const observed = requireObservedCodexNativeState(registration.observation, 'artifact write precheck');
3399
3498
  if (target.codexNativeObservationExpectation === 'installed' && observed !== 'installed') {
@@ -3427,20 +3526,13 @@ function createNativeDriver(options) {
3427
3526
  || (Object.hasOwn(config.snapshot.plugins, expectedPluginId) && !canonical)) {
3428
3527
  throw new Error(`externalOwnershipPresent: Codex marketplace ${marketplaceName} has a foreign source instead of its canonical namespaced root.`);
3429
3528
  }
3430
- },
3431
- commitMaterialize: async (target) => {
3432
- const transaction = mirrorTransactions.get(target);
3433
- if (!transaction)
3434
- return;
3435
- await transaction.commit();
3436
- mirrorTransactions.delete(target);
3437
- },
3438
- rollbackMaterialize: async (target) => {
3439
- const transaction = mirrorTransactions.get(target);
3440
- if (!transaction)
3441
- return;
3442
- await transaction.rollback();
3443
- mirrorTransactions.delete(target);
3529
+ if (target.nativeOverlayDesired) {
3530
+ await prepareCodexNativeRenderCached({
3531
+ context: options.context,
3532
+ plugin: target.plugin,
3533
+ scriptTimeoutMs: options.scriptTimeoutMs,
3534
+ });
3535
+ }
3444
3536
  },
3445
3537
  finalizeCleanup: async () => undefined,
3446
3538
  probeNative: async (component) => {
@@ -3459,12 +3551,17 @@ function createNativeDriver(options) {
3459
3551
  return state.pluginState;
3460
3552
  }
3461
3553
  if (target.identity.platform === 'codex') {
3554
+ const pending = pendingCodexNativeStates.get(codexStateKey(target, component.pluginId));
3555
+ if (pending)
3556
+ return pending;
3462
3557
  const registration = await inspectCodexPluginRegistrationLive({
3463
3558
  pluginId: component.pluginId,
3464
3559
  cwd: target.identity.cwd,
3465
3560
  env: options.context.env,
3466
3561
  nativeCliRunner: runner,
3467
3562
  manager: currentCodexManager(),
3563
+ scope: observationScopeFor(target),
3564
+ observationSession: options.context.codexObservationSession,
3468
3565
  });
3469
3566
  return observedCodexNativeState(registration.observation, 'native probe');
3470
3567
  }
@@ -3495,41 +3592,20 @@ function createNativeDriver(options) {
3495
3592
  env: options.context.env,
3496
3593
  nativeCliRunner: runner,
3497
3594
  manager: currentCodexManager(),
3595
+ scope: observationScopeFor(target),
3596
+ observationSession: options.context.codexObservationSession,
3498
3597
  });
3499
3598
  const nativeState = observedCodexNativeState(registration.observation, 'native health postcheck');
3500
3599
  if (nativeState !== 'present')
3501
3600
  return nativeState;
3502
- if (!registration.observations[target.identity.scope]
3503
- || registration.activations[target.identity.scope] !== true)
3504
- return 'partial';
3505
- const codexManager = currentCodexManager();
3506
- if (target.identity.scope !== 'project' || !codexManager)
3507
- return 'present';
3508
3601
  const userConfig = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath);
3602
+ const userEnabled = userConfig.snapshot.plugins[pluginId]?.enabled === true;
3603
+ if (target.identity.scope === 'user')
3604
+ return userEnabled ? 'present' : 'partial';
3509
3605
  const projectConfig = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'project', options.context.env).configTomlPath);
3510
- const neutralCwd = await fs.mkdtemp(path.join(os.tmpdir(), 'aw-codex-postcheck-'));
3511
- try {
3512
- const invocations = buildCodexProjectPluginListInvocations(codexManager, {
3513
- exactCwd: target.identity.cwd,
3514
- neutralCwd,
3515
- env: options.context.env,
3516
- });
3517
- const [exactResult, neutralResult] = await Promise.all([
3518
- runNativeCliChecked(invocations.exact, runner),
3519
- runNativeCliChecked(invocations.neutral, runner),
3520
- ]);
3521
- postcheckCodexProjectPluginActivation({
3522
- pluginId,
3523
- userEnabled: userConfig.snapshot.plugins[pluginId]?.enabled === true,
3524
- projectEnabled: projectConfig.snapshot.plugins[pluginId]?.enabled === true,
3525
- exactResult,
3526
- neutralResult,
3527
- });
3528
- }
3529
- finally {
3530
- await fs.remove(neutralCwd);
3531
- }
3532
- return 'present';
3606
+ return userEnabled || projectConfig.snapshot.plugins[pluginId]?.enabled === true
3607
+ ? 'present'
3608
+ : 'partial';
3533
3609
  }
3534
3610
  return 'absent';
3535
3611
  },
@@ -3537,33 +3613,51 @@ function createNativeDriver(options) {
3537
3613
  const target = findTarget(component);
3538
3614
  if (!target)
3539
3615
  throw new Error('Native ownership removal cannot resolve its desired target.');
3616
+ await prepareNativeGroup(target);
3540
3617
  if (target.identity.platform === 'codex') {
3541
- const registration = await inspectCodexPluginRegistrationLive({
3542
- pluginId: component.pluginId,
3543
- cwd: target.identity.cwd,
3544
- env: options.context.env,
3545
- nativeCliRunner: runner,
3546
- manager: currentCodexManager(),
3547
- });
3548
- const before = observedCodexNativeState(registration.observation, 'native remove precheck');
3618
+ const marketplaceName = component.pluginId.slice(component.pluginId.lastIndexOf('@') + 1);
3619
+ const stateKey = codexStateKey(target, component.pluginId);
3620
+ const before = pendingCodexNativeStates.get(stateKey) ?? codexNativeStates.get(stateKey);
3621
+ if (!before) {
3622
+ throw new Error('codexNativeObservationUnavailable: native group precheck is missing.');
3623
+ }
3549
3624
  const removalRequired = before === 'present';
3550
3625
  if (!removalRequired)
3551
3626
  return;
3552
3627
  try {
3553
- await runNativeCliChecked(buildCodexPluginRemoveInvocation(requireCodexManager(currentCodexManager()), component.pluginId, target.identity.cwd, options.context.env), runner);
3628
+ codexMutationMarketplaces.add(marketplaceName);
3629
+ await runObservedCodexMutation({
3630
+ marketplaceName,
3631
+ mutation: () => runNativeCliChecked(buildCodexPluginRemoveInvocation(requireCodexManager(currentCodexManager()), component.pluginId, target.identity.cwd, options.context.env), runner),
3632
+ });
3554
3633
  }
3555
3634
  catch {
3556
3635
  throw new Error(`codexNativeRemoveFailed: ${CODEX_REMOVE_RECOVERY}`);
3557
3636
  }
3558
- const after = await inspectCodexPluginRegistrationLive({
3559
- pluginId: component.pluginId,
3560
- cwd: target.identity.cwd,
3561
- env: options.context.env,
3562
- nativeCliRunner: runner,
3563
- manager: currentCodexManager(),
3564
- });
3565
- if (observedCodexNativeState(after.observation, 'native remove postcheck') !== 'absent') {
3566
- throw new Error(`codexNativeRemoveIncomplete: ${CODEX_REMOVE_RECOVERY}`);
3637
+ pendingCodexNativeStates.set(stateKey, 'absent');
3638
+ const postcheck = async () => {
3639
+ const after = await inspectCodexPluginRegistrationLive({
3640
+ pluginId: component.pluginId,
3641
+ cwd: target.identity.cwd,
3642
+ env: options.context.env,
3643
+ nativeCliRunner: runner,
3644
+ manager: currentCodexManager(),
3645
+ scope: observationScopeFor(target),
3646
+ observationSession: options.context.codexObservationSession,
3647
+ });
3648
+ const expected = pendingCodexNativeStates.get(stateKey) ?? 'absent';
3649
+ const observed = requireObservedCodexNativeState(after.observation, 'native mutation postcheck');
3650
+ if ((observed === 'installed' ? 'present' : 'absent') !== expected) {
3651
+ throw new Error(expected === 'absent'
3652
+ ? `codexNativeRemoveIncomplete: ${CODEX_REMOVE_RECOVERY}`
3653
+ : `codexNativeInstallIncomplete: ${component.pluginId} is absent from Codex plugin list.`);
3654
+ }
3655
+ };
3656
+ if (options.operationScopedNativePhases) {
3657
+ deferredPostchecks.push({ target, postcheck });
3658
+ }
3659
+ else {
3660
+ await postcheck();
3567
3661
  }
3568
3662
  }
3569
3663
  else {
@@ -3595,169 +3689,106 @@ function createNativeDriver(options) {
3595
3689
  writeNative: async (target) => {
3596
3690
  if (!target.catalog)
3597
3691
  throw new Error('Missing rebuild target catalog data.');
3598
- const catalog = target.catalog;
3599
3692
  const marketplaceName = target.identity.marketplaceName ?? MARKETPLACE_NAME;
3600
3693
  const expectedMarketplaceRoot = target.identity.marketplaceRoot ?? resolveAwHome(options.context.env);
3601
- const writeGeneration = async () => {
3602
- if (mirrorTransactions.has(target)) {
3603
- throw new Error('Marketplace mirror transaction is already active for this target.');
3694
+ if (target.identity.platform === 'codex') {
3695
+ const config = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath);
3696
+ const marketplace = config.snapshot.marketplaces[marketplaceName];
3697
+ if (marketplace && (marketplace.sourceType !== 'local'
3698
+ || marketplace.sourcePointsToManifest
3699
+ || path.normalize(marketplace.source ?? '') !== path.normalize(expectedMarketplaceRoot))) {
3700
+ throw new Error(`externalOwnershipPresent: Codex marketplace ${marketplaceName} 已由 foreign source 占用,拒绝覆盖。`);
3604
3701
  }
3605
- {
3606
- if (target.identity.platform === 'codex') {
3607
- const config = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath);
3608
- const marketplace = config.snapshot.marketplaces[marketplaceName];
3609
- if (marketplace && (marketplace.sourceType !== 'local'
3610
- || marketplace.sourcePointsToManifest
3611
- || path.normalize(marketplace.source ?? '') !== path.normalize(expectedMarketplaceRoot))) {
3612
- throw new Error(`externalOwnershipPresent: Codex marketplace ${marketplaceName} 已由 foreign source 占用,拒绝覆盖。`);
3613
- }
3614
- }
3615
- else {
3616
- const state = await readClaudeNativeState({
3617
- pluginName: target.identity.pluginName,
3618
- scope: target.identity.scope,
3619
- cwd: target.identity.cwd,
3620
- env: options.context.env,
3621
- marketplaceName,
3622
- marketplaceRoot: expectedMarketplaceRoot,
3623
- });
3624
- if (state.marketplaceState === 'foreign') {
3625
- throw new Error(`Claude marketplace ${marketplaceName} 已由 foreign source 占用,拒绝覆盖。`);
3626
- }
3627
- }
3628
- const pluginId = `${target.identity.pluginName}@${marketplaceName}`;
3629
- let codexRemovalRequired = false;
3630
- if (target.identity.platform === 'codex') {
3631
- const registration = await inspectCodexPluginRegistrationLive({
3632
- pluginId,
3633
- cwd: target.identity.cwd,
3634
- env: options.context.env,
3635
- nativeCliRunner: runner,
3636
- manager: currentCodexManager(),
3637
- });
3638
- codexRemovalRequired = requireObservedCodexNativeState(registration.observation, 'native write precheck') === 'installed';
3639
- }
3640
- const canonicalMarketplaceRoot = await canonicalizePathThroughExistingAncestor(expectedMarketplaceRoot);
3641
- const nativePluginRoot = await canonicalizePathThroughExistingAncestor(path.join(canonicalMarketplaceRoot, 'plugins', target.identity.pluginName));
3642
- const desiredSkillPaths = new Set((target.desiredOwnedComponents ?? [])
3643
- .filter((component) => component.kind === 'skill-directory')
3644
- .map((component) => path.resolve(component.path)));
3645
- const enabledSkillPaths = new Set(target.artifacts
3646
- .filter((artifact) => artifact.kind === 'skill' && artifact.state !== 'disabled')
3647
- .map((artifact) => path.resolve(artifact.path)));
3648
- const requiredSkillWrites = (target.operationActions ?? []).filter((action) => action.kind === 'writeOwnedArtifact'
3649
- && action.artifactKind === 'skill-directory'
3650
- && desiredSkillPaths.has(path.resolve(action.path))
3651
- && enabledSkillPaths.has(path.resolve(action.path))
3652
- && target.identity.scope === 'user'
3653
- && !path.relative(nativePluginRoot, action.path).startsWith('..'));
3654
- const staged = { nativeRender: null };
3655
- let skillsRelativeRoot = null;
3656
- const mirrored = await mirrorCatalog(catalog, options.context.env, target.identity, target.identity.platform === 'codex' ? async (stagingRoot) => {
3657
- for (const action of requiredSkillWrites) {
3658
- await applyPlannedOperationAction({
3659
- ...action,
3660
- path: path.join(stagingRoot, path.relative(canonicalMarketplaceRoot, action.path)),
3661
- allowedRoot: path.join(stagingRoot, path.relative(canonicalMarketplaceRoot, action.allowedRoot)),
3662
- });
3663
- }
3664
- const hasStaticSkills = target.plugin.manifest.skills !== undefined
3665
- || requiredSkillWrites.length > 0;
3666
- staged.nativeRender = await materializeCodexNativeRender({
3667
- plugin: target.plugin,
3668
- stagingRoot,
3669
- env: options.context.env,
3670
- scriptTimeoutMs: options.scriptTimeoutMs,
3671
- hasStaticSkills,
3672
- });
3673
- skillsRelativeRoot = staged.nativeRender
3674
- && (hasStaticSkills || staged.nativeRender.presentSkillKeys.length > 0)
3675
- ? await resolveCodexSkillsRelativeRoot(target.plugin)
3676
- : staged.nativeRender ? null : await ensureCodexActiveSkillsManifest({
3677
- plugin: target.plugin,
3678
- marketplaceRoot: stagingRoot,
3679
- hasRequiredSkills: hasStaticSkills,
3680
- });
3681
- } : undefined);
3682
- mirrorTransactions.set(target, mirrored);
3683
- for (const action of requiredSkillWrites)
3684
- completedNativeSkillWrites.add(action);
3685
- const marketplaceRoot = mirrored.rootDir;
3686
- if (target.identity.platform === 'codex') {
3687
- if (codexRemovalRequired) {
3688
- await runNativeCliChecked(buildCodexPluginRemoveInvocation(requireCodexManager(currentCodexManager()), pluginId, target.identity.cwd, options.context.env), runner);
3689
- }
3690
- const config = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath);
3691
- const marketplace = config.snapshot.marketplaces[marketplaceName];
3692
- if (path.normalize(marketplace?.source ?? '') !== path.normalize(marketplaceRoot)) {
3693
- await runNativeCliChecked(buildCodexMarketplaceAddInvocation(requireCodexManager(currentCodexManager()), marketplaceRoot, target.identity.cwd, options.context.env), runner);
3694
- }
3695
- await runNativeCliChecked(buildCodexMarketplaceListInvocation(requireCodexManager(currentCodexManager()), target.identity.cwd, options.context.env), runner);
3696
- await runNativeCliChecked(buildCodexPluginAddInvocation(requireCodexManager(currentCodexManager()), pluginId, target.identity.cwd, options.context.env), runner);
3697
- await runNativeCliChecked(buildCodexPluginListInvocation(requireCodexManager(currentCodexManager()), target.identity.cwd, options.context.env), runner);
3698
- const installed = await inspectCodexPluginRegistrationLive({
3699
- pluginId,
3700
- cwd: target.identity.cwd,
3701
- env: options.context.env,
3702
- nativeCliRunner: runner,
3703
- manager: currentCodexManager(),
3704
- });
3705
- if (observedCodexNativeState(installed.observation, 'native install postcheck') !== 'present') {
3706
- throw new Error(`codexNativeInstallIncomplete: ${pluginId} is absent from Codex plugin list.`);
3707
- }
3708
- await assertCodexNativeInstallState({
3709
- pluginName: target.identity.pluginName,
3710
- pluginVersion: target.identity.pluginVersion,
3711
- cwd: target.identity.cwd,
3712
- env: options.context.env,
3713
- marketplaceRoot,
3714
- marketplaceName,
3715
- skillsRelativeRoot,
3716
- requiredSkillKeys: [
3717
- ...requiredSkillWrites.flatMap((action) => action.owner?.artifactKey ? [action.owner.artifactKey] : []),
3718
- ...(staged.nativeRender?.presentSkillKeys ?? []),
3719
- ],
3720
- ...(staged.nativeRender ? { nativeRender: staged.nativeRender } : {}),
3721
- });
3722
- }
3723
- else {
3724
- await runNativeCliChecked({
3725
- command: 'claude',
3726
- args: ['plugin', 'marketplace', 'add', marketplaceRoot, '--scope', target.identity.scope],
3727
- cwd: target.identity.cwd,
3728
- env: options.context.env,
3729
- }, runner);
3730
- await runNativeCliChecked({
3731
- command: 'claude',
3732
- args: ['plugin', 'install', pluginId, '--scope', target.identity.scope],
3733
- cwd: target.identity.cwd,
3734
- env: options.context.env,
3735
- }, runner);
3736
- await runNativeCliChecked({
3737
- command: 'claude',
3738
- args: ['plugin', 'list'],
3739
- cwd: target.identity.cwd,
3740
- env: options.context.env,
3741
- }, runner);
3742
- const after = await readClaudeNativeState({
3743
- pluginName: target.identity.pluginName,
3744
- scope: target.identity.scope,
3745
- cwd: target.identity.cwd,
3746
- env: options.context.env,
3747
- marketplaceName,
3748
- marketplaceRoot,
3749
- });
3750
- if (after.marketplaceDeclaredState !== 'canonical'
3751
- || after.marketplaceMaterializedState !== 'canonical'
3752
- || after.pluginState !== 'present'
3753
- || !after.pluginEnabled
3754
- || !after.pluginVersions.includes(target.identity.pluginVersion)) {
3755
- throw new Error(`Claude native install postcheck 失败:declaredMarketplace=${after.marketplaceDeclaredState},materializedMarketplace=${after.marketplaceMaterializedState},plugin=${after.pluginState},enabled=${after.pluginEnabled},versions=${after.pluginVersions.join(',')}。`);
3756
- }
3757
- }
3702
+ }
3703
+ else {
3704
+ const state = await readClaudeNativeState({
3705
+ pluginName: target.identity.pluginName,
3706
+ scope: target.identity.scope,
3707
+ cwd: target.identity.cwd,
3708
+ env: options.context.env,
3709
+ marketplaceName,
3710
+ marketplaceRoot: expectedMarketplaceRoot,
3711
+ });
3712
+ if (state.marketplaceState === 'foreign') {
3713
+ throw new Error(`Claude marketplace ${marketplaceName} 已由 foreign source 占用,拒绝覆盖。`);
3758
3714
  }
3759
- };
3760
- await writeGeneration();
3715
+ }
3716
+ await prepareNativeGroup(target);
3717
+ const pluginId = `${target.identity.pluginName}@${marketplaceName}`;
3718
+ if (target.identity.platform === 'codex') {
3719
+ const stateKey = codexStateKey(target, pluginId);
3720
+ const codexRemovalRequired = pendingCodexNativeStates.get(stateKey)
3721
+ ?? codexNativeStates.get(stateKey);
3722
+ if (!codexRemovalRequired) {
3723
+ throw new Error('codexNativeObservationUnavailable: native group precheck is missing.');
3724
+ }
3725
+ if (codexRemovalRequired === 'present') {
3726
+ codexMutationMarketplaces.add(marketplaceName);
3727
+ await runObservedCodexMutation({
3728
+ marketplaceName,
3729
+ mutation: () => runNativeCliChecked(buildCodexPluginRemoveInvocation(requireCodexManager(currentCodexManager()), pluginId, target.identity.cwd, options.context.env), runner),
3730
+ });
3731
+ pendingCodexNativeStates.set(stateKey, 'absent');
3732
+ }
3733
+ const config = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath);
3734
+ const marketplace = config.snapshot.marketplaces[marketplaceName];
3735
+ if (path.normalize(marketplace?.source ?? '') !== path.normalize(expectedMarketplaceRoot)) {
3736
+ codexMutationMarketplaces.add(marketplaceName);
3737
+ await runObservedCodexMutation({
3738
+ marketplaceName,
3739
+ mutation: () => runNativeCliChecked(buildCodexMarketplaceAddInvocation(requireCodexManager(currentCodexManager()), expectedMarketplaceRoot, target.identity.cwd, options.context.env), runner),
3740
+ });
3741
+ }
3742
+ codexMutationMarketplaces.add(marketplaceName);
3743
+ await runObservedCodexMutation({
3744
+ marketplaceName,
3745
+ mutation: () => runNativeCliChecked(buildCodexPluginAddInvocation(requireCodexManager(currentCodexManager()), pluginId, target.identity.cwd, options.context.env), runner),
3746
+ });
3747
+ pendingCodexNativeStates.set(stateKey, 'present');
3748
+ const installedConfig = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath);
3749
+ const installedMarketplace = installedConfig.snapshot.marketplaces[marketplaceName];
3750
+ if (installedMarketplace?.sourceType !== 'local'
3751
+ || installedMarketplace.sourcePointsToManifest
3752
+ || path.normalize(installedMarketplace.source ?? '') !== path.normalize(expectedMarketplaceRoot)
3753
+ || installedConfig.snapshot.plugins[pluginId]?.enabled !== true) {
3754
+ throw new Error(`codexNativeInstallIncomplete: ${pluginId} public config is not canonical and enabled.`);
3755
+ }
3756
+ }
3757
+ else {
3758
+ await runNativeCliChecked({
3759
+ command: 'claude',
3760
+ args: ['plugin', 'marketplace', 'add', expectedMarketplaceRoot, '--scope', target.identity.scope],
3761
+ cwd: target.identity.cwd,
3762
+ env: options.context.env,
3763
+ }, runner);
3764
+ await runNativeCliChecked({
3765
+ command: 'claude',
3766
+ args: ['plugin', 'install', pluginId, '--scope', target.identity.scope],
3767
+ cwd: target.identity.cwd,
3768
+ env: options.context.env,
3769
+ }, runner);
3770
+ await runNativeCliChecked({
3771
+ command: 'claude',
3772
+ args: ['plugin', 'list'],
3773
+ cwd: target.identity.cwd,
3774
+ env: options.context.env,
3775
+ }, runner);
3776
+ const after = await readClaudeNativeState({
3777
+ pluginName: target.identity.pluginName,
3778
+ scope: target.identity.scope,
3779
+ cwd: target.identity.cwd,
3780
+ env: options.context.env,
3781
+ marketplaceName,
3782
+ marketplaceRoot: expectedMarketplaceRoot,
3783
+ });
3784
+ if (after.marketplaceDeclaredState !== 'canonical'
3785
+ || after.marketplaceMaterializedState !== 'canonical'
3786
+ || after.pluginState !== 'present'
3787
+ || !after.pluginEnabled
3788
+ || !after.pluginVersions.includes(target.identity.pluginVersion)) {
3789
+ throw new Error(`Claude native install postcheck 失败:declaredMarketplace=${after.marketplaceDeclaredState},materializedMarketplace=${after.marketplaceMaterializedState},plugin=${after.pluginState},enabled=${after.pluginEnabled},versions=${after.pluginVersions.join(',')}。`);
3790
+ }
3791
+ }
3761
3792
  },
3762
3793
  writeArtifacts: async (target) => {
3763
3794
  for (const operationAction of target.operationActions ?? []) {
@@ -3771,33 +3802,15 @@ function createNativeDriver(options) {
3771
3802
  }
3772
3803
  return target.artifacts;
3773
3804
  },
3774
- runPostApply: async (invocation) => {
3775
- try {
3776
- await runPostApplyScript({
3777
- plugin: invocation.plugin,
3778
- platform: invocation.target.identity.platform,
3779
- scope: invocation.target.identity.scope,
3780
- cwd: invocation.target.identity.cwd,
3781
- context: options.context,
3782
- artifacts: [...invocation.artifacts],
3783
- components: invocation.components,
3784
- scriptTimeoutMs: options.scriptTimeoutMs,
3785
- });
3786
- }
3787
- catch (error) {
3788
- await options.context.reporter.notice({
3789
- level: 'warning',
3790
- message: error instanceof Error ? error.message : String(error),
3791
- source: {
3792
- plugin: invocation.plugin.manifest.name,
3793
- platform: invocation.target.identity.platform,
3794
- scope: invocation.target.identity.scope,
3795
- action: 'postApply',
3796
- },
3797
- target: 'stderr',
3798
- });
3799
- throw error;
3800
- }
3805
+ runPreApply: async (invocation) => {
3806
+ await runPreApplyScript({
3807
+ plugin: invocation.plugin,
3808
+ platform: invocation.target.identity.platform,
3809
+ scope: invocation.target.identity.scope,
3810
+ cwd: invocation.target.identity.cwd,
3811
+ context: options.context,
3812
+ scriptTimeoutMs: options.scriptTimeoutMs,
3813
+ });
3801
3814
  },
3802
3815
  };
3803
3816
  }
@@ -3806,6 +3819,9 @@ export function createRebuildIntegration(options) {
3806
3819
  const driver = createNativeDriver({ targets, ...options });
3807
3820
  return {
3808
3821
  driver,
3822
+ setNativeMarketplaceEligibleTargets: driver.setNativeMarketplaceEligibleTargets,
3823
+ prepareNativeMarketplaceGroup: driver.prepareNativeMarketplaceGroup,
3824
+ captureCodexPostMutationSnapshots: driver.captureCodexPostMutationSnapshots,
3809
3825
  buildTarget: async (input) => {
3810
3826
  input = {
3811
3827
  ...input,
@@ -3920,7 +3936,11 @@ export function createRebuildIntegration(options) {
3920
3936
  })))).flat();
3921
3937
  const skillRoots = input.platform === 'codex'
3922
3938
  ? input.scope === 'project'
3923
- ? []
3939
+ ? materializing
3940
+ || ((input.command === 'cleanup' || input.command === 'uninstall')
3941
+ && input.readOnlyInspectionPlan !== true)
3942
+ ? [path.join(input.cwd, '.agents', 'skills')]
3943
+ : []
3924
3944
  : [
3925
3945
  path.join(getCodexHome(options.context.env), 'skills'),
3926
3946
  ...(preserveCodexSharedNativePayload || input.readOnlyShallowInspectionPlan === true
@@ -3935,6 +3955,7 @@ export function createRebuildIntegration(options) {
3935
3955
  pluginName: input.plugin.manifest.name,
3936
3956
  platform: input.platform,
3937
3957
  scope: input.scope,
3958
+ recursive: !(input.platform === 'codex' && input.scope === 'project'),
3938
3959
  })));
3939
3960
  const scannedSkills = (await scanSkills()).flat();
3940
3961
  const legacyCodexNativeSkills = input.platform === 'codex'
@@ -3978,15 +3999,14 @@ export function createRebuildIntegration(options) {
3978
3999
  command: input.command,
3979
4000
  });
3980
4001
  if (preserveCodexSharedNativePayload) {
3981
- const nativePluginRoots = await Promise.all([
3982
- path.join(input.marketplaceRoot ?? resolveAwHome(options.context.env), 'plugins', input.plugin.manifest.name),
3983
- path.join(getCodexHome(options.context.env), 'plugins', 'cache', input.marketplaceName ?? MARKETPLACE_NAME, input.plugin.manifest.name),
3984
- ].map((root) => canonicalizePathThroughExistingAncestor(root)));
4002
+ const nativePluginRoot = await canonicalizePathThroughExistingAncestor(path.join(input.marketplaceRoot ?? resolveAwHome(options.context.env), 'plugins', input.plugin.manifest.name));
4003
+ const privateCacheRoot = path.resolve(getCodexHome(options.context.env), 'plugins', 'cache');
3985
4004
  historicalComponents = historicalComponents.filter((component) => {
3986
4005
  if (!('path' in component))
3987
4006
  return true;
3988
- return nativePluginRoots.every((nativePluginRoot) => {
3989
- const relative = path.relative(nativePluginRoot, path.resolve(component.path));
4007
+ const componentPath = path.resolve(component.path);
4008
+ return [nativePluginRoot, privateCacheRoot].every((protectedRoot) => {
4009
+ const relative = path.relative(protectedRoot, componentPath);
3990
4010
  return relative === '..'
3991
4011
  || relative.startsWith(`..${path.sep}`)
3992
4012
  || path.isAbsolute(relative);
@@ -4017,23 +4037,31 @@ export function createRebuildIntegration(options) {
4017
4037
  const nativeDesired = nativeRequired
4018
4038
  && input.command !== 'cleanup'
4019
4039
  && input.command !== 'uninstall';
4020
- const codexNative = input.platform === 'codex' && nativeRequired
4021
- ? await inspectCodexNativeResidue({
4022
- pluginName: input.plugin.manifest.name,
4040
+ const codexRegistration = input.platform === 'codex'
4041
+ && nativeDesired
4042
+ && input.readOnlyInspectionPlan !== true
4043
+ ? await inspectCodexPluginRegistrationLive({
4044
+ pluginId: `${input.plugin.manifest.name}@${input.marketplaceName ?? MARKETPLACE_NAME}`,
4023
4045
  cwd: input.cwd,
4024
4046
  env: options.context.env,
4025
- marketplaceName: input.marketplaceName,
4026
- marketplaceRoot: input.marketplaceRoot,
4047
+ nativeCliRunner: options.context.nativeCliRunner,
4048
+ manager: options.context.codexManager,
4049
+ scope: codexObservationScope(options.context, input.marketplaceName ?? MARKETPLACE_NAME, input.scope),
4050
+ observationSession: options.context.codexObservationSession,
4027
4051
  })
4028
4052
  : undefined;
4029
- const codexNativeExact = codexNative?.state === 'present'
4030
- && codexNative.versions.length > 0
4031
- && codexNative.versions.every((version) => version === input.plugin.manifest.version);
4053
+ const codexNativeExact = codexRegistration?.observation.state === 'observed'
4054
+ && codexRegistration.observation.value === 'installed'
4055
+ && (codexRegistration.observation.entry.version === undefined
4056
+ || codexRegistration.observation.entry.version === input.plugin.manifest.version);
4032
4057
  const materializeNativeRequired = nativeDesired && (input.platform === 'claude'
4033
4058
  || input.command === 'reinstall'
4034
4059
  || !codexNativeExact);
4035
4060
  const nativeCleanupCandidate = input.platform === 'claude'
4036
- && input.includeNativeCleanupCandidate !== false;
4061
+ ? input.includeNativeCleanupCandidate !== false
4062
+ : input.platform === 'codex'
4063
+ && nativeRequired
4064
+ && (input.command === 'cleanup' || input.command === 'uninstall');
4037
4065
  const nativePlugin = (nativeDesired || nativeCleanupCandidate)
4038
4066
  ? [{
4039
4067
  kind: 'native-plugin',
@@ -4046,7 +4074,7 @@ export function createRebuildIntegration(options) {
4046
4074
  && input.command !== 'uninstall';
4047
4075
  const marketplace = input.platform === 'codex'
4048
4076
  && input.scope === 'user'
4049
- && nativeDesired
4077
+ && nativeRequired
4050
4078
  && input.readOnlyShallowInspectionPlan !== true
4051
4079
  ? [createCodexMarketplaceComponent({
4052
4080
  pluginName: input.plugin.manifest.name,
@@ -4116,36 +4144,37 @@ export function createRebuildIntegration(options) {
4116
4144
  ]);
4117
4145
  bindNamespacedOwnerIdentity(allComponents, input);
4118
4146
  const desiredKeys = new Set(desiredComponents.map(componentIdentityKey));
4119
- const operationActions = writes.flatMap((write) => [
4120
- ...(write.action ? [write.action] : []),
4121
- ...(write.additionalActions ?? []),
4122
- ]);
4123
- finalizeOwnedArtifactActions(operationActions);
4124
- if (nativeDesired) {
4125
- operationActions.unshift({
4126
- kind: 'ensureNativeRegistration',
4147
+ const operationActions = [];
4148
+ const preApply = input.plugin.manifest.scripts?.preApply;
4149
+ if (preApply && input.command !== 'cleanup' && input.command !== 'uninstall') {
4150
+ const script = path.resolve(input.plugin.rootDir, preApply.path.replace(/^\.\//u, ''));
4151
+ const scriptDigest = `sha256:${crypto.createHash('sha256')
4152
+ .update(await fs.readFile(script)).digest('hex')}`;
4153
+ operationActions.push({
4154
+ kind: 'runPreApply',
4127
4155
  pluginId: input.pluginId ?? pluginActionId(input.plugin),
4128
4156
  platform: input.platform,
4129
4157
  scope: input.scope,
4130
- registration: `${input.plugin.manifest.name}@${input.marketplaceName ?? MARKETPLACE_NAME}`,
4131
- marketplaceName: input.marketplaceName ?? MARKETPLACE_NAME,
4132
- marketplaceRoot: input.marketplaceRoot ?? resolveAwHome(options.context.env),
4158
+ script: preApply.path,
4159
+ scriptDigest,
4133
4160
  });
4134
4161
  }
4135
- const postApply = input.plugin.manifest.scripts?.postApply;
4136
- if (postApply && input.command !== 'cleanup' && input.command !== 'uninstall') {
4137
- const script = path.resolve(input.plugin.rootDir, postApply.path.replace(/^\.\//u, ''));
4138
- const scriptDigest = `sha256:${crypto.createHash('sha256')
4139
- .update(await fs.readFile(script)).digest('hex')}`;
4162
+ if (nativeDesired) {
4140
4163
  operationActions.push({
4141
- kind: 'runPostApply',
4164
+ kind: 'ensureNativeRegistration',
4142
4165
  pluginId: input.pluginId ?? pluginActionId(input.plugin),
4143
4166
  platform: input.platform,
4144
4167
  scope: input.scope,
4145
- script: postApply.path,
4146
- scriptDigest,
4168
+ registration: `${input.plugin.manifest.name}@${input.marketplaceName ?? MARKETPLACE_NAME}`,
4169
+ marketplaceName: input.marketplaceName ?? MARKETPLACE_NAME,
4170
+ marketplaceRoot: input.marketplaceRoot ?? resolveAwHome(options.context.env),
4147
4171
  });
4148
4172
  }
4173
+ operationActions.push(...writes.flatMap((write) => [
4174
+ ...(write.action ? [write.action] : []),
4175
+ ...(write.additionalActions ?? []),
4176
+ ]));
4177
+ finalizeOwnedArtifactActions(operationActions);
4149
4178
  const target = {
4150
4179
  plugin: input.plugin,
4151
4180
  identity: {
@@ -4167,10 +4196,11 @@ export function createRebuildIntegration(options) {
4167
4196
  optionalOwnedComponents: allComponents.filter((component) => optionalComponentKeys.has(componentIdentityKey(component))),
4168
4197
  requiresNative: nativeDesired,
4169
4198
  writeNativeRequired: materializeNativeRequired,
4199
+ nativeOverlayDesired: nativeDesired,
4170
4200
  artifacts: writes.map((write) => write.artifact),
4171
4201
  operationActions,
4172
4202
  catalog: input.catalog,
4173
- postApplySource: input.plugin,
4203
+ preApplySource: input.plugin,
4174
4204
  };
4175
4205
  targets.push(target);
4176
4206
  return target;
@@ -4213,24 +4243,22 @@ export async function inspectRebuildTarget(options) {
4213
4243
  if (!target.requiresNative)
4214
4244
  return { target, inspection };
4215
4245
  if (target.identity.platform === 'codex') {
4216
- const residue = await inspectCodexNativeResidue({
4217
- pluginName: target.identity.pluginName,
4246
+ const nativeInspection = inspection.components.find((component) => component.component.kind === 'native-plugin');
4247
+ if (nativeInspection?.state !== 'present')
4248
+ return { target, inspection };
4249
+ const registration = await inspectCodexPluginRegistrationLive({
4250
+ pluginId: `${target.identity.pluginName}@${target.identity.marketplaceName ?? MARKETPLACE_NAME}`,
4218
4251
  cwd: target.identity.cwd,
4219
4252
  env: options.context.env,
4220
- marketplaceName: target.identity.marketplaceName,
4221
- marketplaceRoot: target.identity.marketplaceRoot,
4253
+ nativeCliRunner: options.context.nativeCliRunner,
4254
+ manager: options.context.codexManager,
4255
+ scope: target.identity.scope,
4256
+ observationSession: options.context.codexObservationSession,
4222
4257
  });
4223
- if (residue.state !== 'present' || !residue.enabled)
4224
- return { target, inspection };
4225
- const installed = [...new Set(residue.installedVersions)];
4226
- if (installed.length > 0) {
4227
- return installed.length === 1
4228
- ? { target, inspection, installedVersion: installed[0] }
4229
- : { target, inspection };
4230
- }
4231
- const cached = [...new Set(residue.cacheVersions)];
4232
- return cached.length === 1
4233
- ? { target, inspection, installedVersion: cached[0] }
4258
+ return registration.observation.state === 'observed'
4259
+ && registration.observation.value === 'installed'
4260
+ && registration.observation.entry.version !== undefined
4261
+ ? { target, inspection, installedVersion: registration.observation.entry.version }
4234
4262
  : { target, inspection };
4235
4263
  }
4236
4264
  const native = await readClaudeNativeState({
@@ -4366,13 +4394,48 @@ export async function discoverRebuildTargetNames(options) {
4366
4394
  }
4367
4395
  }
4368
4396
  else {
4369
- for (const name of await discoverCodexNativeResidueNames({
4370
- cwd: options.cwd,
4397
+ const marketplaceName = options.marketplaceName ?? MARKETPLACE_NAME;
4398
+ const marketplaceRoot = options.marketplaceRoot ?? resolveAwHome(options.env);
4399
+ const marketplace = config.snapshot.marketplaces[marketplaceName];
4400
+ const canonicalMarketplace = marketplace?.sourceType === 'local'
4401
+ && !marketplace.sourcePointsToManifest
4402
+ && path.normalize(marketplace.source ?? '') === path.normalize(marketplaceRoot);
4403
+ const suffix = `@${marketplaceName}`;
4404
+ const configuredPluginIds = Object.keys(config.snapshot.plugins)
4405
+ .filter((pluginId) => pluginId.endsWith(suffix));
4406
+ if (configuredPluginIds.length > 0 && !canonicalMarketplace) {
4407
+ throw new Error(`externalOwnershipPresent: Codex marketplace ${marketplaceName} has a foreign public config source.`);
4408
+ }
4409
+ for (const pluginId of canonicalMarketplace ? configuredPluginIds : []) {
4410
+ const name = pluginId.slice(0, -suffix.length);
4411
+ if (/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name))
4412
+ names.add(name);
4413
+ }
4414
+ const session = await createCodexNativeObservationSession({
4415
+ exactCwd: options.cwd,
4371
4416
  env: options.env,
4372
- marketplaceName: options.marketplaceName,
4373
- marketplaceRoot: options.marketplaceRoot,
4374
- }))
4375
- names.add(name);
4417
+ runner: options.nativeCliRunner,
4418
+ });
4419
+ try {
4420
+ const snapshot = await session.snapshot(marketplaceName, 'user');
4421
+ if (!snapshot.neutral) {
4422
+ if (options.nativeFailureMode !== 'ignore') {
4423
+ throw new Error('codexNativeObservationFailed: cannot discover official Codex plugins.');
4424
+ }
4425
+ }
4426
+ else {
4427
+ for (const pluginId of snapshot.neutral.keys()) {
4428
+ if (!pluginId.endsWith(suffix))
4429
+ continue;
4430
+ const name = pluginId.slice(0, -suffix.length);
4431
+ if (/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name))
4432
+ names.add(name);
4433
+ }
4434
+ }
4435
+ }
4436
+ finally {
4437
+ await session.dispose();
4438
+ }
4376
4439
  }
4377
4440
  }
4378
4441
  else {