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

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 (227) 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 +159 -48
  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 +22 -20
  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 +19 -0
  47. package/dist/src/commands/rebuild-command.js +828 -283
  48. package/dist/src/commands/rebuild-command.js.map +1 -1
  49. package/dist/src/commands/rebuild-integration.d.ts +25 -47
  50. package/dist/src/commands/rebuild-integration.js +1176 -1016
  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 +260 -91
  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 +10 -8
  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 +2065 -857
  85. package/dist/src/tui/console-app-v4.js.map +1 -1
  86. package/dist/src/tui/controller-runtime.js +731 -62
  87. package/dist/src/tui/controller-runtime.js.map +1 -1
  88. package/dist/src/tui/controller.d.ts +156 -10
  89. package/dist/src/tui/controller.js +1187 -163
  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 +7 -2
  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 +366 -99
  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-plan.d.ts +2 -2
  138. package/dist/src/utils/artifact-plan.js +8 -6
  139. package/dist/src/utils/artifact-plan.js.map +1 -1
  140. package/dist/src/utils/artifact-renderer.d.ts +2 -1
  141. package/dist/src/utils/artifact-renderer.js +139 -120
  142. package/dist/src/utils/artifact-renderer.js.map +1 -1
  143. package/dist/src/utils/artifact-request.d.ts +5 -0
  144. package/dist/src/utils/artifact-request.js +40 -0
  145. package/dist/src/utils/artifact-request.js.map +1 -1
  146. package/dist/src/utils/bundle-layout.js +4 -4
  147. package/dist/src/utils/bundle-layout.js.map +1 -1
  148. package/dist/src/utils/catalog-capability-projection.d.ts +8 -0
  149. package/dist/src/utils/catalog-capability-projection.js +14 -0
  150. package/dist/src/utils/catalog-capability-projection.js.map +1 -0
  151. package/dist/src/utils/catalog-generation-resolver.d.ts +14 -2
  152. package/dist/src/utils/catalog-generation-resolver.js +49 -27
  153. package/dist/src/utils/catalog-generation-resolver.js.map +1 -1
  154. package/dist/src/utils/catalog-generation-service.d.ts +8 -1
  155. package/dist/src/utils/catalog-generation-service.js +81 -47
  156. package/dist/src/utils/catalog-generation-service.js.map +1 -1
  157. package/dist/src/utils/catalog-generation-store.d.ts +1 -0
  158. package/dist/src/utils/catalog-generation-store.js +60 -5
  159. package/dist/src/utils/catalog-generation-store.js.map +1 -1
  160. package/dist/src/utils/catalog-operation-context.js +1 -0
  161. package/dist/src/utils/catalog-operation-context.js.map +1 -1
  162. package/dist/src/utils/catalog-public-error.js +4 -0
  163. package/dist/src/utils/catalog-public-error.js.map +1 -1
  164. package/dist/src/utils/catalog-source-impact.d.ts +3 -2
  165. package/dist/src/utils/catalog-source-impact.js +142 -20
  166. package/dist/src/utils/catalog-source-impact.js.map +1 -1
  167. package/dist/src/utils/catalog-source-references.js +95 -9
  168. package/dist/src/utils/catalog-source-references.js.map +1 -1
  169. package/dist/src/utils/catalog-sources.d.ts +1 -1
  170. package/dist/src/utils/catalog-sources.js +19 -3
  171. package/dist/src/utils/catalog-sources.js.map +1 -1
  172. package/dist/src/utils/codex-manager.js +8 -0
  173. package/dist/src/utils/codex-manager.js.map +1 -1
  174. package/dist/src/utils/codex-native-observation.d.ts +34 -1
  175. package/dist/src/utils/codex-native-observation.js +132 -0
  176. package/dist/src/utils/codex-native-observation.js.map +1 -1
  177. package/dist/src/utils/codex-reconciler.d.ts +0 -2
  178. package/dist/src/utils/codex-reconciler.js.map +1 -1
  179. package/dist/src/utils/codex-shared-native.d.ts +2 -26
  180. package/dist/src/utils/codex-shared-native.js +8 -82
  181. package/dist/src/utils/codex-shared-native.js.map +1 -1
  182. package/dist/src/utils/git-catalog-provider.d.ts +3 -0
  183. package/dist/src/utils/git-catalog-provider.js +40 -1
  184. package/dist/src/utils/git-catalog-provider.js.map +1 -1
  185. package/dist/src/utils/local-marketplace.d.ts +1 -0
  186. package/dist/src/utils/local-marketplace.js +69 -73
  187. package/dist/src/utils/local-marketplace.js.map +1 -1
  188. package/dist/src/utils/multi-source-catalog.js +1 -0
  189. package/dist/src/utils/multi-source-catalog.js.map +1 -1
  190. package/dist/src/utils/native-cli.d.ts +9 -4
  191. package/dist/src/utils/native-cli.js +280 -34
  192. package/dist/src/utils/native-cli.js.map +1 -1
  193. package/dist/src/utils/path-catalog-provider.js +2 -0
  194. package/dist/src/utils/path-catalog-provider.js.map +1 -1
  195. package/dist/src/utils/plugin-config-impact.d.ts +33 -0
  196. package/dist/src/utils/plugin-config-impact.js +298 -0
  197. package/dist/src/utils/plugin-config-impact.js.map +1 -0
  198. package/dist/src/utils/plugin-config-write.d.ts +36 -14
  199. package/dist/src/utils/plugin-config-write.js +709 -225
  200. package/dist/src/utils/plugin-config-write.js.map +1 -1
  201. package/dist/src/utils/plugin-config.d.ts +58 -4
  202. package/dist/src/utils/plugin-config.js +328 -60
  203. package/dist/src/utils/plugin-config.js.map +1 -1
  204. package/dist/src/utils/plugin-scripts.d.ts +20 -12
  205. package/dist/src/utils/plugin-scripts.js +389 -114
  206. package/dist/src/utils/plugin-scripts.js.map +1 -1
  207. package/dist/src/utils/process-tree.d.ts +2 -2
  208. package/dist/src/utils/process-tree.js +1 -1
  209. package/dist/src/utils/process-tree.js.map +1 -1
  210. package/dist/src/utils/project-boundary.d.ts +11 -1
  211. package/dist/src/utils/project-boundary.js +33 -11
  212. package/dist/src/utils/project-boundary.js.map +1 -1
  213. package/dist/src/utils/safe-operation-diagnostic.d.ts +15 -0
  214. package/dist/src/utils/safe-operation-diagnostic.js +33 -0
  215. package/dist/src/utils/safe-operation-diagnostic.js.map +1 -0
  216. package/dist/src/utils/sensitive-content.d.ts +4 -0
  217. package/dist/src/utils/sensitive-content.js +36 -0
  218. package/dist/src/utils/sensitive-content.js.map +1 -0
  219. package/dist/src/utils/windows-claude-shim.d.ts +9 -0
  220. package/dist/src/utils/windows-claude-shim.js +141 -0
  221. package/dist/src/utils/windows-claude-shim.js.map +1 -0
  222. package/dist/vitest.config.js +91 -10
  223. package/dist/vitest.config.js.map +1 -1
  224. package/package.json +9 -9
  225. package/dist/authoring/templates/aw-config-runtime.d.mts +0 -2
  226. package/dist/authoring/templates/aw-config-runtime.mjs +0 -407
  227. 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';
@@ -7,19 +6,20 @@ import { generateInstructionArtifacts, renderCodexManagedInstructionBlock, } fro
7
6
  import { generateCodexPermissionArtifacts, mergeClaudePermissions, } from '../generators/permissions.js';
8
7
  import { buildGeneratedName } from '../generators/shared.js';
9
8
  import { applyConditionalCodexConfigTransactionFile, applyCodexConfigTransactionFile, composeManagedDeveloperInstructionsBlock, readCodexConfigFile, } from '../utils/codex-config.js';
10
- import { canonicalizePathThroughExistingAncestor } from '../utils/state-paths.js';
9
+ import { canonicalizePathThroughExistingAncestor, resolveClaudeProjectKey, resolveCodexProjectKey, } from '../utils/state-paths.js';
11
10
  import { createCodexSkillExpectedTree, createCodexSkillStagingTreeContentDigest, } from '../utils/codex-skill-staging.js';
12
- import { createDynamicSkillExpectedTree, createDynamicSkillTreeContentDigest, readDynamicSkillTree, validateSkillDirectory, writeDynamicSkillTree, } from '../utils/dynamic-skill-tree.js';
11
+ import { AW_PLUGIN_REQUIREMENTS_RECEIPT_PATH, 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';
18
- import { resolveAwDeliveredArtifactIds } from '../utils/artifact-request.js';
19
- import { createAwPluginRequirementsReceipt, } from '../utils/manifest.js';
17
+ import { resolveAwDeliveredArtifactRequestBatches } from '../utils/artifact-request.js';
18
+ import { createAwPluginRequirementsReceipt, parseAwPluginRequirementsReceipt, } 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';
@@ -39,6 +39,26 @@ function requirementsReceipt(plugin) {
39
39
  const receipt = createAwPluginRequirementsReceipt({ name: plugin.manifest.name, version: plugin.manifest.version }, plugin.manifest.requires?.aw.capabilities ?? []);
40
40
  return Buffer.from(`${JSON.stringify(receipt, null, 2)}\n`, 'utf8');
41
41
  }
42
+ function withoutVerifiedRequirementsReceipt(plugin, files) {
43
+ const receipt = files[AW_PLUGIN_REQUIREMENTS_RECEIPT_PATH];
44
+ if (!receipt)
45
+ return { ...files };
46
+ let actual;
47
+ let expected;
48
+ try {
49
+ actual = parseAwPluginRequirementsReceipt(JSON.parse(receipt.toString('utf8')));
50
+ expected = parseAwPluginRequirementsReceipt(JSON.parse(requirementsReceipt(plugin).toString('utf8')));
51
+ }
52
+ catch {
53
+ return { ...files };
54
+ }
55
+ if (JSON.stringify(actual) !== JSON.stringify(expected)) {
56
+ return { ...files };
57
+ }
58
+ const output = { ...files };
59
+ delete output[AW_PLUGIN_REQUIREMENTS_RECEIPT_PATH];
60
+ return output;
61
+ }
42
62
  export class CodexActivationPostcheckError extends Error {
43
63
  code = 'codexActivationPostcheckFailed';
44
64
  codexPluginIds;
@@ -48,13 +68,34 @@ export class CodexActivationPostcheckError extends Error {
48
68
  this.codexPluginIds = [...new Set(typeof codexPluginIds === 'string' ? [codexPluginIds] : codexPluginIds)];
49
69
  }
50
70
  }
71
+ function codexObservationScope(context, marketplaceName, fallbackScope) {
72
+ return context.codexObservationScopeByMarketplace?.get(marketplaceName)
73
+ ?? (fallbackScope === 'user' ? 'user' : 'both');
74
+ }
75
+ function codexMarketplaceFromPluginId(pluginId) {
76
+ return pluginId.slice(pluginId.lastIndexOf('@') + 1);
77
+ }
78
+ function requireCodexObservationCoverage(registration, scope, operation) {
79
+ if (!registration.observations.user
80
+ || (scope === 'both' && !registration.observations.project)) {
81
+ throw new Error(`codexNativeObservationUnavailable: ${operation} requires ${scope === 'user' ? 'neutral' : 'exact and neutral'} Codex plugin list evidence.`);
82
+ }
83
+ }
51
84
  function requireCodexManager(manager) {
52
85
  if (!manager) {
53
86
  throw new Error('codexManagerRequired: Codex native operation has no resolved manager.');
54
87
  }
55
88
  return manager;
56
89
  }
57
- export async function executeCodexReconcileActivations(options) {
90
+ async function runCodexMutationWithInvalidation(options) {
91
+ try {
92
+ return await options.mutation();
93
+ }
94
+ finally {
95
+ options.observationSession?.invalidate(options.marketplaceName);
96
+ }
97
+ }
98
+ async function executeCodexReconcileActivationsInSession(options) {
58
99
  const byScope = {
59
100
  user: { upserts: {}, removals: new Set() },
60
101
  project: { upserts: {}, removals: new Set() },
@@ -87,23 +128,35 @@ export async function executeCodexReconcileActivations(options) {
87
128
  }
88
129
  }
89
130
  }
131
+ const recordActivationMutations = (pluginIds) => {
132
+ if (!options.mutationMarketplaces)
133
+ return;
134
+ for (const pluginId of pluginIds) {
135
+ options.mutationMarketplaces.add(pluginId.slice(pluginId.lastIndexOf('@') + 1));
136
+ }
137
+ };
90
138
  const observedActivationPluginIds = [...new Set(options.plans.flatMap((plan) => plan.postcheck.support === 'observed'
91
139
  ? plan.postcheck.plugins.map((plugin) => plugin.codexPluginId)
92
140
  : []))];
93
- if (observedActivationPluginIds.length > 0) {
141
+ if (!options.preflightCompleted && observedActivationPluginIds.length > 0) {
94
142
  const manager = requireCodexManager(options.manager);
95
143
  for (const pluginId of observedActivationPluginIds) {
144
+ const marketplaceName = codexMarketplaceFromPluginId(pluginId);
145
+ const observationScope = options.observationScopeByMarketplace?.get(marketplaceName)
146
+ ?? (options.plans.some((plan) => plan.requirement.requiresProjectPluginContract)
147
+ ? 'both'
148
+ : 'user');
96
149
  const registration = await inspectCodexPluginRegistrationLive({
97
150
  pluginId,
98
151
  cwd: options.cwd,
99
152
  env: options.env,
100
153
  nativeCliRunner: options.runner,
101
154
  manager,
155
+ scope: observationScope,
156
+ observationSession: options.observationSession,
102
157
  });
103
158
  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
- }
159
+ requireCodexObservationCoverage(registration, observationScope, 'activation precheck');
107
160
  if (observedState !== 'installed') {
108
161
  throw new Error('codexNativeObservationFailed: activation precheck did not observe the installed Codex plugin.');
109
162
  }
@@ -115,7 +168,9 @@ export async function executeCodexReconcileActivations(options) {
115
168
  if (Object.keys(enables).length === 0)
116
169
  continue;
117
170
  const configPath = resolveCodexPaths(options.cwd, scope, options.env).configTomlPath;
118
- await applyCodexConfigTransactionFile(configPath, { upsertPluginActivations: enables });
171
+ const update = await applyCodexConfigTransactionFile(configPath, { upsertPluginActivations: enables });
172
+ if (update.wrote)
173
+ recordActivationMutations(Object.keys(enables));
119
174
  const enabledConfig = await readCodexConfigFile(configPath);
120
175
  const missingEnables = Object.keys(enables).filter((pluginId) => enabledConfig.snapshot.plugins[pluginId]?.enabled !== true);
121
176
  if (missingEnables.length > 0) {
@@ -128,7 +183,7 @@ export async function executeCodexReconcileActivations(options) {
128
183
  .filter(([, desired]) => !desired));
129
184
  if (Object.keys(disables).length === 0 && transaction.removals.size === 0)
130
185
  continue;
131
- await applyCodexConfigTransactionFile(resolveCodexPaths(options.cwd, scope, options.env).configTomlPath, {
186
+ const update = await applyCodexConfigTransactionFile(resolveCodexPaths(options.cwd, scope, options.env).configTomlPath, {
132
187
  ...(transaction.removals.size > 0
133
188
  ? { removePluginActivations: [...transaction.removals].sort() }
134
189
  : {}),
@@ -136,6 +191,12 @@ export async function executeCodexReconcileActivations(options) {
136
191
  ? { upsertPluginActivations: disables }
137
192
  : {}),
138
193
  });
194
+ if (update.wrote) {
195
+ recordActivationMutations([
196
+ ...Object.keys(disables),
197
+ ...transaction.removals,
198
+ ]);
199
+ }
139
200
  }
140
201
  const [userConfig, projectConfig] = await Promise.all([
141
202
  readCodexConfigFile(resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath),
@@ -172,45 +233,87 @@ export async function executeCodexReconcileActivations(options) {
172
233
  : []);
173
234
  if (observedActivationChecks.length === 0)
174
235
  return;
175
- const manager = requireCodexManager(options.manager);
176
- const neutralCwd = await fs.mkdtemp(path.join(os.tmpdir(), 'aw-codex-postcheck-'));
236
+ const ineffectivePlugins = [];
237
+ const ineffectiveDetails = [];
238
+ for (const plugin of observedActivationChecks) {
239
+ const userActive = userConfig.snapshot.plugins[plugin.codexPluginId]?.enabled === true;
240
+ const projectActive = projectConfig.snapshot.plugins[plugin.codexPluginId]?.enabled === true;
241
+ if ((userActive || projectActive) !== (plugin.expectedUserEnabled || plugin.expectedProjectEnabled)
242
+ || userActive !== plugin.expectedUserEnabled) {
243
+ ineffectivePlugins.push(plugin.codexPluginId);
244
+ ineffectiveDetails.push(new CodexProjectConfigNotEffectiveError(plugin.codexPluginId).message);
245
+ }
246
+ }
247
+ if (ineffectivePlugins.length > 0) {
248
+ throw new CodexActivationPostcheckError(ineffectivePlugins, ineffectiveDetails.join(' '));
249
+ }
250
+ }
251
+ export async function executeCodexReconcileActivations(options) {
252
+ const ownedSession = options.observationSession ? undefined
253
+ : await createCodexNativeObservationSession({
254
+ exactCwd: options.cwd,
255
+ env: options.env,
256
+ runner: options.runner,
257
+ manager: options.manager,
258
+ });
177
259
  try {
178
- const invocations = buildCodexProjectPluginListInvocations(manager, {
260
+ await executeCodexReconcileActivationsInSession({
261
+ ...options,
262
+ observationSession: options.observationSession ?? ownedSession,
263
+ });
264
+ }
265
+ finally {
266
+ await ownedSession?.dispose();
267
+ }
268
+ }
269
+ export async function postcheckCodexReconcileActivations(options) {
270
+ const checks = options.plans.flatMap((plan) => plan.postcheck.support === 'observed' ? plan.postcheck.plugins : []);
271
+ if (checks.length === 0)
272
+ return;
273
+ const ownedSession = options.observationSession ? undefined
274
+ : await createCodexNativeObservationSession({
179
275
  exactCwd: options.cwd,
180
- neutralCwd,
181
276
  env: options.env,
277
+ runner: options.runner,
278
+ manager: options.manager,
182
279
  });
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) {
280
+ const session = options.observationSession ?? ownedSession;
281
+ try {
282
+ const failedPluginIds = [];
283
+ const failureDetails = [];
284
+ for (const check of checks) {
190
285
  try {
191
- postcheckCodexProjectPluginActivation({
192
- pluginId: plugin.codexPluginId,
193
- userEnabled: plugin.expectedUserEnabled,
194
- projectEnabled: plugin.expectedProjectEnabled,
195
- exactResult,
196
- neutralResult,
286
+ const marketplaceName = codexMarketplaceFromPluginId(check.codexPluginId);
287
+ const observationScope = options.observationScopeByMarketplace?.get(marketplaceName)
288
+ ?? (options.plans.some((plan) => plan.requirement.requiresProjectPluginContract)
289
+ ? 'both'
290
+ : 'user');
291
+ const registration = await inspectCodexPluginRegistrationLive({
292
+ pluginId: check.codexPluginId,
293
+ cwd: options.cwd,
294
+ env: options.env,
295
+ nativeCliRunner: options.runner,
296
+ manager: options.manager,
297
+ scope: observationScope,
298
+ observationSession: session,
197
299
  });
300
+ const state = requireObservedCodexNativeState(registration.observation, 'activation postcheck');
301
+ requireCodexObservationCoverage(registration, observationScope, 'activation postcheck');
302
+ if (state !== 'installed') {
303
+ throw new Error(`codexNativeObservationFailed: ${check.codexPluginId} was not observed after Codex activation mutation.`);
304
+ }
198
305
  }
199
306
  catch (error) {
200
- if (error instanceof CodexProjectConfigNotEffectiveError) {
201
- ineffectivePlugins.push(plugin.codexPluginId);
202
- ineffectiveDetails.push(error.message);
203
- continue;
204
- }
205
- throw error;
307
+ failedPluginIds.push(check.codexPluginId);
308
+ failureDetails.push(error instanceof Error ? error.message : String(error));
206
309
  }
207
310
  }
208
- if (ineffectivePlugins.length > 0) {
209
- throw new CodexActivationPostcheckError(ineffectivePlugins, ineffectiveDetails.join(' '));
311
+ if (failedPluginIds.length > 0) {
312
+ throw new CodexActivationPostcheckError(failedPluginIds, failureDetails.join(' '));
210
313
  }
211
314
  }
212
315
  finally {
213
- await fs.remove(neutralCwd);
316
+ await ownedSession?.dispose();
214
317
  }
215
318
  }
216
319
  const CODEX_REMOVE_RECOVERY = 'Codex residue was retained. Retry cleanup or uninstall, upgrade AW, or install a compatible official Codex CLI.';
@@ -718,7 +821,8 @@ async function scanOwnedSkills(options) {
718
821
  }));
719
822
  continue;
720
823
  }
721
- pending.push(skillRoot);
824
+ if (options.recursive !== false)
825
+ pending.push(skillRoot);
722
826
  }
723
827
  }
724
828
  return output;
@@ -840,46 +944,60 @@ function renderCodexRole(options) {
840
944
  ].join('\n');
841
945
  }
842
946
  async function resolveDynamicArtifacts(options) {
843
- const requestedArtifactIds = resolveAwDeliveredArtifactIds({
947
+ const requestBatches = resolveAwDeliveredArtifactRequestBatches({
844
948
  manifest: options.plugin.manifest,
845
949
  platform: options.platform,
846
950
  scope: options.scope,
847
951
  });
848
- if (requestedArtifactIds.length === 0)
849
- return { artifacts: [], sourceDirs: () => [] };
850
- const render = await runArtifactRenderer({
851
- event: options.event,
852
- plugin: options.plugin,
853
- platform: options.platform,
854
- scope: options.scope,
855
- cwd: options.cwd,
856
- commandEnv: options.env,
857
- previousArtifacts: [...options.previousArtifacts],
858
- requestedArtifactIds,
859
- scriptTimeoutMs: options.scriptTimeoutMs,
860
- });
952
+ if (requestBatches.length === 0) {
953
+ return { artifacts: [], sourceDirs: () => [], sensitiveValues: [] };
954
+ }
955
+ const previousArtifacts = [...options.previousArtifacts];
956
+ const renders = [];
957
+ for (const requestedArtifactIds of requestBatches) {
958
+ renders.push(await runArtifactRenderer({
959
+ event: options.event,
960
+ plugin: options.plugin,
961
+ platform: options.platform,
962
+ scope: options.scope,
963
+ cwd: options.cwd,
964
+ commandEnv: options.env,
965
+ previousArtifacts,
966
+ requestedArtifactIds,
967
+ scriptTimeoutMs: options.scriptTimeoutMs,
968
+ }));
969
+ }
861
970
  const config = await resolveArtifactRenderConfig({
862
971
  sourceId: options.plugin.sourceId,
863
972
  sourceIdentity: options.plugin.sourceIdentity,
973
+ logicalIdentity: options.plugin.catalogLogicalIdentity,
864
974
  pluginName: options.plugin.manifest.name,
865
975
  manifest: options.plugin.manifest,
866
976
  scope: options.scope,
867
977
  cwd: options.cwd,
868
978
  env: options.env,
869
979
  });
870
- const artifacts = validateArtifactPlan({
871
- manifest: options.plugin.manifest,
872
- plan: render.plan,
873
- platform: options.platform,
874
- scope: options.scope,
875
- requestedArtifactIds: render.context.requestedArtifactIds,
876
- renderConfig: config,
877
- rendererDigest: render.target.rendererDigest,
878
- manifestDigest: render.target.manifestDigest,
879
- }).artifacts;
980
+ const artifacts = [];
981
+ const sourceDirsByArtifact = new Map();
982
+ for (const render of renders) {
983
+ artifacts.push(...validateArtifactPlan({
984
+ manifest: options.plugin.manifest,
985
+ plan: render.plan,
986
+ platform: options.platform,
987
+ scope: options.scope,
988
+ requestedArtifactIds: render.context.requestedArtifactIds,
989
+ renderConfig: config,
990
+ rendererDigest: render.target.rendererDigest,
991
+ manifestDigest: render.target.manifestDigest,
992
+ }).artifacts);
993
+ for (const source of render.context.artifactSources) {
994
+ sourceDirsByArtifact.set(`${source.id}\0${source.target}`, source.sourceDirs);
995
+ }
996
+ }
880
997
  return {
881
998
  artifacts,
882
- sourceDirs: (artifact) => render.context.artifactSources.find((entry) => entry.id === artifact.id && entry.target === artifact.target)?.sourceDirs ?? [],
999
+ sourceDirs: (artifact) => sourceDirsByArtifact.get(`${artifact.id}\0${artifact.target}`) ?? [],
1000
+ sensitiveValues: collectSensitivePluginConfigValues(options.plugin.manifest, config.values),
883
1001
  };
884
1002
  }
885
1003
  function dynamicSkillWrite(options) {
@@ -1053,6 +1171,7 @@ async function buildClaudeWrites(options) {
1053
1171
  requirementsReceipt: requirementsReceipt(options.plugin),
1054
1172
  sourceRequirementsReceiptPolicy: 'allow',
1055
1173
  });
1174
+ assertNoSensitiveConfigInFiles(withoutVerifiedRequirementsReceipt(options.plugin, tree.files), dynamic.sensitiveValues, 'artifactRendererSensitiveConfigExposed: Final dynamic Skill contains sensitive config.');
1056
1175
  writes.push(dynamicSkillWrite({
1057
1176
  ...options,
1058
1177
  platform: 'claude',
@@ -1129,6 +1248,7 @@ async function buildCodexWrites(options) {
1129
1248
  requirementsReceipt: requirementsReceipt(options.plugin),
1130
1249
  sourceRequirementsReceiptPolicy: 'allow',
1131
1250
  });
1251
+ assertNoSensitiveConfigInFiles(withoutVerifiedRequirementsReceipt(options.plugin, tree.files), dynamic.sensitiveValues, 'artifactRendererSensitiveConfigExposed: Final dynamic Skill contains sensitive config.');
1132
1252
  writes.push(dynamicSkillWrite({
1133
1253
  ...options,
1134
1254
  platform: 'codex',
@@ -1636,119 +1756,43 @@ async function buildHistoricalComponents(options) {
1636
1756
  }
1637
1757
  return components;
1638
1758
  }
1639
- function isAbsentCodexResiduePath(error) {
1759
+ function isAbsentPath(error) {
1640
1760
  const code = error.code;
1641
1761
  return code === 'ENOENT';
1642
1762
  }
1643
- async function lstatCodexResidueIfPresent(filePath) {
1763
+ async function lstatIfPresent(filePath) {
1644
1764
  return fs.lstat(filePath).catch((error) => {
1645
- if (isAbsentCodexResiduePath(error))
1765
+ if (isAbsentPath(error))
1646
1766
  return null;
1647
1767
  throw error;
1648
1768
  });
1649
1769
  }
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
- };
1770
+ async function lstatCodexResidueIfPresent(filePath) {
1771
+ return fs.lstat(filePath).catch((error) => {
1772
+ if (isAbsentPath(error))
1773
+ return null;
1774
+ throw error;
1775
+ });
1746
1776
  }
1747
1777
  function isPathWithin(root, candidate) {
1748
1778
  const relative = path.relative(path.resolve(root), path.resolve(candidate));
1749
1779
  return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
1750
1780
  }
1751
- async function codexPayloadPathSafe(payloadRoot) {
1781
+ async function codexReadPathSafe(root, candidate) {
1782
+ if (!isPathWithin(root, candidate))
1783
+ return false;
1784
+ const rootStats = await lstatCodexResidueIfPresent(root);
1785
+ if (rootStats && (!rootStats.isDirectory() || rootStats.isSymbolicLink()))
1786
+ return false;
1787
+ const [physicalRoot, physicalCandidate] = await Promise.all([
1788
+ canonicalizePathThroughExistingAncestor(root),
1789
+ canonicalizePathThroughExistingAncestor(candidate),
1790
+ ]);
1791
+ return isPathWithin(physicalRoot, physicalCandidate);
1792
+ }
1793
+ async function codexPayloadPathSafe(payloadRoot, allowedRoot = payloadRoot) {
1794
+ if (!await codexReadPathSafe(allowedRoot, payloadRoot))
1795
+ return false;
1752
1796
  const rootStats = await lstatCodexResidueIfPresent(payloadRoot);
1753
1797
  if (!rootStats?.isDirectory() || rootStats.isSymbolicLink())
1754
1798
  return false;
@@ -1757,12 +1801,9 @@ async function codexPayloadPathSafe(payloadRoot) {
1757
1801
  const current = pending.pop();
1758
1802
  for (const entry of await fs.readdir(current, { withFileTypes: true })) {
1759
1803
  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()) {
1804
+ if (entry.isSymbolicLink())
1805
+ return false;
1806
+ if (entry.isDirectory()) {
1766
1807
  pending.push(entryPath);
1767
1808
  }
1768
1809
  else if (!entry.isFile()) {
@@ -1772,6 +1813,23 @@ async function codexPayloadPathSafe(payloadRoot) {
1772
1813
  }
1773
1814
  return true;
1774
1815
  }
1816
+ async function resolveCodexSkillsRoot(payloadRoot, rawSkillsRoot) {
1817
+ const normalizedSeparators = rawSkillsRoot.replace(/\\/gu, '/');
1818
+ const relativeRoot = path.posix.normalize(normalizedSeparators.replace(/^\.\//u, ''));
1819
+ if (!relativeRoot
1820
+ || relativeRoot === '.'
1821
+ || relativeRoot === '..'
1822
+ || relativeRoot.startsWith('../')
1823
+ || path.posix.isAbsolute(normalizedSeparators)
1824
+ || path.win32.isAbsolute(rawSkillsRoot)) {
1825
+ throw new Error('codexNativeSkillsRootInvalid: Codex native Skills root is invalid.');
1826
+ }
1827
+ const root = path.join(payloadRoot, ...relativeRoot.split('/'));
1828
+ if (!await codexReadPathSafe(payloadRoot, root)) {
1829
+ throw new Error('codexNativeSkillsRootInvalid: Codex native Skills root is unsafe.');
1830
+ }
1831
+ return { relativeRoot, root };
1832
+ }
1775
1833
  function codexNativeRenderTargets(plugin) {
1776
1834
  return (plugin.manifest.artifacts?.targets ?? []).filter((target) => target.platform === 'codex'
1777
1835
  && target.target === 'codex.skill'
@@ -1779,11 +1837,11 @@ function codexNativeRenderTargets(plugin) {
1779
1837
  }
1780
1838
  async function readCodexNativeRenderReceiptEvidence(payloadRoot) {
1781
1839
  const receiptPath = path.join(payloadRoot, CODEX_NATIVE_RENDER_RECEIPT_PATH);
1782
- const stats = await lstatCodexResidueIfPresent(receiptPath);
1840
+ const stats = await lstatIfPresent(receiptPath);
1783
1841
  if (!stats)
1784
1842
  return { state: 'absent' };
1785
1843
  if (!stats.isFile() || stats.isSymbolicLink())
1786
- return { state: 'unreadable' };
1844
+ return { state: 'unsafe' };
1787
1845
  try {
1788
1846
  return {
1789
1847
  state: 'present',
@@ -1791,7 +1849,7 @@ async function readCodexNativeRenderReceiptEvidence(payloadRoot) {
1791
1849
  };
1792
1850
  }
1793
1851
  catch {
1794
- return { state: 'unreadable' };
1852
+ return { state: 'invalid' };
1795
1853
  }
1796
1854
  }
1797
1855
  async function resolveCodexNativeRenderExpectedEvidence(options) {
@@ -1801,6 +1859,7 @@ async function resolveCodexNativeRenderExpectedEvidence(options) {
1801
1859
  throw new Error('Codex native render projection requires namespaced source identity.');
1802
1860
  }
1803
1861
  const marketplace = await readStrictNamespacedMarketplaceRoot({
1862
+ trustedMarketplaceRoot: path.join(resolveAwHome(options.env), 'marketplaces'),
1804
1863
  rootDir: options.marketplaceRoot,
1805
1864
  sourceId: options.plugin.sourceId,
1806
1865
  marketplaceName: options.marketplaceName,
@@ -1808,7 +1867,13 @@ async function resolveCodexNativeRenderExpectedEvidence(options) {
1808
1867
  if (!marketplace) {
1809
1868
  throw new Error('Codex native render canonical marketplace generation is missing.');
1810
1869
  }
1870
+ if (marketplace.config.sourceIdentity !== options.plugin.sourceIdentity) {
1871
+ return { state: 'foreign' };
1872
+ }
1811
1873
  const payloadRoot = path.join(options.marketplaceRoot, 'plugins', options.plugin.manifest.name);
1874
+ if (!await codexPayloadPathSafe(payloadRoot, options.marketplaceRoot)) {
1875
+ throw new Error('Codex native render canonical marketplace payload path is unsafe.');
1876
+ }
1812
1877
  const receiptEvidence = await readCodexNativeRenderReceiptEvidence(payloadRoot);
1813
1878
  if (receiptEvidence.state !== 'present') {
1814
1879
  throw new Error('Codex native render canonical marketplace receipt is missing or unreadable.');
@@ -1824,7 +1889,7 @@ async function resolveCodexNativeRenderExpectedEvidence(options) {
1824
1889
  throw new Error('Codex native render canonical marketplace payload does not match its receipt.');
1825
1890
  }
1826
1891
  const sourceManifestPath = path.join(payloadRoot, '.agents-plugin', 'plugin.json');
1827
- const sourceManifestStats = await lstatCodexResidueIfPresent(sourceManifestPath);
1892
+ const sourceManifestStats = await lstatIfPresent(sourceManifestPath);
1828
1893
  const sourceManifest = sourceManifestStats?.isFile() && !sourceManifestStats.isSymbolicLink()
1829
1894
  ? await fs.readJson(sourceManifestPath).catch(() => null)
1830
1895
  : null;
@@ -1833,7 +1898,7 @@ async function resolveCodexNativeRenderExpectedEvidence(options) {
1833
1898
  throw new Error('Codex native render canonical marketplace plugin identity is invalid.');
1834
1899
  }
1835
1900
  const codexManifestPath = path.join(payloadRoot, '.codex-plugin', 'plugin.json');
1836
- const codexManifestStats = await lstatCodexResidueIfPresent(codexManifestPath);
1901
+ const codexManifestStats = await lstatIfPresent(codexManifestPath);
1837
1902
  const codexManifest = codexManifestStats?.isFile() && !codexManifestStats.isSymbolicLink()
1838
1903
  ? await fs.readJson(codexManifestPath).catch(() => null)
1839
1904
  : null;
@@ -1843,19 +1908,14 @@ async function resolveCodexNativeRenderExpectedEvidence(options) {
1843
1908
  const rawSkillsRoot = typeof codexManifest.skills === 'string' && codexManifest.skills.trim() !== ''
1844
1909
  ? codexManifest.skills
1845
1910
  : './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
- }
1911
+ const { relativeRoot: skillsRelativeRoot } = await resolveCodexSkillsRoot(payloadRoot, rawSkillsRoot);
1852
1912
  const emptyArtifactDigest = createCodexSkillStagingTreeContentDigest({
1853
1913
  skillKey: 'empty',
1854
1914
  files: {},
1855
1915
  });
1856
1916
  for (const receiptTarget of receipt.targets) {
1857
1917
  const skillRoot = path.join(payloadRoot, ...skillsRelativeRoot.split('/'), receiptTarget.skillKey);
1858
- if (await lstatCodexResidueIfPresent(path.join(skillRoot, '.agent-workflows-owner.json'))) {
1918
+ if (await lstatIfPresent(path.join(skillRoot, '.agent-workflows-owner.json'))) {
1859
1919
  throw new Error('Codex native render canonical marketplace Skill has an AW owner marker.');
1860
1920
  }
1861
1921
  const tree = await readDynamicSkillTree(skillRoot, 'codex.skill');
@@ -1871,7 +1931,7 @@ async function resolveCodexNativeRenderExpectedEvidence(options) {
1871
1931
  if (targets.length === 0
1872
1932
  || targets.length !== receipt.targets.length
1873
1933
  || targets.some((target) => receiptTargets.get(target.id)?.skillKey !== target.skillKey)) {
1874
- return { payloadDigest };
1934
+ return { state: 'present', payloadDigest, receipt, sourceProjection: 'drifted' };
1875
1935
  }
1876
1936
  const renderer = await resolveArtifactRendererTarget(options.plugin);
1877
1937
  if (renderer.noOp || !renderer.rendererDigest) {
@@ -1880,6 +1940,7 @@ async function resolveCodexNativeRenderExpectedEvidence(options) {
1880
1940
  const config = await resolveArtifactRenderConfig({
1881
1941
  sourceId: options.plugin.sourceId,
1882
1942
  sourceIdentity: options.plugin.sourceIdentity,
1943
+ logicalIdentity: options.plugin.catalogLogicalIdentity,
1883
1944
  pluginName: options.plugin.manifest.name,
1884
1945
  manifest: options.plugin.manifest,
1885
1946
  scope: 'user',
@@ -1904,17 +1965,66 @@ async function resolveCodexNativeRenderExpectedEvidence(options) {
1904
1965
  target: 'codex.skill',
1905
1966
  refreshKeys: target.refreshKeys,
1906
1967
  },
1907
- configValues: config.values,
1968
+ renderConfig: config,
1908
1969
  digestSalt: renderer.rendererDigest,
1909
1970
  }),
1910
1971
  artifactDigest: receiptTargets.get(target.id).artifactDigest,
1911
1972
  })),
1912
1973
  });
1913
1974
  return {
1975
+ state: 'present',
1976
+ receipt,
1914
1977
  projection,
1915
1978
  payloadDigest,
1979
+ sourceProjection: codexNativeRenderProjectionsEqual(receipt, projection)
1980
+ ? 'current'
1981
+ : 'drifted',
1916
1982
  };
1917
1983
  }
1984
+ async function assertNoSensitiveValuesInCanonicalCodexNativePayload(options) {
1985
+ if (options.sensitiveValues.length === 0
1986
+ || !pluginHasCodexNativeSkillSurface(options.plugin))
1987
+ return;
1988
+ const sourceId = options.plugin.sourceId;
1989
+ if (!sourceId) {
1990
+ throw new Error('Codex native sensitive scan requires namespaced source identity.');
1991
+ }
1992
+ const awHome = resolveAwHome(options.env);
1993
+ if (!await isSafeCodexMarketplaceRoot({
1994
+ awHome,
1995
+ sourceId,
1996
+ marketplaceRoot: options.marketplaceRoot,
1997
+ })) {
1998
+ throw new Error('Codex native sensitive scan canonical marketplace path is unsafe.');
1999
+ }
2000
+ const payloadRoot = path.join(options.marketplaceRoot, 'plugins', options.plugin.manifest.name);
2001
+ if (!await codexPayloadPathSafe(payloadRoot, options.marketplaceRoot)) {
2002
+ throw new Error('Codex native sensitive scan payload path is unsafe.');
2003
+ }
2004
+ const receiptEvidence = await readCodexNativeRenderReceiptEvidence(payloadRoot);
2005
+ if (receiptEvidence.state === 'unsafe') {
2006
+ throw new Error('codexNativeRenderReceiptInvalid: Codex native render receipt path is unsafe.');
2007
+ }
2008
+ let verifiedRenderReceipt;
2009
+ if (receiptEvidence.state === 'present') {
2010
+ const expected = await resolveCodexNativeRenderExpectedEvidence({
2011
+ plugin: options.plugin,
2012
+ marketplaceRoot: options.marketplaceRoot,
2013
+ marketplaceName: options.marketplaceName,
2014
+ env: options.env,
2015
+ }).catch(() => undefined);
2016
+ if (expected?.state === 'present' && expected.sourceProjection === 'current') {
2017
+ verifiedRenderReceipt = expected.receipt;
2018
+ }
2019
+ }
2020
+ await assertNoSensitiveValuesInCodexNativePayload({
2021
+ plugin: options.plugin,
2022
+ payloadRoot,
2023
+ allowedRoot: options.marketplaceRoot,
2024
+ sensitiveValues: options.sensitiveValues,
2025
+ verifiedRenderReceipt,
2026
+ });
2027
+ }
1918
2028
  function observedCodexNativeState(observation, operation) {
1919
2029
  if (observation.state === 'observed') {
1920
2030
  return observation.value === 'installed' ? 'present' : 'absent';
@@ -1939,47 +2049,32 @@ export async function inspectCodexPluginRegistrationLive(options) {
1939
2049
  activations: {},
1940
2050
  };
1941
2051
  }
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
- };
2052
+ const ownedSession = options.observationSession ? undefined
2053
+ : await createCodexNativeObservationSession({
2054
+ exactCwd: options.cwd,
2055
+ env: options.env,
2056
+ runner: options.nativeCliRunner,
2057
+ manager: options.manager ?? SYSTEM_CODEX_MANAGER,
2058
+ });
2059
+ const session = options.observationSession ?? ownedSession;
2060
+ try {
2061
+ const inspected = await session.inspectPlugin({
2062
+ pluginId: options.pluginId,
2063
+ scope: options.scope ?? 'both',
2064
+ });
2065
+ return {
2066
+ observation: inspected.observation,
2067
+ observations: inspected.observations,
2068
+ activations: inspected.activations,
2069
+ };
2070
+ }
2071
+ finally {
2072
+ await ownedSession?.dispose();
2073
+ }
1979
2074
  }
1980
2075
  /**
1981
- * Read-only filesystem projection for the payload-aware shared-native classifier.
1982
- * It does not invoke Codex or create native/config state.
2076
+ * Read-only shared-native inspection. Official Codex list evidence owns installation
2077
+ * state. Filesystem validation is limited to the AW-owned marketplace source.
1983
2078
  */
1984
2079
  export async function inspectCodexSharedNativeLive(options) {
1985
2080
  const inspectionMode = options.inspectionMode ?? 'payload';
@@ -1992,151 +2087,28 @@ export async function inspectCodexSharedNativeLive(options) {
1992
2087
  }
1993
2088
  const marketplaceName = options.marketplaceName ?? MARKETPLACE_NAME;
1994
2089
  const marketplaceRoot = options.marketplaceRoot ?? path.join(resolveAwHome(options.env), 'marketplaces', options.sourceId);
2090
+ const marketplacePathSafe = await isSafeCodexMarketplaceRoot({
2091
+ awHome: resolveAwHome(options.env),
2092
+ sourceId: options.sourceId,
2093
+ marketplaceRoot,
2094
+ });
1995
2095
  const pluginId = `${options.pluginName}@${marketplaceName}`;
1996
2096
  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
2097
  const pluginList = options.pluginRegistration ?? await inspectCodexPluginRegistrationLive({
2110
2098
  pluginId,
2111
2099
  cwd: options.cwd,
2112
2100
  env: options.env,
2113
2101
  nativeCliRunner: options.nativeCliRunner,
2114
2102
  manager: options.manager,
2103
+ scope: options.observationScope,
2104
+ observationSession: options.observationSession,
2115
2105
  });
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
2106
  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) {
2107
+ let sourceProjection;
2108
+ let expectedMarketplaceForeign = false;
2109
+ const installed = pluginList.observation.state === 'observed'
2110
+ && pluginList.observation.value === 'installed';
2111
+ if (installed && dynamicProjectionExpected && options.nativeRenderPlugin) {
2140
2112
  try {
2141
2113
  const expected = await resolveCodexNativeRenderExpectedEvidence({
2142
2114
  plugin: options.nativeRenderPlugin,
@@ -2144,181 +2116,36 @@ export async function inspectCodexSharedNativeLive(options) {
2144
2116
  marketplaceName,
2145
2117
  env: options.env,
2146
2118
  });
2147
- expectedProjection = expected.projection;
2148
- expectedPayloadDigest = expected.payloadDigest;
2119
+ if (expected.state === 'foreign') {
2120
+ expectedMarketplaceForeign = true;
2121
+ }
2122
+ else {
2123
+ expectedPayloadDigest = expected.payloadDigest;
2124
+ sourceProjection = expected.sourceProjection;
2125
+ }
2149
2126
  }
2150
2127
  catch {
2151
- // Canonical marketplace evidence is weak state. Missing evidence is replaceable damage.
2128
+ sourceProjection = 'drifted';
2152
2129
  }
2153
2130
  }
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
- });
2131
+ const sharedNative = expectedMarketplaceForeign
2132
+ ? { state: 'blocked', reason: 'foreign', diagnostic: 'marketplaceRegistrationForeign' }
2133
+ : inspectCodexSharedNative({
2134
+ pluginName: options.pluginName,
2135
+ pluginId,
2136
+ marketplaceName,
2137
+ canonicalMarketplaceRoot: marketplaceRoot,
2138
+ desiredVersion: options.desiredVersion,
2139
+ expectedPayloadDigest,
2140
+ marketplaceRegistration: config.snapshot.marketplaces[marketplaceName],
2141
+ marketplacePathSafe,
2142
+ observation: pluginList.observation,
2143
+ ...(sourceProjection ? { sourceProjection } : {}),
2144
+ });
2174
2145
  return {
2175
2146
  sharedNative,
2176
2147
  ...(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
2148
  };
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
2149
  }
2323
2150
  async function ensureCodexActiveSkillsManifest(options) {
2324
2151
  const relativeSkillsRoot = await resolveCodexSkillsRelativeRoot(options.plugin);
@@ -2358,124 +2185,6 @@ function createCodexMarketplaceComponent(options) {
2358
2185
  },
2359
2186
  };
2360
2187
  }
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
2188
  async function readJsonObjectIfExists(filePath, label) {
2480
2189
  let value;
2481
2190
  try {
@@ -2793,7 +2502,111 @@ function createClaudeMarketplaceComponent(options) {
2793
2502
  },
2794
2503
  };
2795
2504
  }
2796
- async function materializeCodexNativeRender(options) {
2505
+ const codexNativeRenderPreparationCaches = new WeakMap();
2506
+ function codexNativeRenderPreparationCache(context) {
2507
+ let cache = codexNativeRenderPreparationCaches.get(context);
2508
+ if (!cache) {
2509
+ cache = new WeakMap();
2510
+ codexNativeRenderPreparationCaches.set(context, cache);
2511
+ }
2512
+ return cache;
2513
+ }
2514
+ function pluginHasCodexNativeSkillSurface(plugin) {
2515
+ return plugin.manifest.skills !== undefined
2516
+ || codexNativeRenderTargets(plugin).length > 0;
2517
+ }
2518
+ async function resolveSensitiveUserConfigValues(plugin, env) {
2519
+ const hasSensitiveFields = Object.values(plugin.manifest.config?.fields ?? {}).some((field) => field.sensitive === true
2520
+ || (field.type === 'object'
2521
+ && (Object.values(field.properties ?? {}).some((child) => child.sensitive === true)
2522
+ || (field.additionalProperties !== false
2523
+ && field.additionalProperties?.sensitive === true))));
2524
+ if (!hasSensitiveFields)
2525
+ return [];
2526
+ const config = await resolveArtifactRenderConfig({
2527
+ sourceId: plugin.sourceId,
2528
+ sourceIdentity: plugin.sourceIdentity,
2529
+ pluginName: plugin.manifest.name,
2530
+ manifest: plugin.manifest,
2531
+ scope: 'user',
2532
+ cwd: plugin.rootDir,
2533
+ env,
2534
+ });
2535
+ return collectSensitivePluginConfigValues(plugin.manifest, config.values);
2536
+ }
2537
+ async function assertNoSensitiveValuesInCodexNativePayload(options) {
2538
+ if (options.sensitiveValues.length === 0)
2539
+ return;
2540
+ if (!await codexPayloadPathSafe(options.payloadRoot, options.allowedRoot)) {
2541
+ throw new Error('Codex native sensitive scan payload path is unsafe.');
2542
+ }
2543
+ const codexManifestPath = path.join(options.payloadRoot, '.codex-plugin', 'plugin.json');
2544
+ const codexManifestStats = await lstatCodexResidueIfPresent(codexManifestPath);
2545
+ if (!codexManifestStats?.isFile()) {
2546
+ throw new Error('Codex native sensitive scan manifest is missing or unsafe.');
2547
+ }
2548
+ const codexManifest = await fs.readJson(codexManifestPath).catch(() => null);
2549
+ if (!codexManifest
2550
+ || codexManifest.name !== options.plugin.manifest.name
2551
+ || codexManifest.version !== options.plugin.manifest.version) {
2552
+ throw new Error('Codex native sensitive scan manifest identity is invalid.');
2553
+ }
2554
+ const rawSkillsRoot = codexManifest.skills;
2555
+ if (rawSkillsRoot !== undefined) {
2556
+ if (typeof rawSkillsRoot !== 'string' || rawSkillsRoot.trim() === '') {
2557
+ throw new Error('Codex native sensitive scan Skills root is invalid.');
2558
+ }
2559
+ const { root: skillsRoot } = await resolveCodexSkillsRoot(options.payloadRoot, rawSkillsRoot);
2560
+ const skillsRootStats = await lstatCodexResidueIfPresent(skillsRoot);
2561
+ if (!skillsRootStats?.isDirectory()) {
2562
+ throw new Error('Codex native sensitive scan Skills root is missing or unsafe.');
2563
+ }
2564
+ for (const entry of await fs.readdir(skillsRoot, { withFileTypes: true })) {
2565
+ if (!entry.isDirectory())
2566
+ continue;
2567
+ const skillRoot = path.join(skillsRoot, entry.name);
2568
+ const skillFileStats = await lstatCodexResidueIfPresent(path.join(skillRoot, 'SKILL.md'));
2569
+ if (!skillFileStats)
2570
+ continue;
2571
+ if (!skillFileStats.isFile()) {
2572
+ throw new Error('Codex native sensitive scan Skill entry is unsafe.');
2573
+ }
2574
+ const tree = await readDynamicSkillTree(skillRoot, 'codex.skill');
2575
+ if (!tree)
2576
+ continue;
2577
+ const leakedPath = findSensitiveTokenPath(withoutVerifiedRequirementsReceipt(options.plugin, tree.files), options.sensitiveValues);
2578
+ if (leakedPath) {
2579
+ throw new Error(`codexNativeRenderSensitiveValue: Codex native Skill "${entry.name}" exposes ` +
2580
+ `sensitive config in "${leakedPath}".`);
2581
+ }
2582
+ }
2583
+ }
2584
+ const receiptPath = path.join(options.payloadRoot, CODEX_NATIVE_RENDER_RECEIPT_PATH);
2585
+ const receiptStats = await lstatCodexResidueIfPresent(receiptPath);
2586
+ if (receiptStats) {
2587
+ if (!receiptStats.isFile()) {
2588
+ throw new Error('Codex native sensitive scan render receipt path is unsafe.');
2589
+ }
2590
+ const receiptContent = await fs.readFile(receiptPath);
2591
+ let actualReceipt;
2592
+ try {
2593
+ actualReceipt = parseCodexNativeRenderReceipt(receiptContent.toString('utf8'));
2594
+ }
2595
+ catch {
2596
+ actualReceipt = undefined;
2597
+ }
2598
+ const verified = options.verifiedRenderReceipt
2599
+ && JSON.stringify(actualReceipt) === JSON.stringify(options.verifiedRenderReceipt);
2600
+ if (!verified) {
2601
+ const receiptLeak = findSensitiveTokenPath({ [CODEX_NATIVE_RENDER_RECEIPT_PATH]: receiptContent }, options.sensitiveValues);
2602
+ if (receiptLeak) {
2603
+ throw new Error(`codexNativeRenderSensitiveValue: Codex native render receipt exposes sensitive config in ` +
2604
+ `"${receiptLeak}".`);
2605
+ }
2606
+ }
2607
+ }
2608
+ }
2609
+ async function prepareCodexNativeRender(options) {
2797
2610
  const targets = codexNativeRenderTargets(options.plugin);
2798
2611
  if (targets.length === 0)
2799
2612
  return null;
@@ -2805,6 +2618,8 @@ async function materializeCodexNativeRender(options) {
2805
2618
  byScope.set(requestScope, group);
2806
2619
  }
2807
2620
  const renderedArtifacts = [];
2621
+ const receiptTargets = [];
2622
+ const presentSkillKeys = [];
2808
2623
  let rendererDigest;
2809
2624
  for (const scope of ['user', 'project']) {
2810
2625
  const group = byScope.get(scope);
@@ -2834,9 +2649,31 @@ async function materializeCodexNativeRender(options) {
2834
2649
  manifestDigest: rendered.target.manifestDigest,
2835
2650
  });
2836
2651
  for (const artifact of validated.artifacts) {
2652
+ if (artifact.target !== 'codex.skill') {
2653
+ throw new Error(`Codex native renderer returned non-Skill artifact "${artifact.id}".`);
2654
+ }
2655
+ const sourceDirs = rendered.context.artifactSources.find((entry) => entry.id === artifact.id && entry.target === artifact.target)?.sourceDirs ?? [];
2656
+ const tree = await createCodexSkillExpectedTree({
2657
+ artifact,
2658
+ sourceDirs,
2659
+ requireReadme: true,
2660
+ requirementsReceipt: requirementsReceipt(options.plugin),
2661
+ sourceRequirementsReceiptPolicy: 'allow',
2662
+ });
2663
+ assertNoSensitiveConfigInFiles(withoutVerifiedRequirementsReceipt(options.plugin, tree.files), collectSensitivePluginConfigValues(options.plugin.manifest, rendered.context.config.values), 'artifactRendererSensitiveConfigExposed: Final dynamic Skill contains sensitive config.');
2664
+ const skillKey = artifact.skillKey;
2837
2665
  renderedArtifacts.push({
2838
2666
  artifact,
2839
- sourceDirs: rendered.context.artifactSources.find((entry) => entry.id === artifact.id && entry.target === artifact.target)?.sourceDirs ?? [],
2667
+ skillKey,
2668
+ tree,
2669
+ });
2670
+ if (artifact.state === 'present')
2671
+ presentSkillKeys.push(skillKey);
2672
+ receiptTargets.push({
2673
+ id: artifact.id,
2674
+ skillKey,
2675
+ configDigest: artifact.configDigest,
2676
+ artifactDigest: createCodexSkillStagingTreeContentDigest(tree),
2840
2677
  });
2841
2678
  }
2842
2679
  }
@@ -2844,72 +2681,88 @@ async function materializeCodexNativeRender(options) {
2844
2681
  throw new Error('Codex native renderer did not produce the complete declared target set.');
2845
2682
  }
2846
2683
  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',
2684
+ const logicalPluginId = options.plugin.pluginId
2685
+ ?? (options.plugin.sourceId ? `${options.plugin.sourceId}/${options.plugin.manifest.name}` : undefined);
2686
+ if (!logicalPluginId || !options.plugin.sourceIdentity) {
2687
+ throw new Error('Codex native render materialization requires namespaced source identity.');
2688
+ }
2689
+ const projection = createCodexNativeRenderProjection({
2690
+ logicalPluginId,
2691
+ sourceIdentity: options.plugin.sourceIdentity,
2692
+ pluginVersion: options.plugin.manifest.version,
2693
+ rendererDigest,
2694
+ targets: receiptTargets,
2695
+ });
2696
+ return {
2697
+ logicalPluginId,
2698
+ sourceIdentity: options.plugin.sourceIdentity,
2699
+ rendererDigest,
2700
+ skillsRelativeRoot,
2701
+ renderedArtifacts,
2702
+ receiptTargets,
2703
+ projection,
2704
+ presentSkillKeys: presentSkillKeys.sort(),
2705
+ targetSkillKeys: targets.map((target) => target.skillKey).sort(),
2706
+ };
2707
+ }
2708
+ function prepareCodexNativeRenderCached(options) {
2709
+ const cache = codexNativeRenderPreparationCache(options.context);
2710
+ let preparation = cache.get(options.plugin);
2711
+ if (!preparation) {
2712
+ preparation = prepareCodexNativeRender({
2713
+ plugin: options.plugin,
2714
+ env: options.context.env,
2715
+ scriptTimeoutMs: options.scriptTimeoutMs,
2861
2716
  });
2717
+ cache.set(options.plugin, preparation);
2718
+ }
2719
+ return preparation;
2720
+ }
2721
+ async function requirePreparedCodexNativeRender(options) {
2722
+ if (codexNativeRenderTargets(options.plugin).length === 0)
2723
+ return null;
2724
+ const preparation = codexNativeRenderPreparationCache(options.context).get(options.plugin);
2725
+ if (!preparation) {
2726
+ throw new Error('codexNativeRenderPreflightMissing: Codex native renderer was not prepared.');
2727
+ }
2728
+ return preparation;
2729
+ }
2730
+ async function materializeCodexNativeRender(options) {
2731
+ const skillsRoot = path.join(options.stagingRoot, 'plugins', options.plugin.manifest.name, ...options.prepared.skillsRelativeRoot.split('/'));
2732
+ for (const { artifact, skillKey, tree } of options.prepared.renderedArtifacts) {
2862
2733
  const skillRoot = path.join(skillsRoot, skillKey);
2863
2734
  if (artifact.state === 'present') {
2864
2735
  await writeDynamicSkillTree(skillRoot, tree);
2865
- presentSkillKeys.push(skillKey);
2866
2736
  }
2867
2737
  else {
2868
2738
  await fs.remove(skillRoot);
2869
2739
  }
2870
- receiptTargets.push({
2871
- id: artifact.id,
2872
- skillKey,
2873
- configDigest: artifact.configDigest,
2874
- artifactDigest: createCodexSkillStagingTreeContentDigest(tree),
2875
- });
2876
2740
  }
2877
2741
  await ensureCodexActiveSkillsManifest({
2878
2742
  plugin: options.plugin,
2879
2743
  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,
2744
+ hasRequiredSkills: options.hasStaticSkills || options.prepared.presentSkillKeys.length > 0,
2893
2745
  });
2894
2746
  const pluginRoot = path.join(options.stagingRoot, 'plugins', options.plugin.manifest.name);
2895
2747
  const payloadDigest = await calculateCodexNativeRenderPayloadDigest(pluginRoot);
2896
2748
  const receipt = createCodexNativeRenderReceipt({
2897
- logicalPluginId,
2898
- sourceIdentity: options.plugin.sourceIdentity,
2749
+ logicalPluginId: options.prepared.logicalPluginId,
2750
+ sourceIdentity: options.prepared.sourceIdentity,
2899
2751
  pluginVersion: options.plugin.manifest.version,
2900
- rendererDigest,
2901
- targets: receiptTargets,
2752
+ rendererDigest: options.prepared.rendererDigest,
2753
+ targets: options.prepared.receiptTargets,
2902
2754
  payloadDigest,
2903
2755
  });
2904
2756
  await fs.outputFile(path.join(pluginRoot, CODEX_NATIVE_RENDER_RECEIPT_PATH), serializeCodexNativeRenderReceipt(receipt), 'utf8');
2905
2757
  return {
2906
2758
  payloadDigest,
2907
- projection,
2908
- presentSkillKeys: presentSkillKeys.sort(),
2909
- targetSkillKeys: targets.map((target) => target.skillKey).sort(),
2759
+ projection: options.prepared.projection,
2760
+ receipt,
2761
+ presentSkillKeys: options.prepared.presentSkillKeys,
2762
+ targetSkillKeys: options.prepared.targetSkillKeys,
2910
2763
  };
2911
2764
  }
2912
- async function mirrorCatalog(catalog, env, target, prepareStaging) {
2765
+ async function mirrorCatalog(catalog, env, target, logicalIdentity, prepareStaging) {
2913
2766
  if (!target?.sourceId || !target.marketplaceRoot) {
2914
2767
  const rootDir = resolveAwHome(env);
2915
2768
  await fs.ensureDir(rootDir);
@@ -2957,6 +2810,7 @@ async function mirrorCatalog(catalog, env, target, prepareStaging) {
2957
2810
  schemaVersion: 2,
2958
2811
  sourceId: target.sourceId,
2959
2812
  sourceIdentity: target.sourceIdentity,
2813
+ logicalIdentity,
2960
2814
  marketplaceName,
2961
2815
  }, { spaces: 2 });
2962
2816
  const destinationExists = await fs.lstat(destination).then(() => true, (error) => {
@@ -3146,8 +3000,53 @@ async function applyPlannedOperationAction(action) {
3146
3000
  function createNativeDriver(options) {
3147
3001
  const runner = options.context.nativeCliRunner;
3148
3002
  const currentCodexManager = () => options.context.codexManager;
3149
- const mirrorTransactions = new WeakMap();
3003
+ const nativeGroupPreparations = new Map();
3004
+ const nativeTargetPreparations = new WeakMap();
3005
+ const codexNativeStates = new Map();
3006
+ const pendingCodexNativeStates = new Map();
3007
+ const codexMutationMarketplaces = new Set();
3150
3008
  const completedNativeSkillWrites = new WeakSet();
3009
+ const committedNativeGroups = new Set();
3010
+ let nativeMarketplaceEligibleTargets;
3011
+ let deferredPostchecks = [];
3012
+ const nativeGroupKey = (target) => [
3013
+ target.identity.sourceId ?? '',
3014
+ target.identity.sourceIdentity ?? '',
3015
+ target.identity.marketplaceName ?? MARKETPLACE_NAME,
3016
+ path.normalize(path.resolve(target.identity.marketplaceRoot ?? resolveAwHome(options.context.env))),
3017
+ target.identity.platform,
3018
+ ].join('\0');
3019
+ const observationScopeFor = (target) => codexObservationScope(options.context, target.identity.marketplaceName ?? MARKETPLACE_NAME, target.identity.scope);
3020
+ const groupTargets = (target) => {
3021
+ if (!options.operationScopedNativePhases)
3022
+ return [target];
3023
+ const key = nativeGroupKey(target);
3024
+ return options.targets.filter((candidate) => nativeGroupKey(candidate) === key
3025
+ && (nativeMarketplaceEligibleTargets?.has(candidate) ?? true));
3026
+ };
3027
+ const nativeSkillOverlay = async (target) => {
3028
+ const canonicalMarketplaceRoot = await canonicalizePathThroughExistingAncestor(target.identity.marketplaceRoot ?? resolveAwHome(options.context.env));
3029
+ const nativePluginRoot = await canonicalizePathThroughExistingAncestor(path.join(canonicalMarketplaceRoot, 'plugins', target.identity.pluginName));
3030
+ const desiredSkillPaths = new Set((target.desiredOwnedComponents ?? [])
3031
+ .filter((component) => component.kind === 'skill-directory')
3032
+ .map((component) => path.resolve(component.path)));
3033
+ const enabledSkillPaths = new Set(target.artifacts
3034
+ .filter((artifact) => artifact.kind === 'skill' && artifact.state !== 'disabled')
3035
+ .map((artifact) => path.resolve(artifact.path)));
3036
+ return {
3037
+ marketplaceRoot: canonicalMarketplaceRoot,
3038
+ writes: (target.operationActions ?? []).filter((action) => action.kind === 'writeOwnedArtifact'
3039
+ && action.artifactKind === 'skill-directory'
3040
+ && desiredSkillPaths.has(path.resolve(action.path))
3041
+ && enabledSkillPaths.has(path.resolve(action.path))
3042
+ && target.identity.scope === 'user'
3043
+ && !path.relative(nativePluginRoot, action.path).startsWith('..')),
3044
+ };
3045
+ };
3046
+ const asNativeGroupFailure = (error) => {
3047
+ const result = error instanceof Error ? error : new Error(String(error));
3048
+ return Object.assign(result, { nativeMarketplaceGroupFailure: true });
3049
+ };
3151
3050
  const findTarget = (component) => {
3152
3051
  const target = options.targets.find((entry) => entry.identity.pluginName === component.identity.plugin
3153
3052
  && entry.identity.sourceId === component.identity.sourceId
@@ -3157,13 +3056,144 @@ function createNativeDriver(options) {
3157
3056
  throw new Error('Ownership executor cannot resolve its desired target.');
3158
3057
  return target;
3159
3058
  };
3059
+ const codexStateKey = (target, pluginId) => `${nativeGroupKey(target)}\0${pluginId}`;
3060
+ const runObservedCodexMutation = async (options_) => runCodexMutationWithInvalidation({
3061
+ ...options_,
3062
+ ...(options.operationScopedNativePhases
3063
+ ? {}
3064
+ : { observationSession: options.context.codexObservationSession }),
3065
+ });
3066
+ const prepareNativeGroup = async (target) => {
3067
+ const key = nativeGroupKey(target);
3068
+ let preparation = options.operationScopedNativePhases
3069
+ ? nativeGroupPreparations.get(key)
3070
+ : nativeTargetPreparations.get(target);
3071
+ if (!preparation) {
3072
+ preparation = (async () => {
3073
+ const members = groupTargets(target);
3074
+ const materializing = members.filter((candidate) => candidate.writeNativeRequired ?? candidate.requiresNative);
3075
+ const overlayTargets = members.filter((candidate) => candidate.nativeOverlayDesired ?? candidate.requiresNative);
3076
+ if (target.identity.platform === 'codex') {
3077
+ const observationScope = codexObservationScope(options.context, target.identity.marketplaceName ?? MARKETPLACE_NAME, members.some((candidate) => candidate.identity.scope === 'project') ? 'project' : 'user');
3078
+ const nativePluginIds = [...new Set(members.flatMap((candidate) => candidate.ownedComponents.flatMap((component) => component.kind === 'native-plugin' ? [component.pluginId] : [])))].sort();
3079
+ for (const pluginId of nativePluginIds) {
3080
+ const member = members.find((candidate) => candidate.ownedComponents.some((component) => component.kind === 'native-plugin' && component.pluginId === pluginId));
3081
+ const registration = await inspectCodexPluginRegistrationLive({
3082
+ pluginId,
3083
+ cwd: member.identity.cwd,
3084
+ env: options.context.env,
3085
+ nativeCliRunner: runner,
3086
+ manager: currentCodexManager(),
3087
+ scope: observationScope,
3088
+ observationSession: options.context.codexObservationSession,
3089
+ });
3090
+ codexNativeStates.set(codexStateKey(member, pluginId), requireObservedCodexNativeState(registration.observation, 'native group precheck') === 'installed' ? 'present' : 'absent');
3091
+ }
3092
+ }
3093
+ if (materializing.length === 0)
3094
+ return;
3095
+ const first = materializing[0];
3096
+ if (!first.catalog)
3097
+ throw new Error('Missing rebuild target catalog data.');
3098
+ const catalogRoot = path.resolve(first.catalog.rootDir);
3099
+ if (overlayTargets.some((candidate) => !candidate.catalog || path.resolve(candidate.catalog.rootDir) !== catalogRoot)) {
3100
+ throw new Error('nativeMarketplaceGroupInvalid: grouped targets do not share one Catalog root.');
3101
+ }
3102
+ const materializationTargets = [...overlayTargets].sort((left, right) => [
3103
+ left.identity.pluginId ?? left.identity.pluginName,
3104
+ left.identity.scope,
3105
+ left.identity.cwd,
3106
+ ].join('\0').localeCompare([
3107
+ right.identity.pluginId ?? right.identity.pluginName,
3108
+ right.identity.scope,
3109
+ right.identity.cwd,
3110
+ ].join('\0'), 'en'));
3111
+ const nativeSkillOverlays = new Map(await Promise.all(materializationTargets.map(async (candidate) => [
3112
+ candidate,
3113
+ await nativeSkillOverlay(candidate),
3114
+ ])));
3115
+ const transaction = await mirrorCatalog(first.catalog, options.context.env, first.identity, first.plugin.catalogLogicalIdentity, first.identity.platform === 'codex' ? async (stagingRoot) => {
3116
+ for (const candidate of materializationTargets) {
3117
+ const overlay = nativeSkillOverlays.get(candidate);
3118
+ const requiredSkillWrites = overlay.writes;
3119
+ for (const action of requiredSkillWrites) {
3120
+ await applyPlannedOperationAction({
3121
+ ...action,
3122
+ path: path.join(stagingRoot, path.relative(overlay.marketplaceRoot, action.path)),
3123
+ allowedRoot: path.join(stagingRoot, path.relative(overlay.marketplaceRoot, action.allowedRoot)),
3124
+ });
3125
+ }
3126
+ const hasStaticSkills = candidate.plugin.manifest.skills !== undefined
3127
+ || requiredSkillWrites.length > 0;
3128
+ const preparedNativeRender = await requirePreparedCodexNativeRender({
3129
+ context: options.context,
3130
+ plugin: candidate.plugin,
3131
+ });
3132
+ const nativeRender = preparedNativeRender
3133
+ ? await materializeCodexNativeRender({
3134
+ plugin: candidate.plugin,
3135
+ stagingRoot,
3136
+ prepared: preparedNativeRender,
3137
+ hasStaticSkills,
3138
+ })
3139
+ : null;
3140
+ if (!nativeRender) {
3141
+ await ensureCodexActiveSkillsManifest({
3142
+ plugin: candidate.plugin,
3143
+ marketplaceRoot: stagingRoot,
3144
+ hasRequiredSkills: hasStaticSkills,
3145
+ });
3146
+ }
3147
+ if (pluginHasCodexNativeSkillSurface(candidate.plugin)) {
3148
+ await assertNoSensitiveValuesInCodexNativePayload({
3149
+ plugin: candidate.plugin,
3150
+ payloadRoot: path.join(stagingRoot, 'plugins', candidate.identity.pluginName),
3151
+ allowedRoot: stagingRoot,
3152
+ sensitiveValues: await resolveSensitiveUserConfigValues(candidate.plugin, options.context.env),
3153
+ ...(nativeRender ? { verifiedRenderReceipt: nativeRender.receipt } : {}),
3154
+ });
3155
+ }
3156
+ }
3157
+ } : undefined);
3158
+ try {
3159
+ await transaction.commit();
3160
+ }
3161
+ catch (error) {
3162
+ if (error.durableMaterializeCommit !== true) {
3163
+ try {
3164
+ await transaction.rollback();
3165
+ }
3166
+ catch (rollbackError) {
3167
+ throw new AggregateError([error, rollbackError], 'nativeMarketplaceGroupCommitFailed: marketplace commit and rollback failed.');
3168
+ }
3169
+ }
3170
+ throw error;
3171
+ }
3172
+ for (const candidate of materializationTargets) {
3173
+ for (const action of nativeSkillOverlays.get(candidate)?.writes ?? []) {
3174
+ completedNativeSkillWrites.add(action);
3175
+ }
3176
+ }
3177
+ committedNativeGroups.add(key);
3178
+ })().catch((error) => {
3179
+ throw asNativeGroupFailure(error);
3180
+ });
3181
+ if (options.operationScopedNativePhases) {
3182
+ nativeGroupPreparations.set(key, preparation);
3183
+ }
3184
+ else {
3185
+ nativeTargetPreparations.set(target, preparation);
3186
+ }
3187
+ }
3188
+ await preparation;
3189
+ };
3160
3190
  const inspectCodexMarketplace = async (component) => {
3161
3191
  if (component.locator.kind !== 'codex-marketplace')
3162
3192
  throw new Error('Invalid Codex marketplace locator.');
3163
3193
  const target = findTarget(component);
3164
3194
  const configPath = resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath;
3165
- const marketplace = (await readCodexConfigFile(configPath))
3166
- .snapshot.marketplaces[component.locator.marketplaceName];
3195
+ const config = await readCodexConfigFile(configPath);
3196
+ const marketplace = config.snapshot.marketplaces[component.locator.marketplaceName];
3167
3197
  const canonical = !marketplace
3168
3198
  ? 'absent'
3169
3199
  : marketplace.sourceType === 'local'
@@ -3171,29 +3201,47 @@ function createNativeDriver(options) {
3171
3201
  && path.normalize(marketplace.source ?? '') === path.normalize(component.locator.marketplaceRoot)
3172
3202
  ? 'present'
3173
3203
  : '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
3204
  if (component.locator.desired) {
3184
- return canonical === 'present' && plugin.state === 'present' && plugin.enabled
3185
- ? 'present'
3186
- : 'absent';
3205
+ if (canonical !== 'present')
3206
+ return canonical;
3207
+ const pluginId = `${component.identity.plugin}@${component.locator.marketplaceName}`;
3208
+ return config.snapshot.plugins[pluginId]?.enabled === true ? 'present' : 'absent';
3187
3209
  }
3188
3210
  if (canonical !== 'present')
3189
3211
  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';
3212
+ const suffix = `@${component.locator.marketplaceName}`;
3213
+ const currentPluginId = `${component.identity.plugin}${suffix}`;
3214
+ const projectConfig = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'project', options.context.env).configTomlPath);
3215
+ const configuredPluginIds = [
3216
+ ...Object.keys(config.snapshot.plugins),
3217
+ ...Object.keys(projectConfig.snapshot.plugins),
3218
+ ].filter((pluginId) => pluginId.endsWith(suffix));
3219
+ if (configuredPluginIds.some((pluginId) => pluginId !== currentPluginId))
3220
+ return 'absent';
3221
+ const ownedSession = options.context.codexObservationSession ? undefined
3222
+ : await createCodexNativeObservationSession({
3223
+ exactCwd: target.identity.cwd,
3224
+ env: options.context.env,
3225
+ runner,
3226
+ manager: currentCodexManager() ?? SYSTEM_CODEX_MANAGER,
3227
+ });
3228
+ const session = options.context.codexObservationSession ?? ownedSession;
3229
+ try {
3230
+ const observationScope = observationScopeFor(target);
3231
+ const snapshot = await session.snapshot(component.locator.marketplaceName, observationScope);
3232
+ if (!snapshot.neutral || (observationScope === 'both' && !snapshot.exact))
3233
+ return 'absent';
3234
+ const officialPluginIds = new Set([
3235
+ ...(snapshot.exact?.keys() ?? []),
3236
+ ...snapshot.neutral.keys(),
3237
+ ]);
3238
+ return [...officialPluginIds].some((pluginId) => pluginId !== currentPluginId)
3239
+ ? 'absent'
3240
+ : 'present';
3241
+ }
3242
+ finally {
3243
+ await ownedSession?.dispose();
3244
+ }
3197
3245
  };
3198
3246
  const inspectClaudeMarketplace = async (component) => {
3199
3247
  if (component.locator.kind !== 'claude-marketplace')
@@ -3335,6 +3383,15 @@ function createNativeDriver(options) {
3335
3383
  }) ? { removeAgentRegistrations: [locator.roleKey] } : null);
3336
3384
  return;
3337
3385
  case 'codex-marketplace': {
3386
+ if (locator.desired)
3387
+ return;
3388
+ const target = findTarget(component);
3389
+ await prepareNativeGroup(target);
3390
+ codexMutationMarketplaces.add(locator.marketplaceName);
3391
+ await runObservedCodexMutation({
3392
+ marketplaceName: locator.marketplaceName,
3393
+ mutation: () => runNativeCliChecked(buildCodexMarketplaceRemoveInvocation(currentCodexManager() ?? SYSTEM_CODEX_MANAGER, locator.marketplaceName, target.identity.cwd, options.context.env), runner),
3394
+ });
3338
3395
  return;
3339
3396
  }
3340
3397
  case 'claude-marketplace': {
@@ -3384,7 +3441,114 @@ function createNativeDriver(options) {
3384
3441
  };
3385
3442
  return {
3386
3443
  ownership,
3444
+ setNativeMarketplaceEligibleTargets: (eligibleTargets) => {
3445
+ nativeMarketplaceEligibleTargets = new Set(eligibleTargets);
3446
+ },
3447
+ prepareNativeMarketplaceGroup: prepareNativeGroup,
3448
+ selectRecoveryCleanupComponents: (target, components) => {
3449
+ if (!committedNativeGroups.has(nativeGroupKey(target)))
3450
+ return components;
3451
+ const marketplaceRoot = path.resolve(target.identity.marketplaceRoot ?? resolveAwHome(options.context.env));
3452
+ return components.filter((component) => {
3453
+ if (!('path' in component))
3454
+ return true;
3455
+ const relative = path.relative(marketplaceRoot, path.resolve(component.path));
3456
+ return relative === '..'
3457
+ || relative.startsWith(`..${path.sep}`)
3458
+ || path.isAbsolute(relative);
3459
+ });
3460
+ },
3461
+ ...(options.operationScopedNativePhases ? {
3462
+ deferPostcheck: (target, postcheck) => {
3463
+ if (target.identity.platform !== 'codex')
3464
+ return false;
3465
+ deferredPostchecks.push({
3466
+ target,
3467
+ postcheck: async () => {
3468
+ if (target.requiresNative) {
3469
+ const marketplaceName = target.identity.marketplaceName ?? MARKETPLACE_NAME;
3470
+ const pluginId = `${target.identity.pluginName}@${marketplaceName}`;
3471
+ const registration = await inspectCodexPluginRegistrationLive({
3472
+ pluginId,
3473
+ cwd: target.identity.cwd,
3474
+ env: options.context.env,
3475
+ nativeCliRunner: runner,
3476
+ manager: currentCodexManager(),
3477
+ scope: observationScopeFor(target),
3478
+ observationSession: options.context.codexObservationSession,
3479
+ });
3480
+ const state = requireObservedCodexNativeState(registration.observation, 'native install postcheck');
3481
+ requireCodexObservationCoverage(registration, observationScopeFor(target), 'native install postcheck');
3482
+ if (state !== 'installed') {
3483
+ throw new Error(`codexNativeInstallIncomplete: ${pluginId} is absent from Codex plugin list.`);
3484
+ }
3485
+ const installedEntry = registration.observation.state === 'observed'
3486
+ && registration.observation.value === 'installed'
3487
+ ? registration.observation.entry
3488
+ : undefined;
3489
+ if (installedEntry?.version !== undefined
3490
+ && installedEntry.version !== target.identity.pluginVersion) {
3491
+ throw new Error(`codexNativeInstallIncomplete: ${pluginId} version is ${installedEntry.version}; expected ${target.identity.pluginVersion}.`);
3492
+ }
3493
+ }
3494
+ await postcheck();
3495
+ },
3496
+ });
3497
+ return true;
3498
+ },
3499
+ completePostchecks: async () => {
3500
+ const pending = deferredPostchecks;
3501
+ deferredPostchecks = [];
3502
+ const failures = [];
3503
+ for (const entry of pending) {
3504
+ try {
3505
+ await entry.postcheck();
3506
+ }
3507
+ catch (error) {
3508
+ failures.push({ target: entry.target, error });
3509
+ }
3510
+ }
3511
+ return failures;
3512
+ },
3513
+ } : {}),
3514
+ captureCodexPostMutationSnapshots: async (marketplaceNames = []) => {
3515
+ for (const marketplaceName of marketplaceNames) {
3516
+ codexMutationMarketplaces.add(marketplaceName);
3517
+ }
3518
+ if (codexMutationMarketplaces.size === 0)
3519
+ return;
3520
+ const session = options.context.codexObservationSession;
3521
+ if (!session) {
3522
+ throw new Error('codexNativeObservationUnavailable: grouped postcheck requires an observation session.');
3523
+ }
3524
+ const marketplaces = [...codexMutationMarketplaces].sort();
3525
+ codexMutationMarketplaces.clear();
3526
+ for (const marketplaceName of marketplaces)
3527
+ session.invalidate(marketplaceName);
3528
+ await Promise.all(marketplaces.map((marketplaceName) => session.snapshot(marketplaceName, options.context.codexObservationScopeByMarketplace?.get(marketplaceName)
3529
+ ?? (options.targets.some((target) => (nativeMarketplaceEligibleTargets?.has(target) ?? true)
3530
+ && target.identity.platform === 'codex'
3531
+ && (target.identity.marketplaceName ?? MARKETPLACE_NAME) === marketplaceName
3532
+ && target.identity.scope === 'project')
3533
+ ? 'both'
3534
+ : 'user'))));
3535
+ },
3387
3536
  preflightMutation: async (target) => {
3537
+ if (!target.codexNativeObservationExpectation
3538
+ && !target.codexNativeSensitivePreflightRequired)
3539
+ return;
3540
+ const sensitiveValues = pluginHasCodexNativeSkillSurface(target.plugin)
3541
+ ? await resolveSensitiveUserConfigValues(target.plugin, options.context.env)
3542
+ : [];
3543
+ if (sensitiveValues.length > 0) {
3544
+ await assertNoSensitiveValuesInCanonicalCodexNativePayload({
3545
+ plugin: target.plugin,
3546
+ marketplaceRoot: target.identity.marketplaceRoot ?? resolveAwHome(options.context.env),
3547
+ marketplaceName: target.identity.marketplaceName ?? MARKETPLACE_NAME,
3548
+ env: options.context.env,
3549
+ sensitiveValues,
3550
+ });
3551
+ }
3388
3552
  if (!target.codexNativeObservationExpectation)
3389
3553
  return;
3390
3554
  const pluginId = `${target.identity.pluginName}@${target.identity.marketplaceName ?? MARKETPLACE_NAME}`;
@@ -3394,6 +3558,8 @@ function createNativeDriver(options) {
3394
3558
  env: options.context.env,
3395
3559
  nativeCliRunner: runner,
3396
3560
  manager: currentCodexManager(),
3561
+ scope: observationScopeFor(target),
3562
+ observationSession: options.context.codexObservationSession,
3397
3563
  });
3398
3564
  const observed = requireObservedCodexNativeState(registration.observation, 'artifact write precheck');
3399
3565
  if (target.codexNativeObservationExpectation === 'installed' && observed !== 'installed') {
@@ -3427,20 +3593,13 @@ function createNativeDriver(options) {
3427
3593
  || (Object.hasOwn(config.snapshot.plugins, expectedPluginId) && !canonical)) {
3428
3594
  throw new Error(`externalOwnershipPresent: Codex marketplace ${marketplaceName} has a foreign source instead of its canonical namespaced root.`);
3429
3595
  }
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);
3596
+ if (target.nativeOverlayDesired) {
3597
+ await prepareCodexNativeRenderCached({
3598
+ context: options.context,
3599
+ plugin: target.plugin,
3600
+ scriptTimeoutMs: options.scriptTimeoutMs,
3601
+ });
3602
+ }
3444
3603
  },
3445
3604
  finalizeCleanup: async () => undefined,
3446
3605
  probeNative: async (component) => {
@@ -3459,12 +3618,17 @@ function createNativeDriver(options) {
3459
3618
  return state.pluginState;
3460
3619
  }
3461
3620
  if (target.identity.platform === 'codex') {
3621
+ const pending = pendingCodexNativeStates.get(codexStateKey(target, component.pluginId));
3622
+ if (pending)
3623
+ return pending;
3462
3624
  const registration = await inspectCodexPluginRegistrationLive({
3463
3625
  pluginId: component.pluginId,
3464
3626
  cwd: target.identity.cwd,
3465
3627
  env: options.context.env,
3466
3628
  nativeCliRunner: runner,
3467
3629
  manager: currentCodexManager(),
3630
+ scope: observationScopeFor(target),
3631
+ observationSession: options.context.codexObservationSession,
3468
3632
  });
3469
3633
  return observedCodexNativeState(registration.observation, 'native probe');
3470
3634
  }
@@ -3495,41 +3659,20 @@ function createNativeDriver(options) {
3495
3659
  env: options.context.env,
3496
3660
  nativeCliRunner: runner,
3497
3661
  manager: currentCodexManager(),
3662
+ scope: observationScopeFor(target),
3663
+ observationSession: options.context.codexObservationSession,
3498
3664
  });
3499
3665
  const nativeState = observedCodexNativeState(registration.observation, 'native health postcheck');
3500
3666
  if (nativeState !== 'present')
3501
3667
  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
3668
  const userConfig = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath);
3669
+ const userEnabled = userConfig.snapshot.plugins[pluginId]?.enabled === true;
3670
+ if (target.identity.scope === 'user')
3671
+ return userEnabled ? 'present' : 'partial';
3509
3672
  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';
3673
+ return userEnabled || projectConfig.snapshot.plugins[pluginId]?.enabled === true
3674
+ ? 'present'
3675
+ : 'partial';
3533
3676
  }
3534
3677
  return 'absent';
3535
3678
  },
@@ -3537,33 +3680,51 @@ function createNativeDriver(options) {
3537
3680
  const target = findTarget(component);
3538
3681
  if (!target)
3539
3682
  throw new Error('Native ownership removal cannot resolve its desired target.');
3683
+ await prepareNativeGroup(target);
3540
3684
  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');
3685
+ const marketplaceName = component.pluginId.slice(component.pluginId.lastIndexOf('@') + 1);
3686
+ const stateKey = codexStateKey(target, component.pluginId);
3687
+ const before = pendingCodexNativeStates.get(stateKey) ?? codexNativeStates.get(stateKey);
3688
+ if (!before) {
3689
+ throw new Error('codexNativeObservationUnavailable: native group precheck is missing.');
3690
+ }
3549
3691
  const removalRequired = before === 'present';
3550
3692
  if (!removalRequired)
3551
3693
  return;
3552
3694
  try {
3553
- await runNativeCliChecked(buildCodexPluginRemoveInvocation(requireCodexManager(currentCodexManager()), component.pluginId, target.identity.cwd, options.context.env), runner);
3695
+ codexMutationMarketplaces.add(marketplaceName);
3696
+ await runObservedCodexMutation({
3697
+ marketplaceName,
3698
+ mutation: () => runNativeCliChecked(buildCodexPluginRemoveInvocation(requireCodexManager(currentCodexManager()), component.pluginId, target.identity.cwd, options.context.env), runner),
3699
+ });
3554
3700
  }
3555
3701
  catch {
3556
3702
  throw new Error(`codexNativeRemoveFailed: ${CODEX_REMOVE_RECOVERY}`);
3557
3703
  }
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}`);
3704
+ pendingCodexNativeStates.set(stateKey, 'absent');
3705
+ const postcheck = async () => {
3706
+ const after = await inspectCodexPluginRegistrationLive({
3707
+ pluginId: component.pluginId,
3708
+ cwd: target.identity.cwd,
3709
+ env: options.context.env,
3710
+ nativeCliRunner: runner,
3711
+ manager: currentCodexManager(),
3712
+ scope: observationScopeFor(target),
3713
+ observationSession: options.context.codexObservationSession,
3714
+ });
3715
+ const expected = pendingCodexNativeStates.get(stateKey) ?? 'absent';
3716
+ const observed = requireObservedCodexNativeState(after.observation, 'native mutation postcheck');
3717
+ if ((observed === 'installed' ? 'present' : 'absent') !== expected) {
3718
+ throw new Error(expected === 'absent'
3719
+ ? `codexNativeRemoveIncomplete: ${CODEX_REMOVE_RECOVERY}`
3720
+ : `codexNativeInstallIncomplete: ${component.pluginId} is absent from Codex plugin list.`);
3721
+ }
3722
+ };
3723
+ if (options.operationScopedNativePhases) {
3724
+ deferredPostchecks.push({ target, postcheck });
3725
+ }
3726
+ else {
3727
+ await postcheck();
3567
3728
  }
3568
3729
  }
3569
3730
  else {
@@ -3595,169 +3756,106 @@ function createNativeDriver(options) {
3595
3756
  writeNative: async (target) => {
3596
3757
  if (!target.catalog)
3597
3758
  throw new Error('Missing rebuild target catalog data.');
3598
- const catalog = target.catalog;
3599
3759
  const marketplaceName = target.identity.marketplaceName ?? MARKETPLACE_NAME;
3600
3760
  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.');
3761
+ if (target.identity.platform === 'codex') {
3762
+ const config = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath);
3763
+ const marketplace = config.snapshot.marketplaces[marketplaceName];
3764
+ if (marketplace && (marketplace.sourceType !== 'local'
3765
+ || marketplace.sourcePointsToManifest
3766
+ || path.normalize(marketplace.source ?? '') !== path.normalize(expectedMarketplaceRoot))) {
3767
+ throw new Error(`externalOwnershipPresent: Codex marketplace ${marketplaceName} 已由 foreign source 占用,拒绝覆盖。`);
3604
3768
  }
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
- }
3769
+ }
3770
+ else {
3771
+ const state = await readClaudeNativeState({
3772
+ pluginName: target.identity.pluginName,
3773
+ scope: target.identity.scope,
3774
+ cwd: target.identity.cwd,
3775
+ env: options.context.env,
3776
+ marketplaceName,
3777
+ marketplaceRoot: expectedMarketplaceRoot,
3778
+ });
3779
+ if (state.marketplaceState === 'foreign') {
3780
+ throw new Error(`Claude marketplace ${marketplaceName} 已由 foreign source 占用,拒绝覆盖。`);
3758
3781
  }
3759
- };
3760
- await writeGeneration();
3782
+ }
3783
+ await prepareNativeGroup(target);
3784
+ const pluginId = `${target.identity.pluginName}@${marketplaceName}`;
3785
+ if (target.identity.platform === 'codex') {
3786
+ const stateKey = codexStateKey(target, pluginId);
3787
+ const codexRemovalRequired = pendingCodexNativeStates.get(stateKey)
3788
+ ?? codexNativeStates.get(stateKey);
3789
+ if (!codexRemovalRequired) {
3790
+ throw new Error('codexNativeObservationUnavailable: native group precheck is missing.');
3791
+ }
3792
+ if (codexRemovalRequired === 'present') {
3793
+ codexMutationMarketplaces.add(marketplaceName);
3794
+ await runObservedCodexMutation({
3795
+ marketplaceName,
3796
+ mutation: () => runNativeCliChecked(buildCodexPluginRemoveInvocation(requireCodexManager(currentCodexManager()), pluginId, target.identity.cwd, options.context.env), runner),
3797
+ });
3798
+ pendingCodexNativeStates.set(stateKey, 'absent');
3799
+ }
3800
+ const config = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath);
3801
+ const marketplace = config.snapshot.marketplaces[marketplaceName];
3802
+ if (path.normalize(marketplace?.source ?? '') !== path.normalize(expectedMarketplaceRoot)) {
3803
+ codexMutationMarketplaces.add(marketplaceName);
3804
+ await runObservedCodexMutation({
3805
+ marketplaceName,
3806
+ mutation: () => runNativeCliChecked(buildCodexMarketplaceAddInvocation(requireCodexManager(currentCodexManager()), expectedMarketplaceRoot, target.identity.cwd, options.context.env), runner),
3807
+ });
3808
+ }
3809
+ codexMutationMarketplaces.add(marketplaceName);
3810
+ await runObservedCodexMutation({
3811
+ marketplaceName,
3812
+ mutation: () => runNativeCliChecked(buildCodexPluginAddInvocation(requireCodexManager(currentCodexManager()), pluginId, target.identity.cwd, options.context.env), runner),
3813
+ });
3814
+ pendingCodexNativeStates.set(stateKey, 'present');
3815
+ const installedConfig = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath);
3816
+ const installedMarketplace = installedConfig.snapshot.marketplaces[marketplaceName];
3817
+ if (installedMarketplace?.sourceType !== 'local'
3818
+ || installedMarketplace.sourcePointsToManifest
3819
+ || path.normalize(installedMarketplace.source ?? '') !== path.normalize(expectedMarketplaceRoot)
3820
+ || installedConfig.snapshot.plugins[pluginId]?.enabled !== true) {
3821
+ throw new Error(`codexNativeInstallIncomplete: ${pluginId} public config is not canonical and enabled.`);
3822
+ }
3823
+ }
3824
+ else {
3825
+ await runNativeCliChecked({
3826
+ command: 'claude',
3827
+ args: ['plugin', 'marketplace', 'add', expectedMarketplaceRoot, '--scope', target.identity.scope],
3828
+ cwd: target.identity.cwd,
3829
+ env: options.context.env,
3830
+ }, runner);
3831
+ await runNativeCliChecked({
3832
+ command: 'claude',
3833
+ args: ['plugin', 'install', pluginId, '--scope', target.identity.scope],
3834
+ cwd: target.identity.cwd,
3835
+ env: options.context.env,
3836
+ }, runner);
3837
+ await runNativeCliChecked({
3838
+ command: 'claude',
3839
+ args: ['plugin', 'list'],
3840
+ cwd: target.identity.cwd,
3841
+ env: options.context.env,
3842
+ }, runner);
3843
+ const after = await readClaudeNativeState({
3844
+ pluginName: target.identity.pluginName,
3845
+ scope: target.identity.scope,
3846
+ cwd: target.identity.cwd,
3847
+ env: options.context.env,
3848
+ marketplaceName,
3849
+ marketplaceRoot: expectedMarketplaceRoot,
3850
+ });
3851
+ if (after.marketplaceDeclaredState !== 'canonical'
3852
+ || after.marketplaceMaterializedState !== 'canonical'
3853
+ || after.pluginState !== 'present'
3854
+ || !after.pluginEnabled
3855
+ || !after.pluginVersions.includes(target.identity.pluginVersion)) {
3856
+ throw new Error(`Claude native install postcheck 失败:declaredMarketplace=${after.marketplaceDeclaredState},materializedMarketplace=${after.marketplaceMaterializedState},plugin=${after.pluginState},enabled=${after.pluginEnabled},versions=${after.pluginVersions.join(',')}。`);
3857
+ }
3858
+ }
3761
3859
  },
3762
3860
  writeArtifacts: async (target) => {
3763
3861
  for (const operationAction of target.operationActions ?? []) {
@@ -3771,33 +3869,45 @@ function createNativeDriver(options) {
3771
3869
  }
3772
3870
  return target.artifacts;
3773
3871
  },
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
- });
3872
+ runPreApply: async (invocation) => {
3873
+ const run = () => runPreApplyScript({
3874
+ plugin: invocation.plugin,
3875
+ platform: invocation.target.identity.platform,
3876
+ scope: invocation.target.identity.scope,
3877
+ cwd: invocation.target.identity.cwd,
3878
+ context: options.context,
3879
+ scriptTimeoutMs: options.scriptTimeoutMs,
3880
+ });
3881
+ const cache = options.context.preApplyExecutionCache;
3882
+ if (!cache) {
3883
+ await run();
3884
+ return;
3786
3885
  }
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;
3886
+ const { identity } = invocation.target;
3887
+ const sourceIdentity = invocation.plugin.sourceIdentity ?? identity.sourceIdentity;
3888
+ const pluginId = invocation.plugin.pluginId ?? identity.pluginId;
3889
+ if (!sourceIdentity || !pluginId) {
3890
+ await run();
3891
+ return;
3800
3892
  }
3893
+ const projectKey = identity.scope === 'project'
3894
+ ? identity.platform === 'codex'
3895
+ ? await resolveCodexProjectKey(identity.cwd)
3896
+ : await resolveClaudeProjectKey(identity.cwd)
3897
+ : '';
3898
+ const key = [
3899
+ sourceIdentity,
3900
+ pluginId,
3901
+ identity.platform,
3902
+ identity.scope,
3903
+ projectKey,
3904
+ ].join('\0');
3905
+ let execution = cache.get(key);
3906
+ if (!execution) {
3907
+ execution = run();
3908
+ cache.set(key, execution);
3909
+ }
3910
+ await execution;
3801
3911
  },
3802
3912
  };
3803
3913
  }
@@ -3806,6 +3916,9 @@ export function createRebuildIntegration(options) {
3806
3916
  const driver = createNativeDriver({ targets, ...options });
3807
3917
  return {
3808
3918
  driver,
3919
+ setNativeMarketplaceEligibleTargets: driver.setNativeMarketplaceEligibleTargets,
3920
+ prepareNativeMarketplaceGroup: driver.prepareNativeMarketplaceGroup,
3921
+ captureCodexPostMutationSnapshots: driver.captureCodexPostMutationSnapshots,
3809
3922
  buildTarget: async (input) => {
3810
3923
  input = {
3811
3924
  ...input,
@@ -3920,7 +4033,11 @@ export function createRebuildIntegration(options) {
3920
4033
  })))).flat();
3921
4034
  const skillRoots = input.platform === 'codex'
3922
4035
  ? input.scope === 'project'
3923
- ? []
4036
+ ? materializing
4037
+ || ((input.command === 'cleanup' || input.command === 'uninstall')
4038
+ && input.readOnlyInspectionPlan !== true)
4039
+ ? [path.join(input.cwd, '.agents', 'skills')]
4040
+ : []
3924
4041
  : [
3925
4042
  path.join(getCodexHome(options.context.env), 'skills'),
3926
4043
  ...(preserveCodexSharedNativePayload || input.readOnlyShallowInspectionPlan === true
@@ -3935,6 +4052,7 @@ export function createRebuildIntegration(options) {
3935
4052
  pluginName: input.plugin.manifest.name,
3936
4053
  platform: input.platform,
3937
4054
  scope: input.scope,
4055
+ recursive: !(input.platform === 'codex' && input.scope === 'project'),
3938
4056
  })));
3939
4057
  const scannedSkills = (await scanSkills()).flat();
3940
4058
  const legacyCodexNativeSkills = input.platform === 'codex'
@@ -3978,15 +4096,14 @@ export function createRebuildIntegration(options) {
3978
4096
  command: input.command,
3979
4097
  });
3980
4098
  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)));
4099
+ const nativePluginRoot = await canonicalizePathThroughExistingAncestor(path.join(input.marketplaceRoot ?? resolveAwHome(options.context.env), 'plugins', input.plugin.manifest.name));
4100
+ const privateCacheRoot = path.resolve(getCodexHome(options.context.env), 'plugins', 'cache');
3985
4101
  historicalComponents = historicalComponents.filter((component) => {
3986
4102
  if (!('path' in component))
3987
4103
  return true;
3988
- return nativePluginRoots.every((nativePluginRoot) => {
3989
- const relative = path.relative(nativePluginRoot, path.resolve(component.path));
4104
+ const componentPath = path.resolve(component.path);
4105
+ return [nativePluginRoot, privateCacheRoot].every((protectedRoot) => {
4106
+ const relative = path.relative(protectedRoot, componentPath);
3990
4107
  return relative === '..'
3991
4108
  || relative.startsWith(`..${path.sep}`)
3992
4109
  || path.isAbsolute(relative);
@@ -4017,23 +4134,31 @@ export function createRebuildIntegration(options) {
4017
4134
  const nativeDesired = nativeRequired
4018
4135
  && input.command !== 'cleanup'
4019
4136
  && input.command !== 'uninstall';
4020
- const codexNative = input.platform === 'codex' && nativeRequired
4021
- ? await inspectCodexNativeResidue({
4022
- pluginName: input.plugin.manifest.name,
4137
+ const codexRegistration = input.platform === 'codex'
4138
+ && nativeDesired
4139
+ && input.readOnlyInspectionPlan !== true
4140
+ ? await inspectCodexPluginRegistrationLive({
4141
+ pluginId: `${input.plugin.manifest.name}@${input.marketplaceName ?? MARKETPLACE_NAME}`,
4023
4142
  cwd: input.cwd,
4024
4143
  env: options.context.env,
4025
- marketplaceName: input.marketplaceName,
4026
- marketplaceRoot: input.marketplaceRoot,
4144
+ nativeCliRunner: options.context.nativeCliRunner,
4145
+ manager: options.context.codexManager,
4146
+ scope: codexObservationScope(options.context, input.marketplaceName ?? MARKETPLACE_NAME, input.scope),
4147
+ observationSession: options.context.codexObservationSession,
4027
4148
  })
4028
4149
  : undefined;
4029
- const codexNativeExact = codexNative?.state === 'present'
4030
- && codexNative.versions.length > 0
4031
- && codexNative.versions.every((version) => version === input.plugin.manifest.version);
4150
+ const codexNativeExact = codexRegistration?.observation.state === 'observed'
4151
+ && codexRegistration.observation.value === 'installed'
4152
+ && (codexRegistration.observation.entry.version === undefined
4153
+ || codexRegistration.observation.entry.version === input.plugin.manifest.version);
4032
4154
  const materializeNativeRequired = nativeDesired && (input.platform === 'claude'
4033
4155
  || input.command === 'reinstall'
4034
4156
  || !codexNativeExact);
4035
4157
  const nativeCleanupCandidate = input.platform === 'claude'
4036
- && input.includeNativeCleanupCandidate !== false;
4158
+ ? input.includeNativeCleanupCandidate !== false
4159
+ : input.platform === 'codex'
4160
+ && nativeRequired
4161
+ && (input.command === 'cleanup' || input.command === 'uninstall');
4037
4162
  const nativePlugin = (nativeDesired || nativeCleanupCandidate)
4038
4163
  ? [{
4039
4164
  kind: 'native-plugin',
@@ -4046,7 +4171,7 @@ export function createRebuildIntegration(options) {
4046
4171
  && input.command !== 'uninstall';
4047
4172
  const marketplace = input.platform === 'codex'
4048
4173
  && input.scope === 'user'
4049
- && nativeDesired
4174
+ && nativeRequired
4050
4175
  && input.readOnlyShallowInspectionPlan !== true
4051
4176
  ? [createCodexMarketplaceComponent({
4052
4177
  pluginName: input.plugin.manifest.name,
@@ -4116,36 +4241,37 @@ export function createRebuildIntegration(options) {
4116
4241
  ]);
4117
4242
  bindNamespacedOwnerIdentity(allComponents, input);
4118
4243
  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',
4244
+ const operationActions = [];
4245
+ const preApply = input.plugin.manifest.scripts?.preApply;
4246
+ if (preApply && input.command !== 'cleanup' && input.command !== 'uninstall') {
4247
+ const script = path.resolve(input.plugin.rootDir, preApply.path.replace(/^\.\//u, ''));
4248
+ const scriptDigest = `sha256:${crypto.createHash('sha256')
4249
+ .update(await fs.readFile(script)).digest('hex')}`;
4250
+ operationActions.push({
4251
+ kind: 'runPreApply',
4127
4252
  pluginId: input.pluginId ?? pluginActionId(input.plugin),
4128
4253
  platform: input.platform,
4129
4254
  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),
4255
+ script: preApply.path,
4256
+ scriptDigest,
4133
4257
  });
4134
4258
  }
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')}`;
4259
+ if (nativeDesired) {
4140
4260
  operationActions.push({
4141
- kind: 'runPostApply',
4261
+ kind: 'ensureNativeRegistration',
4142
4262
  pluginId: input.pluginId ?? pluginActionId(input.plugin),
4143
4263
  platform: input.platform,
4144
4264
  scope: input.scope,
4145
- script: postApply.path,
4146
- scriptDigest,
4265
+ registration: `${input.plugin.manifest.name}@${input.marketplaceName ?? MARKETPLACE_NAME}`,
4266
+ marketplaceName: input.marketplaceName ?? MARKETPLACE_NAME,
4267
+ marketplaceRoot: input.marketplaceRoot ?? resolveAwHome(options.context.env),
4147
4268
  });
4148
4269
  }
4270
+ operationActions.push(...writes.flatMap((write) => [
4271
+ ...(write.action ? [write.action] : []),
4272
+ ...(write.additionalActions ?? []),
4273
+ ]));
4274
+ finalizeOwnedArtifactActions(operationActions);
4149
4275
  const target = {
4150
4276
  plugin: input.plugin,
4151
4277
  identity: {
@@ -4167,10 +4293,11 @@ export function createRebuildIntegration(options) {
4167
4293
  optionalOwnedComponents: allComponents.filter((component) => optionalComponentKeys.has(componentIdentityKey(component))),
4168
4294
  requiresNative: nativeDesired,
4169
4295
  writeNativeRequired: materializeNativeRequired,
4296
+ nativeOverlayDesired: nativeDesired,
4170
4297
  artifacts: writes.map((write) => write.artifact),
4171
4298
  operationActions,
4172
4299
  catalog: input.catalog,
4173
- postApplySource: input.plugin,
4300
+ preApplySource: input.plugin,
4174
4301
  };
4175
4302
  targets.push(target);
4176
4303
  return target;
@@ -4213,24 +4340,22 @@ export async function inspectRebuildTarget(options) {
4213
4340
  if (!target.requiresNative)
4214
4341
  return { target, inspection };
4215
4342
  if (target.identity.platform === 'codex') {
4216
- const residue = await inspectCodexNativeResidue({
4217
- pluginName: target.identity.pluginName,
4343
+ const nativeInspection = inspection.components.find((component) => component.component.kind === 'native-plugin');
4344
+ if (nativeInspection?.state !== 'present')
4345
+ return { target, inspection };
4346
+ const registration = await inspectCodexPluginRegistrationLive({
4347
+ pluginId: `${target.identity.pluginName}@${target.identity.marketplaceName ?? MARKETPLACE_NAME}`,
4218
4348
  cwd: target.identity.cwd,
4219
4349
  env: options.context.env,
4220
- marketplaceName: target.identity.marketplaceName,
4221
- marketplaceRoot: target.identity.marketplaceRoot,
4350
+ nativeCliRunner: options.context.nativeCliRunner,
4351
+ manager: options.context.codexManager,
4352
+ scope: target.identity.scope,
4353
+ observationSession: options.context.codexObservationSession,
4222
4354
  });
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] }
4355
+ return registration.observation.state === 'observed'
4356
+ && registration.observation.value === 'installed'
4357
+ && registration.observation.entry.version !== undefined
4358
+ ? { target, inspection, installedVersion: registration.observation.entry.version }
4234
4359
  : { target, inspection };
4235
4360
  }
4236
4361
  const native = await readClaudeNativeState({
@@ -4366,13 +4491,48 @@ export async function discoverRebuildTargetNames(options) {
4366
4491
  }
4367
4492
  }
4368
4493
  else {
4369
- for (const name of await discoverCodexNativeResidueNames({
4370
- cwd: options.cwd,
4494
+ const marketplaceName = options.marketplaceName ?? MARKETPLACE_NAME;
4495
+ const marketplaceRoot = options.marketplaceRoot ?? resolveAwHome(options.env);
4496
+ const marketplace = config.snapshot.marketplaces[marketplaceName];
4497
+ const canonicalMarketplace = marketplace?.sourceType === 'local'
4498
+ && !marketplace.sourcePointsToManifest
4499
+ && path.normalize(marketplace.source ?? '') === path.normalize(marketplaceRoot);
4500
+ const suffix = `@${marketplaceName}`;
4501
+ const configuredPluginIds = Object.keys(config.snapshot.plugins)
4502
+ .filter((pluginId) => pluginId.endsWith(suffix));
4503
+ if (configuredPluginIds.length > 0 && !canonicalMarketplace) {
4504
+ throw new Error(`externalOwnershipPresent: Codex marketplace ${marketplaceName} has a foreign public config source.`);
4505
+ }
4506
+ for (const pluginId of canonicalMarketplace ? configuredPluginIds : []) {
4507
+ const name = pluginId.slice(0, -suffix.length);
4508
+ if (/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name))
4509
+ names.add(name);
4510
+ }
4511
+ const session = await createCodexNativeObservationSession({
4512
+ exactCwd: options.cwd,
4371
4513
  env: options.env,
4372
- marketplaceName: options.marketplaceName,
4373
- marketplaceRoot: options.marketplaceRoot,
4374
- }))
4375
- names.add(name);
4514
+ runner: options.nativeCliRunner,
4515
+ });
4516
+ try {
4517
+ const snapshot = await session.snapshot(marketplaceName, 'user');
4518
+ if (!snapshot.neutral) {
4519
+ if (options.nativeFailureMode !== 'ignore') {
4520
+ throw new Error('codexNativeObservationFailed: cannot discover official Codex plugins.');
4521
+ }
4522
+ }
4523
+ else {
4524
+ for (const pluginId of snapshot.neutral.keys()) {
4525
+ if (!pluginId.endsWith(suffix))
4526
+ continue;
4527
+ const name = pluginId.slice(0, -suffix.length);
4528
+ if (/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name))
4529
+ names.add(name);
4530
+ }
4531
+ }
4532
+ }
4533
+ finally {
4534
+ await session.dispose();
4535
+ }
4376
4536
  }
4377
4537
  }
4378
4538
  else {