@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.
- package/dist/authoring/assets/aw-config.d.mts +1 -50
- package/dist/authoring/assets/aw-config.mjs +58 -928
- package/dist/authoring/assets/aw-config.mjs.map +1 -1
- package/dist/authoring/scripts/init-plugin.mjs +28 -33
- package/dist/authoring/scripts/init-plugin.mjs.map +1 -1
- package/dist/authoring/scripts/lib/plugin-version-check.d.mts +1 -0
- package/dist/authoring/scripts/lib/plugin-version-check.mjs +229 -8
- package/dist/authoring/scripts/lib/plugin-version-check.mjs.map +1 -1
- package/dist/authoring/scripts/validate-plugin.mjs +159 -48
- package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
- package/dist/scripts/bundle-plugins.js +18 -4
- package/dist/scripts/bundle-plugins.js.map +1 -1
- package/dist/scripts/test-plugin-scripts.d.ts +2 -0
- package/dist/scripts/test-plugin-scripts.js +21 -5
- package/dist/scripts/test-plugin-scripts.js.map +1 -1
- package/dist/src/commands/aw-capabilities.d.ts +1 -1
- package/dist/src/commands/aw-capabilities.js +1 -1
- package/dist/src/commands/aw-capabilities.js.map +1 -1
- package/dist/src/commands/catalog-dev-source.d.ts +6 -3
- package/dist/src/commands/catalog-dev-source.js +96 -35
- package/dist/src/commands/catalog-dev-source.js.map +1 -1
- package/dist/src/commands/catalog-publication-inspection.d.ts +1 -1
- package/dist/src/commands/catalog-publication-inspection.js +54 -8
- package/dist/src/commands/catalog-publication-inspection.js.map +1 -1
- package/dist/src/commands/catalog-publication-journal.js +1 -1
- package/dist/src/commands/catalog-publication-journal.js.map +1 -1
- package/dist/src/commands/catalog-source.d.ts +19 -4
- package/dist/src/commands/catalog-source.js +121 -12
- package/dist/src/commands/catalog-source.js.map +1 -1
- package/dist/src/commands/catalog-store.d.ts +26 -7
- package/dist/src/commands/catalog-store.js +361 -42
- package/dist/src/commands/catalog-store.js.map +1 -1
- package/dist/src/commands/console.js +28 -17
- package/dist/src/commands/console.js.map +1 -1
- package/dist/src/commands/operation-plan.d.ts +2 -1
- package/dist/src/commands/operation-plan.js +24 -0
- package/dist/src/commands/operation-plan.js.map +1 -1
- package/dist/src/commands/operation-planner.d.ts +2 -2
- package/dist/src/commands/operation-planner.js +22 -20
- package/dist/src/commands/operation-planner.js.map +1 -1
- package/dist/src/commands/options.d.ts +2 -2
- package/dist/src/commands/options.js +18 -4
- package/dist/src/commands/options.js.map +1 -1
- package/dist/src/commands/plugin-capabilities.js +2 -2
- package/dist/src/commands/plugin-capabilities.js.map +1 -1
- package/dist/src/commands/rebuild-command.d.ts +19 -0
- package/dist/src/commands/rebuild-command.js +828 -283
- package/dist/src/commands/rebuild-command.js.map +1 -1
- package/dist/src/commands/rebuild-integration.d.ts +25 -47
- package/dist/src/commands/rebuild-integration.js +1176 -1016
- package/dist/src/commands/rebuild-integration.js.map +1 -1
- package/dist/src/commands/rebuild-operations.d.ts +50 -12
- package/dist/src/commands/rebuild-operations.js +207 -101
- package/dist/src/commands/rebuild-operations.js.map +1 -1
- package/dist/src/commands/status-v6.d.ts +21 -2
- package/dist/src/commands/status-v6.js +163 -31
- package/dist/src/commands/status-v6.js.map +1 -1
- package/dist/src/commands/sync-command.d.ts +17 -0
- package/dist/src/commands/sync-command.js +45 -37
- package/dist/src/commands/sync-command.js.map +1 -1
- package/dist/src/commands/write-commands.d.ts +5 -0
- package/dist/src/commands/write-commands.js +50 -3
- package/dist/src/commands/write-commands.js.map +1 -1
- package/dist/src/generators/local-plugin.js +14 -4
- package/dist/src/generators/local-plugin.js.map +1 -1
- package/dist/src/index.js +260 -91
- package/dist/src/index.js.map +1 -1
- package/dist/src/output/command-receipt.js +1 -3
- package/dist/src/output/command-receipt.js.map +1 -1
- package/dist/src/output/rebuild-progress.js +1 -3
- package/dist/src/output/rebuild-progress.js.map +1 -1
- package/dist/src/portable/materializer.js +10 -8
- package/dist/src/portable/materializer.js.map +1 -1
- package/dist/src/tui/catalog-generation.js +41 -1
- package/dist/src/tui/catalog-generation.js.map +1 -1
- package/dist/src/tui/catalog-sources/session.d.ts +1 -0
- package/dist/src/tui/catalog-sources/session.js +16 -3
- package/dist/src/tui/catalog-sources/session.js.map +1 -1
- package/dist/src/tui/catalog-sources/types.d.ts +3 -2
- package/dist/src/tui/components.d.ts +4 -0
- package/dist/src/tui/components.js +36 -24
- package/dist/src/tui/components.js.map +1 -1
- package/dist/src/tui/console-app-v4.d.ts +13 -4
- package/dist/src/tui/console-app-v4.js +2065 -857
- package/dist/src/tui/console-app-v4.js.map +1 -1
- package/dist/src/tui/controller-runtime.js +731 -62
- package/dist/src/tui/controller-runtime.js.map +1 -1
- package/dist/src/tui/controller.d.ts +156 -10
- package/dist/src/tui/controller.js +1187 -163
- package/dist/src/tui/controller.js.map +1 -1
- package/dist/src/tui/diagnostic-projection.d.ts +22 -0
- package/dist/src/tui/diagnostic-projection.js +437 -0
- package/dist/src/tui/diagnostic-projection.js.map +1 -0
- package/dist/src/tui/diagnostics.d.ts +1 -1
- package/dist/src/tui/diagnostics.js +7 -2
- package/dist/src/tui/diagnostics.js.map +1 -1
- package/dist/src/tui/glossary.d.ts +23 -0
- package/dist/src/tui/glossary.js +42 -0
- package/dist/src/tui/glossary.js.map +1 -0
- package/dist/src/tui/index.d.ts +4 -1
- package/dist/src/tui/index.js +4 -1
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/tui/keys.d.ts +44 -0
- package/dist/src/tui/keys.js +43 -15
- package/dist/src/tui/keys.js.map +1 -1
- package/dist/src/tui/lifecycle-action-view.d.ts +6 -0
- package/dist/src/tui/lifecycle-action-view.js +34 -0
- package/dist/src/tui/lifecycle-action-view.js.map +1 -0
- package/dist/src/tui/model.d.ts +12 -2
- package/dist/src/tui/model.js +128 -52
- package/dist/src/tui/model.js.map +1 -1
- package/dist/src/tui/operation-diagnostic-screens.d.ts +21 -0
- package/dist/src/tui/operation-diagnostic-screens.js +101 -0
- package/dist/src/tui/operation-diagnostic-screens.js.map +1 -0
- package/dist/src/tui/operation-history.d.ts +80 -0
- package/dist/src/tui/operation-history.js +130 -0
- package/dist/src/tui/operation-history.js.map +1 -0
- package/dist/src/tui/operation-review-planner.js +366 -99
- package/dist/src/tui/operation-review-planner.js.map +1 -1
- package/dist/src/tui/router.d.ts +82 -17
- package/dist/src/tui/router.js +186 -38
- package/dist/src/tui/router.js.map +1 -1
- package/dist/src/tui/screen-registry.d.ts +546 -0
- package/dist/src/tui/screen-registry.js +179 -0
- package/dist/src/tui/screen-registry.js.map +1 -0
- package/dist/src/tui/session-console-app.d.ts +6 -0
- package/dist/src/tui/session-console-app.js +13 -0
- package/dist/src/tui/session-console-app.js.map +1 -0
- package/dist/src/tui/session-host.d.ts +44 -0
- package/dist/src/tui/session-host.js +197 -0
- package/dist/src/tui/session-host.js.map +1 -0
- package/dist/src/types.d.ts +64 -3
- package/dist/src/types.js +1 -1
- package/dist/src/types.js.map +1 -1
- package/dist/src/utils/agent-version.js +1 -1
- package/dist/src/utils/agent-version.js.map +1 -1
- package/dist/src/utils/artifact-plan.d.ts +2 -2
- package/dist/src/utils/artifact-plan.js +8 -6
- package/dist/src/utils/artifact-plan.js.map +1 -1
- package/dist/src/utils/artifact-renderer.d.ts +2 -1
- package/dist/src/utils/artifact-renderer.js +139 -120
- package/dist/src/utils/artifact-renderer.js.map +1 -1
- package/dist/src/utils/artifact-request.d.ts +5 -0
- package/dist/src/utils/artifact-request.js +40 -0
- package/dist/src/utils/artifact-request.js.map +1 -1
- package/dist/src/utils/bundle-layout.js +4 -4
- package/dist/src/utils/bundle-layout.js.map +1 -1
- package/dist/src/utils/catalog-capability-projection.d.ts +8 -0
- package/dist/src/utils/catalog-capability-projection.js +14 -0
- package/dist/src/utils/catalog-capability-projection.js.map +1 -0
- package/dist/src/utils/catalog-generation-resolver.d.ts +14 -2
- package/dist/src/utils/catalog-generation-resolver.js +49 -27
- package/dist/src/utils/catalog-generation-resolver.js.map +1 -1
- package/dist/src/utils/catalog-generation-service.d.ts +8 -1
- package/dist/src/utils/catalog-generation-service.js +81 -47
- package/dist/src/utils/catalog-generation-service.js.map +1 -1
- package/dist/src/utils/catalog-generation-store.d.ts +1 -0
- package/dist/src/utils/catalog-generation-store.js +60 -5
- package/dist/src/utils/catalog-generation-store.js.map +1 -1
- package/dist/src/utils/catalog-operation-context.js +1 -0
- package/dist/src/utils/catalog-operation-context.js.map +1 -1
- package/dist/src/utils/catalog-public-error.js +4 -0
- package/dist/src/utils/catalog-public-error.js.map +1 -1
- package/dist/src/utils/catalog-source-impact.d.ts +3 -2
- package/dist/src/utils/catalog-source-impact.js +142 -20
- package/dist/src/utils/catalog-source-impact.js.map +1 -1
- package/dist/src/utils/catalog-source-references.js +95 -9
- package/dist/src/utils/catalog-source-references.js.map +1 -1
- package/dist/src/utils/catalog-sources.d.ts +1 -1
- package/dist/src/utils/catalog-sources.js +19 -3
- package/dist/src/utils/catalog-sources.js.map +1 -1
- package/dist/src/utils/codex-manager.js +8 -0
- package/dist/src/utils/codex-manager.js.map +1 -1
- package/dist/src/utils/codex-native-observation.d.ts +34 -1
- package/dist/src/utils/codex-native-observation.js +132 -0
- package/dist/src/utils/codex-native-observation.js.map +1 -1
- package/dist/src/utils/codex-reconciler.d.ts +0 -2
- package/dist/src/utils/codex-reconciler.js.map +1 -1
- package/dist/src/utils/codex-shared-native.d.ts +2 -26
- package/dist/src/utils/codex-shared-native.js +8 -82
- package/dist/src/utils/codex-shared-native.js.map +1 -1
- package/dist/src/utils/git-catalog-provider.d.ts +3 -0
- package/dist/src/utils/git-catalog-provider.js +40 -1
- package/dist/src/utils/git-catalog-provider.js.map +1 -1
- package/dist/src/utils/local-marketplace.d.ts +1 -0
- package/dist/src/utils/local-marketplace.js +69 -73
- package/dist/src/utils/local-marketplace.js.map +1 -1
- package/dist/src/utils/multi-source-catalog.js +1 -0
- package/dist/src/utils/multi-source-catalog.js.map +1 -1
- package/dist/src/utils/native-cli.d.ts +9 -4
- package/dist/src/utils/native-cli.js +280 -34
- package/dist/src/utils/native-cli.js.map +1 -1
- package/dist/src/utils/path-catalog-provider.js +2 -0
- package/dist/src/utils/path-catalog-provider.js.map +1 -1
- package/dist/src/utils/plugin-config-impact.d.ts +33 -0
- package/dist/src/utils/plugin-config-impact.js +298 -0
- package/dist/src/utils/plugin-config-impact.js.map +1 -0
- package/dist/src/utils/plugin-config-write.d.ts +36 -14
- package/dist/src/utils/plugin-config-write.js +709 -225
- package/dist/src/utils/plugin-config-write.js.map +1 -1
- package/dist/src/utils/plugin-config.d.ts +58 -4
- package/dist/src/utils/plugin-config.js +328 -60
- package/dist/src/utils/plugin-config.js.map +1 -1
- package/dist/src/utils/plugin-scripts.d.ts +20 -12
- package/dist/src/utils/plugin-scripts.js +389 -114
- package/dist/src/utils/plugin-scripts.js.map +1 -1
- package/dist/src/utils/process-tree.d.ts +2 -2
- package/dist/src/utils/process-tree.js +1 -1
- package/dist/src/utils/process-tree.js.map +1 -1
- package/dist/src/utils/project-boundary.d.ts +11 -1
- package/dist/src/utils/project-boundary.js +33 -11
- package/dist/src/utils/project-boundary.js.map +1 -1
- package/dist/src/utils/safe-operation-diagnostic.d.ts +15 -0
- package/dist/src/utils/safe-operation-diagnostic.js +33 -0
- package/dist/src/utils/safe-operation-diagnostic.js.map +1 -0
- package/dist/src/utils/sensitive-content.d.ts +4 -0
- package/dist/src/utils/sensitive-content.js +36 -0
- package/dist/src/utils/sensitive-content.js.map +1 -0
- package/dist/src/utils/windows-claude-shim.d.ts +9 -0
- package/dist/src/utils/windows-claude-shim.js +141 -0
- package/dist/src/utils/windows-claude-shim.js.map +1 -0
- package/dist/vitest.config.js +91 -10
- package/dist/vitest.config.js.map +1 -1
- package/package.json +9 -9
- package/dist/authoring/templates/aw-config-runtime.d.mts +0 -2
- package/dist/authoring/templates/aw-config-runtime.mjs +0 -407
- 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,
|
|
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 {
|
|
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 {
|
|
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
|
|
32
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
176
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
]
|
|
187
|
-
const
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
201
|
-
|
|
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 (
|
|
209
|
-
throw new CodexActivationPostcheckError(
|
|
311
|
+
if (failedPluginIds.length > 0) {
|
|
312
|
+
throw new CodexActivationPostcheckError(failedPluginIds, failureDetails.join(' '));
|
|
210
313
|
}
|
|
211
314
|
}
|
|
212
315
|
finally {
|
|
213
|
-
await
|
|
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
|
-
|
|
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
|
|
947
|
+
const requestBatches = resolveAwDeliveredArtifactRequestBatches({
|
|
844
948
|
manifest: options.plugin.manifest,
|
|
845
949
|
platform: options.platform,
|
|
846
950
|
scope: options.scope,
|
|
847
951
|
});
|
|
848
|
-
if (
|
|
849
|
-
return { artifacts: [], sourceDirs: () => [] };
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
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 =
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
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) =>
|
|
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
|
|
1759
|
+
function isAbsentPath(error) {
|
|
1640
1760
|
const code = error.code;
|
|
1641
1761
|
return code === 'ENOENT';
|
|
1642
1762
|
}
|
|
1643
|
-
async function
|
|
1763
|
+
async function lstatIfPresent(filePath) {
|
|
1644
1764
|
return fs.lstat(filePath).catch((error) => {
|
|
1645
|
-
if (
|
|
1765
|
+
if (isAbsentPath(error))
|
|
1646
1766
|
return null;
|
|
1647
1767
|
throw error;
|
|
1648
1768
|
});
|
|
1649
1769
|
}
|
|
1650
|
-
async function
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
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
|
|
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
|
-
|
|
1762
|
-
|
|
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
|
|
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: '
|
|
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: '
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
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
|
|
1982
|
-
*
|
|
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
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
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
|
-
|
|
2148
|
-
|
|
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
|
-
|
|
2128
|
+
sourceProjection = 'drifted';
|
|
2152
2129
|
}
|
|
2153
2130
|
}
|
|
2154
|
-
const sharedNative =
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
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.
|
|
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
|
-
|
|
2909
|
-
|
|
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
|
|
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
|
|
3166
|
-
|
|
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
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
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
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
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
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
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
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
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
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
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
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
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
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
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
|
|
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
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
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
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
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
|
|
3982
|
-
|
|
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
|
-
|
|
3989
|
-
|
|
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
|
|
4021
|
-
|
|
4022
|
-
|
|
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
|
-
|
|
4026
|
-
|
|
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 =
|
|
4030
|
-
&&
|
|
4031
|
-
&&
|
|
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
|
-
|
|
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
|
-
&&
|
|
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 =
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
operationActions.
|
|
4126
|
-
kind: '
|
|
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
|
-
|
|
4131
|
-
|
|
4132
|
-
marketplaceRoot: input.marketplaceRoot ?? resolveAwHome(options.context.env),
|
|
4255
|
+
script: preApply.path,
|
|
4256
|
+
scriptDigest,
|
|
4133
4257
|
});
|
|
4134
4258
|
}
|
|
4135
|
-
|
|
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: '
|
|
4261
|
+
kind: 'ensureNativeRegistration',
|
|
4142
4262
|
pluginId: input.pluginId ?? pluginActionId(input.plugin),
|
|
4143
4263
|
platform: input.platform,
|
|
4144
4264
|
scope: input.scope,
|
|
4145
|
-
|
|
4146
|
-
|
|
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
|
-
|
|
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
|
|
4217
|
-
|
|
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
|
-
|
|
4221
|
-
|
|
4350
|
+
nativeCliRunner: options.context.nativeCliRunner,
|
|
4351
|
+
manager: options.context.codexManager,
|
|
4352
|
+
scope: target.identity.scope,
|
|
4353
|
+
observationSession: options.context.codexObservationSession,
|
|
4222
4354
|
});
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
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
|
-
|
|
4370
|
-
|
|
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
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
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 {
|