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