@lesliechan721/aw 0.1.0-beta.8 → 0.1.0-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authoring/scripts/add-skill.mjs +4 -4
- package/dist/authoring/scripts/add-skill.mjs.map +1 -1
- package/dist/authoring/scripts/init-plugin.mjs +4 -4
- package/dist/authoring/scripts/init-plugin.mjs.map +1 -1
- package/dist/authoring/scripts/lib/plugin-version-check.mjs +1 -1
- package/dist/authoring/scripts/lib/skill-scaffold.d.mts +77 -90
- package/dist/authoring/scripts/lib/skill-scaffold.mjs +99 -120
- package/dist/authoring/scripts/lib/skill-scaffold.mjs.map +1 -1
- package/dist/authoring/scripts/remove-plugin.mjs +1 -1
- package/dist/authoring/scripts/remove-plugin.mjs.map +1 -1
- package/dist/authoring/scripts/validate-plugin.mjs +34 -778
- package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
- package/dist/scripts/aw-skill-eval-materializer.d.ts +24 -0
- package/dist/scripts/{plugin-eval-materializer.js → aw-skill-eval-materializer.js} +34 -145
- package/dist/scripts/aw-skill-eval-materializer.js.map +1 -0
- package/dist/scripts/bundle-plugins.d.ts +1 -0
- package/dist/scripts/bundle-plugins.js +2 -1
- package/dist/scripts/bundle-plugins.js.map +1 -1
- package/dist/scripts/plugin-staging.d.ts +5 -6
- package/dist/scripts/plugin-staging.js +12 -13
- package/dist/scripts/plugin-staging.js.map +1 -1
- package/dist/src/authoring/workspace-commands.d.ts +2 -1
- package/dist/src/authoring/workspace-commands.js.map +1 -1
- package/dist/src/commands/aw-capabilities.d.ts +2 -2
- package/dist/src/commands/aw-capabilities.js +6 -11
- package/dist/src/commands/aw-capabilities.js.map +1 -1
- package/dist/src/commands/catalog-dev-source.d.ts +18 -12
- package/dist/src/commands/catalog-dev-source.js +241 -305
- package/dist/src/commands/catalog-dev-source.js.map +1 -1
- package/dist/src/commands/catalog-generation.d.ts +6 -19
- package/dist/src/commands/catalog-generation.js +10 -11
- package/dist/src/commands/catalog-generation.js.map +1 -1
- package/dist/src/commands/catalog-publication-inspection.d.ts +28 -27
- package/dist/src/commands/catalog-publication-inspection.js +139 -50
- package/dist/src/commands/catalog-publication-inspection.js.map +1 -1
- package/dist/src/commands/catalog-publication-journal.d.ts +49 -0
- package/dist/src/commands/catalog-publication-journal.js +147 -0
- package/dist/src/commands/catalog-publication-journal.js.map +1 -0
- package/dist/src/commands/catalog-publication-target.d.ts +2 -1
- package/dist/src/commands/catalog-publication-target.js +37 -2
- package/dist/src/commands/catalog-publication-target.js.map +1 -1
- package/dist/src/commands/catalog-publish.d.ts +51 -15
- package/dist/src/commands/catalog-publish.js +576 -219
- package/dist/src/commands/catalog-publish.js.map +1 -1
- package/dist/src/commands/catalog-source.d.ts +3 -28
- package/dist/src/commands/catalog-source.js +29 -140
- package/dist/src/commands/catalog-source.js.map +1 -1
- package/dist/src/commands/catalog-status.d.ts +6 -26
- package/dist/src/commands/catalog-status.js +8 -146
- package/dist/src/commands/catalog-status.js.map +1 -1
- package/dist/src/commands/catalog-workspace-resolution.d.ts +13 -0
- package/dist/src/commands/catalog-workspace-resolution.js +42 -0
- package/dist/src/commands/catalog-workspace-resolution.js.map +1 -0
- package/dist/src/commands/dependency-operation-plan.d.ts +88 -9
- package/dist/src/commands/dependency-operation-plan.js +331 -6
- package/dist/src/commands/dependency-operation-plan.js.map +1 -1
- package/dist/src/commands/init.d.ts +5 -11
- package/dist/src/commands/init.js +30 -51
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/offline-cleanup-evidence.d.ts +20 -0
- package/dist/src/commands/offline-cleanup-evidence.js +87 -9
- package/dist/src/commands/offline-cleanup-evidence.js.map +1 -1
- package/dist/src/commands/options.d.ts +6 -1
- package/dist/src/commands/options.js +64 -6
- package/dist/src/commands/options.js.map +1 -1
- package/dist/src/commands/plugin-capabilities.js +4 -0
- package/dist/src/commands/plugin-capabilities.js.map +1 -1
- package/dist/src/commands/rebuild-command.d.ts +10 -8
- package/dist/src/commands/rebuild-command.js +1217 -426
- package/dist/src/commands/rebuild-command.js.map +1 -1
- package/dist/src/commands/rebuild-integration.d.ts +54 -6
- package/dist/src/commands/rebuild-integration.js +691 -364
- package/dist/src/commands/rebuild-integration.js.map +1 -1
- package/dist/src/commands/rebuild-operations.d.ts +2 -0
- package/dist/src/commands/rebuild-operations.js +31 -17
- package/dist/src/commands/rebuild-operations.js.map +1 -1
- package/dist/src/commands/skill-eval.d.ts +33 -0
- package/dist/src/commands/skill-eval.js +363 -0
- package/dist/src/commands/skill-eval.js.map +1 -0
- package/dist/src/commands/{status-v5.d.ts → status-v6.d.ts} +59 -26
- package/dist/src/commands/{status-v5.js → status-v6.js} +882 -79
- package/dist/src/commands/status-v6.js.map +1 -0
- package/dist/src/commands/sync-command.d.ts +21 -0
- package/dist/src/commands/sync-command.js +206 -0
- package/dist/src/commands/sync-command.js.map +1 -0
- package/dist/src/commands/wizard.d.ts +2 -2
- package/dist/src/commands/write-commands.js +1 -43
- package/dist/src/commands/write-commands.js.map +1 -1
- package/dist/src/constants.d.ts +5 -4
- package/dist/src/constants.js +5 -4
- package/dist/src/constants.js.map +1 -1
- package/dist/src/eval/manifest-v1.d.ts +66 -0
- package/dist/src/eval/manifest-v1.js +326 -0
- package/dist/src/eval/manifest-v1.js.map +1 -0
- package/dist/src/generators/local-plugin.d.ts +3 -0
- package/dist/src/generators/local-plugin.js +123 -55
- package/dist/src/generators/local-plugin.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +412 -152
- package/dist/src/index.js.map +1 -1
- package/dist/src/output/command-receipt.d.ts +58 -8
- package/dist/src/output/command-receipt.js +141 -11
- package/dist/src/output/command-receipt.js.map +1 -1
- package/dist/src/output/model.d.ts +3 -11
- package/dist/src/portable/commands.d.ts +2 -2
- package/dist/src/portable/commands.js +2 -8
- package/dist/src/portable/commands.js.map +1 -1
- package/dist/src/portable/materializer.d.ts +1 -4
- package/dist/src/portable/materializer.js +0 -2
- package/dist/src/portable/materializer.js.map +1 -1
- package/dist/src/tui/catalog-generation.d.ts +20 -41
- package/dist/src/tui/catalog-generation.js +65 -224
- package/dist/src/tui/catalog-generation.js.map +1 -1
- package/dist/src/tui/components.js +1 -2
- package/dist/src/tui/components.js.map +1 -1
- package/dist/src/tui/console-app-v4.js +461 -330
- package/dist/src/tui/console-app-v4.js.map +1 -1
- package/dist/src/tui/controller-runtime.d.ts +3 -5
- package/dist/src/tui/controller-runtime.js +182 -187
- package/dist/src/tui/controller-runtime.js.map +1 -1
- package/dist/src/tui/controller.d.ts +42 -45
- package/dist/src/tui/controller.js +454 -407
- package/dist/src/tui/controller.js.map +1 -1
- package/dist/src/tui/diagnostics.d.ts +1 -2
- package/dist/src/tui/diagnostics.js.map +1 -1
- package/dist/src/tui/index.d.ts +1 -1
- package/dist/src/tui/index.js +1 -1
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/tui/model.d.ts +81 -13
- package/dist/src/tui/model.js +378 -95
- package/dist/src/tui/model.js.map +1 -1
- package/dist/src/tui/router.d.ts +9 -0
- package/dist/src/tui/router.js.map +1 -1
- package/dist/src/types.d.ts +54 -47
- package/dist/src/types.js.map +1 -1
- package/dist/src/utils/agent-version.d.ts +4 -1
- package/dist/src/utils/agent-version.js +28 -3
- package/dist/src/utils/agent-version.js.map +1 -1
- package/dist/src/utils/catalog-digests.d.ts +7 -0
- package/dist/src/utils/catalog-digests.js +27 -0
- package/dist/src/utils/catalog-digests.js.map +1 -1
- package/dist/src/utils/catalog-generation-control-snapshot.d.ts +11 -0
- package/dist/src/utils/catalog-generation-control-snapshot.js +45 -0
- package/dist/src/utils/catalog-generation-control-snapshot.js.map +1 -0
- package/dist/src/utils/catalog-generation-error.d.ts +2 -1
- package/dist/src/utils/catalog-generation-error.js +12 -0
- package/dist/src/utils/catalog-generation-error.js.map +1 -1
- package/dist/src/utils/catalog-generation-resolver.d.ts +10 -3
- package/dist/src/utils/catalog-generation-resolver.js +90 -76
- package/dist/src/utils/catalog-generation-resolver.js.map +1 -1
- package/dist/src/utils/catalog-generation-service.d.ts +24 -33
- package/dist/src/utils/catalog-generation-service.js +253 -567
- package/dist/src/utils/catalog-generation-service.js.map +1 -1
- package/dist/src/utils/catalog-generation-state.d.ts +4 -0
- package/dist/src/utils/catalog-generation-state.js +73 -106
- package/dist/src/utils/catalog-generation-state.js.map +1 -1
- package/dist/src/utils/catalog-generation-store.d.ts +16 -26
- package/dist/src/utils/catalog-generation-store.js +154 -290
- package/dist/src/utils/catalog-generation-store.js.map +1 -1
- package/dist/src/utils/catalog-manifest-v6.d.ts +5 -0
- package/dist/src/utils/{catalog-manifest-v5.js → catalog-manifest-v6.js} +23 -35
- package/dist/src/utils/catalog-manifest-v6.js.map +1 -0
- package/dist/src/utils/catalog-operation-context.d.ts +6 -14
- package/dist/src/utils/catalog-operation-context.js +68 -169
- package/dist/src/utils/catalog-operation-context.js.map +1 -1
- package/dist/src/utils/catalog-public-error.js +1 -4
- package/dist/src/utils/catalog-public-error.js.map +1 -1
- package/dist/src/utils/catalog-runtime-projection.d.ts +3 -0
- package/dist/src/utils/catalog-runtime-projection.js +5 -40
- package/dist/src/utils/catalog-runtime-projection.js.map +1 -1
- package/dist/src/utils/catalog-snapshot.d.ts +2 -2
- package/dist/src/utils/catalog-source-references.d.ts +7 -0
- package/dist/src/utils/catalog-source-references.js +44 -55
- package/dist/src/utils/catalog-source-references.js.map +1 -1
- package/dist/src/utils/catalog-sources.js +25 -9
- package/dist/src/utils/catalog-sources.js.map +1 -1
- package/dist/src/utils/codex-config.d.ts +2 -0
- package/dist/src/utils/codex-config.js +22 -1
- package/dist/src/utils/codex-config.js.map +1 -1
- package/dist/src/utils/codex-manager.d.ts +50 -2
- package/dist/src/utils/codex-manager.js +160 -6
- package/dist/src/utils/codex-manager.js.map +1 -1
- package/dist/src/utils/codex-reconciler.d.ts +131 -0
- package/dist/src/utils/codex-reconciler.js +327 -0
- package/dist/src/utils/codex-reconciler.js.map +1 -0
- package/dist/src/utils/codex-shared-native.d.ts +56 -0
- package/dist/src/utils/codex-shared-native.js +87 -0
- package/dist/src/utils/codex-shared-native.js.map +1 -0
- package/dist/src/utils/codex-surface-analysis.d.ts +44 -0
- package/dist/src/utils/codex-surface-analysis.js +247 -0
- package/dist/src/utils/codex-surface-analysis.js.map +1 -0
- package/dist/src/utils/git-catalog-provider.js +56 -115
- package/dist/src/utils/git-catalog-provider.js.map +1 -1
- package/dist/src/utils/manifest.d.ts +1 -3
- package/dist/src/utils/manifest.js +1 -3
- package/dist/src/utils/manifest.js.map +1 -1
- package/dist/src/utils/multi-source-catalog.d.ts +1 -10
- package/dist/src/utils/multi-source-catalog.js +8 -175
- package/dist/src/utils/multi-source-catalog.js.map +1 -1
- package/dist/src/utils/native-cli.d.ts +34 -0
- package/dist/src/utils/native-cli.js +91 -0
- package/dist/src/utils/native-cli.js.map +1 -1
- package/dist/src/utils/orphan-marketplace-cleanup.js +1 -0
- package/dist/src/utils/orphan-marketplace-cleanup.js.map +1 -1
- package/dist/src/utils/ownership.d.ts +0 -10
- package/dist/src/utils/ownership.js +3 -88
- package/dist/src/utils/ownership.js.map +1 -1
- package/dist/src/utils/path-catalog-provider.d.ts +1 -1
- package/dist/src/utils/path-catalog-provider.js +45 -62
- package/dist/src/utils/path-catalog-provider.js.map +1 -1
- package/dist/src/utils/paths.d.ts +0 -1
- package/dist/src/utils/paths.js +0 -1
- package/dist/src/utils/paths.js.map +1 -1
- package/dist/src/utils/plugin-config-write.js +0 -5
- package/dist/src/utils/plugin-config-write.js.map +1 -1
- package/dist/src/utils/plugin-intent-state.d.ts +39 -0
- package/dist/src/utils/plugin-intent-state.js +325 -0
- package/dist/src/utils/plugin-intent-state.js.map +1 -0
- package/dist/src/utils/target-eligibility.d.ts +3 -2
- package/dist/src/utils/target-eligibility.js +4 -3
- package/dist/src/utils/target-eligibility.js.map +1 -1
- package/package.json +5 -4
- package/dist/scripts/aw-skill-eval-engine.d.ts +0 -13
- package/dist/scripts/aw-skill-eval-engine.js +0 -31
- package/dist/scripts/aw-skill-eval-engine.js.map +0 -1
- package/dist/scripts/codex-eval-app-server.d.ts +0 -91
- package/dist/scripts/codex-eval-app-server.js +0 -1334
- package/dist/scripts/codex-eval-app-server.js.map +0 -1
- package/dist/scripts/codex-eval-isolation.d.ts +0 -61
- package/dist/scripts/codex-eval-isolation.js +0 -318
- package/dist/scripts/codex-eval-isolation.js.map +0 -1
- package/dist/scripts/codex-eval-process-management.d.ts +0 -8
- package/dist/scripts/codex-eval-process-management.js +0 -69
- package/dist/scripts/codex-eval-process-management.js.map +0 -1
- package/dist/scripts/codex-eval-process-tree.d.ts +0 -38
- package/dist/scripts/codex-eval-process-tree.js +0 -803
- package/dist/scripts/codex-eval-process-tree.js.map +0 -1
- package/dist/scripts/codex-eval-runtime-config.d.ts +0 -62
- package/dist/scripts/codex-eval-runtime-config.js +0 -843
- package/dist/scripts/codex-eval-runtime-config.js.map +0 -1
- package/dist/scripts/codex-trigger-eval.d.ts +0 -84
- package/dist/scripts/codex-trigger-eval.js +0 -968
- package/dist/scripts/codex-trigger-eval.js.map +0 -1
- package/dist/scripts/eval-plugin-skills-audited.d.ts +0 -6
- package/dist/scripts/eval-plugin-skills-audited.js +0 -261
- package/dist/scripts/eval-plugin-skills-audited.js.map +0 -1
- package/dist/scripts/eval-plugin-skills.d.ts +0 -45
- package/dist/scripts/eval-plugin-skills.js +0 -1589
- package/dist/scripts/eval-plugin-skills.js.map +0 -1
- package/dist/scripts/eval-run-layout.d.ts +0 -17
- package/dist/scripts/eval-run-layout.js +0 -26
- package/dist/scripts/eval-run-layout.js.map +0 -1
- package/dist/scripts/plugin-eval-contract.d.ts +0 -189
- package/dist/scripts/plugin-eval-contract.js +0 -745
- package/dist/scripts/plugin-eval-contract.js.map +0 -1
- package/dist/scripts/plugin-eval-materializer.d.ts +0 -13
- package/dist/scripts/plugin-eval-materializer.js.map +0 -1
- package/dist/scripts/skill-reporting.d.ts +0 -212
- package/dist/scripts/skill-reporting.js +0 -922
- package/dist/scripts/skill-reporting.js.map +0 -1
- package/dist/src/commands/catalog-context.d.ts +0 -52
- package/dist/src/commands/catalog-context.js +0 -156
- package/dist/src/commands/catalog-context.js.map +0 -1
- package/dist/src/commands/status-v5.js.map +0 -1
- package/dist/src/utils/catalog-manifest-v5.d.ts +0 -5
- package/dist/src/utils/catalog-manifest-v5.js.map +0 -1
- package/dist/src/utils/catalog-source-cache.d.ts +0 -62
- package/dist/src/utils/catalog-source-cache.js +0 -321
- package/dist/src/utils/catalog-source-cache.js.map +0 -1
- package/dist/src/utils/codex-project-surface.d.ts +0 -3
- package/dist/src/utils/codex-project-surface.js +0 -49
- package/dist/src/utils/codex-project-surface.js.map +0 -1
- package/dist/src/utils/plugin-release.d.ts +0 -2
- package/dist/src/utils/plugin-release.js +0 -2
- package/dist/src/utils/plugin-release.js.map +0 -1
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
|
+
import os from 'node:os';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import fs from 'fs-extra';
|
|
4
5
|
import { generateAgentArtifacts } from '../generators/agents.js';
|
|
5
|
-
import { extractCodexHookGroups } from '../generators/hooks.js';
|
|
6
6
|
import { generateInstructionArtifacts, renderCodexManagedInstructionBlock, } from '../generators/instructions.js';
|
|
7
7
|
import { generateCodexPermissionArtifacts, mergeClaudePermissions, } from '../generators/permissions.js';
|
|
8
8
|
import { buildGeneratedName } from '../generators/shared.js';
|
|
9
|
-
import { applyConditionalCodexConfigTransactionFile, applyCodexConfigTransactionFile, composeManagedDeveloperInstructionsBlock, readCodexConfigFile,
|
|
9
|
+
import { applyConditionalCodexConfigTransactionFile, applyCodexConfigTransactionFile, composeManagedDeveloperInstructionsBlock, readCodexConfigFile, } from '../utils/codex-config.js';
|
|
10
|
+
import { canonicalizePathThroughExistingAncestor } from '../utils/state-paths.js';
|
|
10
11
|
import { createCodexSkillExpectedTree, createCodexSkillStagingTreeContentDigest, } from '../utils/codex-skill-staging.js';
|
|
11
12
|
import { createDynamicSkillExpectedTree, createDynamicSkillTreeContentDigest, skillDirectoryMatchesExpectedTree, validateSkillDirectory, writeDynamicSkillTree, } from '../utils/dynamic-skill-tree.js';
|
|
12
|
-
import { parseCodexSharedHooksFile,
|
|
13
|
-
import { buildCodexMarketplaceAddInvocation, buildCodexMarketplaceListInvocation, buildCodexMarketplaceRemoveInvocation, buildCodexPluginAddInvocation, buildCodexPluginListInvocation, buildCodexPluginRemoveInvocation, runNativeCliChecked, } from '../utils/native-cli.js';
|
|
14
|
-
import { OWNED_TEXT_HEADER_PREFIX, createSharedOwnerToken,
|
|
13
|
+
import { parseCodexSharedHooksFile, serializeTrackedHookGroupIdentity, } from '../utils/hooks-contract.js';
|
|
14
|
+
import { buildCodexMarketplaceAddInvocation, buildCodexMarketplaceListInvocation, buildCodexMarketplaceRemoveInvocation, buildCodexPluginAddInvocation, buildCodexPluginListInvocation, buildCodexProjectPluginListInvocations, buildCodexPluginRemoveInvocation, postcheckCodexProjectPluginActivation, runNativeCliChecked, } from '../utils/native-cli.js';
|
|
15
|
+
import { OWNED_TEXT_HEADER_PREFIX, createSharedOwnerToken, parseSharedOwnerToken, parseOwnedTextHeader, readSkillOwnerMarker, inspectOwnedComponents, writeOwnedTextFile, } from '../utils/ownership.js';
|
|
15
16
|
import { resolveArtifactRenderConfig, runArtifactRenderer } from '../utils/artifact-renderer.js';
|
|
16
17
|
import { validateArtifactPlan } from '../utils/artifact-plan.js';
|
|
17
18
|
import { createAwPluginRequirementsReceipt, readPluginManifest, } from '../utils/manifest.js';
|
|
@@ -23,11 +24,13 @@ import { readCatalogSourcesConfigSnapshot } from '../utils/catalog-sources.js';
|
|
|
23
24
|
import { getNamespacedMarketplaceName, projectMarketplaceManifestForPlatform, replayNamespacedCodexSkillOverlays, } from '../utils/local-marketplace.js';
|
|
24
25
|
import { cleanupOrphanNamespacedMarketplaceRoot, commitMarketplaceGenerationBackup, } from '../utils/orphan-marketplace-cleanup.js';
|
|
25
26
|
import { readClaudeSettings, writeClaudeSettings } from '../adapters/helpers.js';
|
|
26
|
-
import {
|
|
27
|
+
import { analyzeCodexSurface } from '../utils/codex-surface-analysis.js';
|
|
28
|
+
import { inspectCodexSharedNative, } from '../utils/codex-shared-native.js';
|
|
29
|
+
import { calculateCodexSharedNativePayloadDigest } from '../utils/catalog-digests.js';
|
|
27
30
|
import { assertCanonicalProjectManagedRoots } from '../utils/project-boundary.js';
|
|
28
31
|
import { findCodexRoleNameConflict, resolveCodexRoleNamespace, } from '../utils/codex-roles.js';
|
|
29
32
|
const MARKETPLACE_NAME = 'aw';
|
|
30
|
-
const
|
|
33
|
+
const LEGACY_MCP_OWNER_ENV = 'AW_AGENT_WORKFLOWS_OWNER';
|
|
31
34
|
function requirementsReceipt(plugin) {
|
|
32
35
|
const receipt = createAwPluginRequirementsReceipt({ name: plugin.manifest.name, version: plugin.manifest.version }, plugin.manifest.requires?.aw.capabilities ?? []);
|
|
33
36
|
return Buffer.from(`${JSON.stringify(receipt, null, 2)}\n`, 'utf8');
|
|
@@ -38,6 +41,112 @@ function requireCodexManager(manager) {
|
|
|
38
41
|
}
|
|
39
42
|
return manager;
|
|
40
43
|
}
|
|
44
|
+
export async function executeCodexReconcileActivations(options) {
|
|
45
|
+
const byScope = {
|
|
46
|
+
user: { upserts: {}, removals: new Set() },
|
|
47
|
+
project: { upserts: {}, removals: new Set() },
|
|
48
|
+
};
|
|
49
|
+
for (const plan of options.plans) {
|
|
50
|
+
for (const scope of ['user', 'project']) {
|
|
51
|
+
for (const action of plan.activationActions[scope]) {
|
|
52
|
+
if (action.action === 'enable' || action.action === 'disable') {
|
|
53
|
+
byScope[scope].upserts[action.codexPluginId] = action.desired;
|
|
54
|
+
byScope[scope].removals.delete(action.codexPluginId);
|
|
55
|
+
}
|
|
56
|
+
else if (action.action === 'remove') {
|
|
57
|
+
byScope[scope].removals.add(action.codexPluginId);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
for (const removal of plan.staleActivationRemovals) {
|
|
62
|
+
if (!Object.hasOwn(byScope[removal.scope].upserts, removal.codexPluginId)) {
|
|
63
|
+
byScope[removal.scope].removals.add(removal.codexPluginId);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
for (const scope of ['user', 'project']) {
|
|
68
|
+
const enables = Object.fromEntries(Object.entries(byScope[scope].upserts)
|
|
69
|
+
.filter(([, desired]) => desired));
|
|
70
|
+
if (Object.keys(enables).length === 0)
|
|
71
|
+
continue;
|
|
72
|
+
const configPath = resolveCodexPaths(options.cwd, scope, options.env).configTomlPath;
|
|
73
|
+
await applyCodexConfigTransactionFile(configPath, { upsertPluginActivations: enables });
|
|
74
|
+
const enabledConfig = await readCodexConfigFile(configPath);
|
|
75
|
+
for (const pluginId of Object.keys(enables)) {
|
|
76
|
+
if (enabledConfig.snapshot.plugins[pluginId]?.enabled !== true) {
|
|
77
|
+
throw new Error(`codexActivationPostcheckFailed: ${pluginId} ${scope} activation was not enabled before obsolete activation removal.`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for (const scope of ['user', 'project']) {
|
|
82
|
+
const transaction = byScope[scope];
|
|
83
|
+
const disables = Object.fromEntries(Object.entries(transaction.upserts)
|
|
84
|
+
.filter(([, desired]) => !desired));
|
|
85
|
+
if (Object.keys(disables).length === 0 && transaction.removals.size === 0)
|
|
86
|
+
continue;
|
|
87
|
+
await applyCodexConfigTransactionFile(resolveCodexPaths(options.cwd, scope, options.env).configTomlPath, {
|
|
88
|
+
...(transaction.removals.size > 0
|
|
89
|
+
? { removePluginActivations: [...transaction.removals].sort() }
|
|
90
|
+
: {}),
|
|
91
|
+
...(Object.keys(disables).length > 0
|
|
92
|
+
? { upsertPluginActivations: disables }
|
|
93
|
+
: {}),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
const [userConfig, projectConfig] = await Promise.all([
|
|
97
|
+
readCodexConfigFile(resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath),
|
|
98
|
+
readCodexConfigFile(resolveCodexPaths(options.cwd, 'project', options.env).configTomlPath),
|
|
99
|
+
]);
|
|
100
|
+
for (const plan of options.plans) {
|
|
101
|
+
for (const scope of ['user', 'project']) {
|
|
102
|
+
const snapshot = scope === 'user' ? userConfig.snapshot : projectConfig.snapshot;
|
|
103
|
+
for (const action of plan.activationActions[scope]) {
|
|
104
|
+
const actual = snapshot.plugins[action.codexPluginId]?.enabled;
|
|
105
|
+
const expected = action.desired
|
|
106
|
+
? true
|
|
107
|
+
: action.action === 'disable' || actual === false ? false : undefined;
|
|
108
|
+
if (actual !== expected) {
|
|
109
|
+
throw new Error(`codexActivationPostcheckFailed: ${action.codexPluginId} ${scope} activation is ${String(actual)}; expected ${String(expected)}.`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
for (const removal of plan.staleActivationRemovals.filter((entry) => entry.scope === scope)) {
|
|
113
|
+
if (snapshot.plugins[removal.codexPluginId]?.enabled !== undefined) {
|
|
114
|
+
throw new Error(`codexActivationPostcheckFailed: stale ${removal.codexPluginId} ${scope} activation remains.`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const observedProjectChecks = options.plans.flatMap((plan) => plan.postcheck.support === 'observed'
|
|
120
|
+
? plan.postcheck.plugins.filter((plugin) => plugin.neutralCwdRequired)
|
|
121
|
+
: []);
|
|
122
|
+
if (observedProjectChecks.length === 0)
|
|
123
|
+
return;
|
|
124
|
+
const manager = requireCodexManager(options.manager);
|
|
125
|
+
const neutralCwd = await fs.mkdtemp(path.join(os.tmpdir(), 'aw-codex-postcheck-'));
|
|
126
|
+
try {
|
|
127
|
+
const invocations = buildCodexProjectPluginListInvocations(manager, {
|
|
128
|
+
exactCwd: options.cwd,
|
|
129
|
+
neutralCwd,
|
|
130
|
+
env: options.env,
|
|
131
|
+
});
|
|
132
|
+
const [exactResult, neutralResult] = await Promise.all([
|
|
133
|
+
runNativeCliChecked(invocations.exact, options.runner),
|
|
134
|
+
runNativeCliChecked(invocations.neutral, options.runner),
|
|
135
|
+
]);
|
|
136
|
+
for (const plugin of observedProjectChecks) {
|
|
137
|
+
postcheckCodexProjectPluginActivation({
|
|
138
|
+
pluginId: plugin.codexPluginId,
|
|
139
|
+
userEnabled: plugin.expectedUserEnabled,
|
|
140
|
+
projectEnabled: plugin.expectedProjectEnabled,
|
|
141
|
+
exactResult,
|
|
142
|
+
neutralResult,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
await fs.remove(neutralCwd);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
41
150
|
const CODEX_REMOVE_RECOVERY = 'Codex residue was retained. Retry cleanup or uninstall, upgrade AW, or install a compatible official Codex CLI.';
|
|
42
151
|
function ownerIdentity(plugin, artifactKey, platform, scope) {
|
|
43
152
|
return { owner: 'agent-workflows', plugin, artifactKey, platform, scope };
|
|
@@ -202,84 +311,85 @@ function createCodexInstructionComponent(options) {
|
|
|
202
311
|
},
|
|
203
312
|
};
|
|
204
313
|
}
|
|
205
|
-
function
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
314
|
+
function createCodexActivationComponent(options) {
|
|
315
|
+
return {
|
|
316
|
+
kind: 'shared-config',
|
|
317
|
+
identity: options.identity,
|
|
318
|
+
label: `${options.configPath}#plugins.${options.codexPluginId}.enabled`,
|
|
319
|
+
inspect: async () => {
|
|
320
|
+
const plugin = (await readCodexConfigFile(options.configPath))
|
|
321
|
+
.snapshot.plugins[options.codexPluginId];
|
|
322
|
+
return plugin?.enabled === options.desired ? 'present' : 'absent';
|
|
323
|
+
},
|
|
324
|
+
remove: async () => {
|
|
325
|
+
await applyCodexConfigTransactionFile(options.configPath, {
|
|
326
|
+
removePluginActivations: [options.codexPluginId],
|
|
327
|
+
});
|
|
328
|
+
},
|
|
329
|
+
};
|
|
213
330
|
}
|
|
214
|
-
function
|
|
215
|
-
const ownerKey = createSharedOwnerToken(options.identity);
|
|
331
|
+
function createCodexActivationCleanupComponent(options) {
|
|
216
332
|
return {
|
|
217
333
|
kind: 'shared-config',
|
|
218
334
|
identity: options.identity,
|
|
219
|
-
label: `${options.configPath}#
|
|
335
|
+
label: `${options.configPath}#plugins.${options.codexPluginId}.enabled`,
|
|
220
336
|
inspect: async () => {
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
337
|
+
const actual = (await readCodexConfigFile(options.configPath))
|
|
338
|
+
.snapshot.plugins[options.codexPluginId]?.enabled;
|
|
339
|
+
return options.disposition === 'remove'
|
|
340
|
+
? actual === undefined ? 'absent' : 'present'
|
|
341
|
+
: actual === true ? 'present' : 'absent';
|
|
226
342
|
},
|
|
227
343
|
remove: async () => {
|
|
228
344
|
await applyConditionalCodexConfigTransactionFile(options.configPath, (snapshot) => {
|
|
229
|
-
const actual = snapshot.
|
|
230
|
-
|
|
231
|
-
|
|
345
|
+
const actual = snapshot.plugins[options.codexPluginId]?.enabled;
|
|
346
|
+
if (options.disposition === 'remove') {
|
|
347
|
+
return actual === undefined
|
|
348
|
+
? null
|
|
349
|
+
: { removePluginActivations: [options.codexPluginId] };
|
|
350
|
+
}
|
|
351
|
+
return actual === true
|
|
352
|
+
? { upsertPluginActivations: { [options.codexPluginId]: false } }
|
|
232
353
|
: null;
|
|
233
354
|
});
|
|
234
355
|
},
|
|
235
356
|
};
|
|
236
357
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
try {
|
|
246
|
-
return Boolean(parseOwnedTextHeader(raw, options.fileIdentity));
|
|
247
|
-
}
|
|
248
|
-
catch {
|
|
249
|
-
return false;
|
|
250
|
-
}
|
|
358
|
+
function legacyCodexMcpOwner(actual) {
|
|
359
|
+
const envValue = actual?.value?.env;
|
|
360
|
+
return envValue
|
|
361
|
+
&& typeof envValue === 'object'
|
|
362
|
+
&& !Array.isArray(envValue)
|
|
363
|
+
&& typeof envValue[LEGACY_MCP_OWNER_ENV] === 'string'
|
|
364
|
+
? envValue[LEGACY_MCP_OWNER_ENV]
|
|
365
|
+
: null;
|
|
251
366
|
}
|
|
252
|
-
function
|
|
253
|
-
const
|
|
254
|
-
const config = await readCodexConfigFile(options.configPath);
|
|
255
|
-
const registration = config.snapshot.agents[options.roleKey];
|
|
256
|
-
if (!registration)
|
|
257
|
-
return 'absent';
|
|
258
|
-
return await codexRoleRegistrationOwned({ ...options, registration })
|
|
259
|
-
? 'present'
|
|
260
|
-
: 'foreign';
|
|
261
|
-
};
|
|
367
|
+
function createLegacyCodexMcpCleanupComponent(options) {
|
|
368
|
+
const ownerKey = createSharedOwnerToken(options.identity);
|
|
262
369
|
return {
|
|
263
370
|
kind: 'shared-config',
|
|
264
371
|
identity: options.identity,
|
|
265
|
-
label: `${options.configPath}#
|
|
266
|
-
inspect
|
|
372
|
+
label: `${options.configPath}#mcp_servers.${options.serverKey}`,
|
|
373
|
+
inspect: async () => {
|
|
374
|
+
const actual = (await readCodexConfigFile(options.configPath))
|
|
375
|
+
.snapshot.mcpServers[options.serverKey];
|
|
376
|
+
if (!actual)
|
|
377
|
+
return 'absent';
|
|
378
|
+
return legacyCodexMcpOwner(actual) === ownerKey ? 'present' : 'foreign';
|
|
379
|
+
},
|
|
267
380
|
remove: async () => {
|
|
268
|
-
await applyConditionalCodexConfigTransactionFile(options.configPath,
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
? { removeAgentRegistrations: [options.roleKey] }
|
|
272
|
-
: null;
|
|
273
|
-
});
|
|
381
|
+
await applyConditionalCodexConfigTransactionFile(options.configPath, (snapshot) => legacyCodexMcpOwner(snapshot.mcpServers[options.serverKey]) === ownerKey
|
|
382
|
+
? { removeMcpServers: [options.serverKey] }
|
|
383
|
+
: null);
|
|
274
384
|
},
|
|
275
385
|
};
|
|
276
386
|
}
|
|
277
|
-
async function
|
|
387
|
+
async function readLegacyCodexHooks(hooksPath) {
|
|
278
388
|
if (!await fs.pathExists(hooksPath))
|
|
279
389
|
return parseCodexSharedHooksFile({ hooks: {} });
|
|
280
390
|
return parseCodexSharedHooksFile(await fs.readJson(hooksPath));
|
|
281
391
|
}
|
|
282
|
-
function
|
|
392
|
+
function renderLegacyCodexHookGroups(groups) {
|
|
283
393
|
const hooks = {};
|
|
284
394
|
for (const group of groups) {
|
|
285
395
|
hooks[group.event] ??= [];
|
|
@@ -287,16 +397,15 @@ function renderCodexHookGroups(groups) {
|
|
|
287
397
|
}
|
|
288
398
|
return `${JSON.stringify({ hooks }, null, 2)}\n`;
|
|
289
399
|
}
|
|
290
|
-
|
|
400
|
+
async function applyLegacyCodexHookCleanupTransactionFile(hooksPath, update) {
|
|
291
401
|
return withConfigFileMutation({
|
|
292
402
|
filePath: hooksPath,
|
|
293
403
|
allowedRoot: path.dirname(hooksPath),
|
|
294
404
|
action: async () => {
|
|
295
|
-
const parsed = await
|
|
405
|
+
const parsed = await readLegacyCodexHooks(hooksPath);
|
|
296
406
|
const groups = update(parsed);
|
|
297
407
|
if (!groups)
|
|
298
408
|
return false;
|
|
299
|
-
await testHooks?.beforeCommit?.();
|
|
300
409
|
if (groups.length === 0) {
|
|
301
410
|
await atomicRemoveConfigFile({
|
|
302
411
|
filePath: hooksPath,
|
|
@@ -307,40 +416,27 @@ export async function applyCodexHookGroupsTransactionFile(hooksPath, update, tes
|
|
|
307
416
|
await atomicReplaceConfigFile({
|
|
308
417
|
filePath: hooksPath,
|
|
309
418
|
allowedRoot: path.dirname(hooksPath),
|
|
310
|
-
content:
|
|
419
|
+
content: renderLegacyCodexHookGroups(groups),
|
|
311
420
|
});
|
|
312
421
|
}
|
|
313
422
|
return true;
|
|
314
423
|
},
|
|
315
424
|
});
|
|
316
425
|
}
|
|
317
|
-
function
|
|
318
|
-
const rendered = renderTrackedHookGroupIdentity(identity);
|
|
319
|
-
const rawGroup = {
|
|
320
|
-
...(rendered.matcher === undefined ? {} : { matcher: rendered.matcher }),
|
|
321
|
-
hooks: rendered.hooks,
|
|
322
|
-
};
|
|
323
|
-
return {
|
|
324
|
-
event: rendered.event,
|
|
325
|
-
matcher: rendered.matcher,
|
|
326
|
-
hooks: rendered.hooks,
|
|
327
|
-
rawGroup,
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
function createCodexHookComponent(options) {
|
|
426
|
+
function createLegacyCodexHookCleanupComponent(options) {
|
|
331
427
|
const serialized = serializeTrackedHookGroupIdentity(options.identity);
|
|
332
428
|
return {
|
|
333
429
|
kind: 'shared-config',
|
|
334
430
|
identity: ownerIdentity(options.pluginName, internalArtifactKey('hook', serialized), 'codex', options.scope),
|
|
335
431
|
label: `${options.hooksPath}#${serialized}`,
|
|
336
432
|
inspect: async () => {
|
|
337
|
-
const parsed = await
|
|
433
|
+
const parsed = await readLegacyCodexHooks(options.hooksPath);
|
|
338
434
|
return parsed.managedIdentities.some((entry) => serializeTrackedHookGroupIdentity(entry) === serialized)
|
|
339
435
|
? 'present'
|
|
340
436
|
: 'absent';
|
|
341
437
|
},
|
|
342
438
|
remove: async () => {
|
|
343
|
-
await
|
|
439
|
+
await applyLegacyCodexHookCleanupTransactionFile(options.hooksPath, (parsed) => {
|
|
344
440
|
let removed = false;
|
|
345
441
|
const remaining = parsed.groups.flatMap((entry) => {
|
|
346
442
|
if (!removed
|
|
@@ -356,19 +452,44 @@ function createCodexHookComponent(options) {
|
|
|
356
452
|
},
|
|
357
453
|
};
|
|
358
454
|
}
|
|
359
|
-
function
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
455
|
+
async function codexRoleRegistrationOwned(options) {
|
|
456
|
+
if (!options.registration?.configFile)
|
|
457
|
+
return false;
|
|
458
|
+
const expected = path.normalize(path.relative(path.dirname(options.configPath), options.rolePath));
|
|
459
|
+
const actual = path.normalize(path.relative(path.dirname(options.configPath), path.resolve(path.dirname(options.configPath), options.registration.configFile)));
|
|
460
|
+
if (actual !== expected)
|
|
461
|
+
return false;
|
|
462
|
+
const raw = await fs.readFile(options.rolePath, 'utf8').catch(() => '');
|
|
463
|
+
try {
|
|
464
|
+
return Boolean(parseOwnedTextHeader(raw, options.fileIdentity));
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function createCodexRoleRegistrationComponent(options) {
|
|
471
|
+
const inspect = async () => {
|
|
472
|
+
const config = await readCodexConfigFile(options.configPath);
|
|
473
|
+
const registration = config.snapshot.agents[options.roleKey];
|
|
474
|
+
if (!registration)
|
|
475
|
+
return 'absent';
|
|
476
|
+
return await codexRoleRegistrationOwned({ ...options, registration })
|
|
477
|
+
? 'present'
|
|
478
|
+
: 'foreign';
|
|
368
479
|
};
|
|
369
480
|
return {
|
|
370
|
-
|
|
371
|
-
|
|
481
|
+
kind: 'shared-config',
|
|
482
|
+
identity: options.identity,
|
|
483
|
+
label: `${options.configPath}#agents.${options.roleKey}`,
|
|
484
|
+
inspect,
|
|
485
|
+
remove: async () => {
|
|
486
|
+
await applyConditionalCodexConfigTransactionFile(options.configPath, async (snapshot) => {
|
|
487
|
+
const registration = snapshot.agents[options.roleKey];
|
|
488
|
+
return await codexRoleRegistrationOwned({ ...options, registration })
|
|
489
|
+
? { removeAgentRegistrations: [options.roleKey] }
|
|
490
|
+
: null;
|
|
491
|
+
});
|
|
492
|
+
},
|
|
372
493
|
};
|
|
373
494
|
}
|
|
374
495
|
function sameOwnerIdentity(left, right) {
|
|
@@ -381,17 +502,6 @@ function sameOwnerIdentity(left, right) {
|
|
|
381
502
|
&& left.platform === right.platform
|
|
382
503
|
&& left.scope === right.scope;
|
|
383
504
|
}
|
|
384
|
-
function readOwnedHookIdentity(identity, scope) {
|
|
385
|
-
try {
|
|
386
|
-
const parsed = parseOwnedTextHeader(identity.command);
|
|
387
|
-
return parsed?.identity.platform === 'codex' && parsed.identity.scope === scope
|
|
388
|
-
? parsed.identity
|
|
389
|
-
: null;
|
|
390
|
-
}
|
|
391
|
-
catch {
|
|
392
|
-
return null;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
505
|
function sharedOwnerPluginName(value) {
|
|
396
506
|
return parseSharedOwnerToken(value)?.plugin ?? value;
|
|
397
507
|
}
|
|
@@ -415,7 +525,6 @@ async function scanCodexSharedComponents(options) {
|
|
|
415
525
|
remove: async () => undefined,
|
|
416
526
|
});
|
|
417
527
|
const containsOwnedEvidence = (raw) => raw.includes(options.pluginName) && (raw.includes('[[aw-managed:start plugin=')
|
|
418
|
-
|| raw.includes(MCP_OWNER_ENV)
|
|
419
528
|
|| raw.includes(OWNED_TEXT_HEADER_PREFIX)
|
|
420
529
|
|| raw.includes('agent-workflows'));
|
|
421
530
|
if (options.scanConfig) {
|
|
@@ -437,24 +546,6 @@ async function scanCodexSharedComponents(options) {
|
|
|
437
546
|
configPath: options.paths.configTomlPath,
|
|
438
547
|
}));
|
|
439
548
|
}
|
|
440
|
-
for (const [serverKey, server] of Object.entries(config?.snapshot.mcpServers ?? {})) {
|
|
441
|
-
const envValue = server.value?.env;
|
|
442
|
-
if (envValue
|
|
443
|
-
&& typeof envValue === 'object'
|
|
444
|
-
&& !Array.isArray(envValue)
|
|
445
|
-
&& parseSharedOwnerToken(String(envValue[MCP_OWNER_ENV]))?.pluginId === options.pluginId) {
|
|
446
|
-
const identity = expectedIdentity(internalArtifactKey('mcp', serverKey));
|
|
447
|
-
if (createSharedOwnerToken(identity) !== envValue[MCP_OWNER_ENV])
|
|
448
|
-
continue;
|
|
449
|
-
output.push(createCodexMcpComponent({
|
|
450
|
-
identity,
|
|
451
|
-
scope: options.scope,
|
|
452
|
-
configPath: options.paths.configTomlPath,
|
|
453
|
-
serverKey,
|
|
454
|
-
owned: true,
|
|
455
|
-
}));
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
549
|
for (const [roleKey, registration] of Object.entries(config?.snapshot.agents ?? {})) {
|
|
459
550
|
if (!registration.configFile)
|
|
460
551
|
continue;
|
|
@@ -479,28 +570,6 @@ async function scanCodexSharedComponents(options) {
|
|
|
479
570
|
}));
|
|
480
571
|
}
|
|
481
572
|
}
|
|
482
|
-
if (options.scope === 'project' && options.scanHooks) {
|
|
483
|
-
const hooks = await readCodexHooks(options.paths.hooksPath).catch(async (error) => {
|
|
484
|
-
if (!options.ignoreReadErrors)
|
|
485
|
-
throw error;
|
|
486
|
-
const raw = await fs.readFile(options.paths.hooksPath, 'utf8').catch(() => '');
|
|
487
|
-
if (containsOwnedEvidence(raw)) {
|
|
488
|
-
output.push(corruptedComponent(options.paths.hooksPath, error));
|
|
489
|
-
}
|
|
490
|
-
return null;
|
|
491
|
-
});
|
|
492
|
-
for (const identity of hooks?.managedIdentities ?? []) {
|
|
493
|
-
const parsedIdentity = readOwnedHookIdentity(identity, options.scope);
|
|
494
|
-
if (!parsedIdentity || !sameOwnerIdentity(parsedIdentity, expectedIdentity(parsedIdentity.artifactKey)))
|
|
495
|
-
continue;
|
|
496
|
-
output.push(createCodexHookComponent({
|
|
497
|
-
pluginName: options.pluginName,
|
|
498
|
-
scope: options.scope,
|
|
499
|
-
hooksPath: options.paths.hooksPath,
|
|
500
|
-
identity,
|
|
501
|
-
}));
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
573
|
return output;
|
|
505
574
|
}
|
|
506
575
|
async function scanClaudeSharedComponents(options) {
|
|
@@ -615,14 +684,19 @@ async function preflightCodexWriteSafety(options) {
|
|
|
615
684
|
const pluginName = options.plugin.manifest.name;
|
|
616
685
|
const userConfigPath = resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath;
|
|
617
686
|
const projectConfigPath = resolveCodexPaths(options.cwd, 'project', options.env).configTomlPath;
|
|
687
|
+
const nativeSurface = analyzeCodexSurface(options.plugin.manifest).nativeKinds.length > 0;
|
|
688
|
+
const needsUserConfig = options.scope === 'user'
|
|
689
|
+
|| nativeSurface;
|
|
618
690
|
let userConfig;
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
691
|
+
if (needsUserConfig) {
|
|
692
|
+
try {
|
|
693
|
+
userConfig = await readCodexConfigFile(userConfigPath);
|
|
694
|
+
}
|
|
695
|
+
catch (error) {
|
|
696
|
+
throw new Error(`codexProjectUserConfigUnreadable: 无法解析用户级 Codex 配置 ${userConfigPath}:${error instanceof Error ? error.message : String(error)}`);
|
|
697
|
+
}
|
|
624
698
|
}
|
|
625
|
-
if (options.scope === 'project') {
|
|
699
|
+
if (options.scope === 'project' && userConfig) {
|
|
626
700
|
const expectedPluginId = `${pluginName}@${options.plugin.marketplaceName ?? MARKETPLACE_NAME}`;
|
|
627
701
|
for (const [key, plugin] of Object.entries(userConfig.snapshot.plugins)) {
|
|
628
702
|
if (key !== expectedPluginId
|
|
@@ -637,7 +711,7 @@ async function preflightCodexWriteSafety(options) {
|
|
|
637
711
|
&& target.target === 'codex.agentRole'
|
|
638
712
|
&& target.roleKey)
|
|
639
713
|
.map((target) => target.roleKey));
|
|
640
|
-
const needsProjectConfig = options.scope === 'project' && Boolean(
|
|
714
|
+
const needsProjectConfig = options.scope === 'project' && Boolean(nativeSurface
|
|
641
715
|
|| resolvePlatformEntries(options.plugin.manifest.extensions?.instructions, 'codex').length > 0
|
|
642
716
|
|| (options.plugin.manifest.artifacts?.targets ?? []).some((target) => target.platform === 'codex'
|
|
643
717
|
&& target.scopes.includes('project')
|
|
@@ -654,7 +728,7 @@ async function preflightCodexWriteSafety(options) {
|
|
|
654
728
|
if (roleKeys.size === 0)
|
|
655
729
|
return;
|
|
656
730
|
for (const configPath of options.scope === 'project'
|
|
657
|
-
? [
|
|
731
|
+
? [projectConfigPath]
|
|
658
732
|
: [userConfigPath]) {
|
|
659
733
|
const configScope = configPath === userConfigPath ? 'user' : 'project';
|
|
660
734
|
const ownedRolePaths = [];
|
|
@@ -705,33 +779,6 @@ function renderCodexRole(options) {
|
|
|
705
779
|
'',
|
|
706
780
|
].join('\n');
|
|
707
781
|
}
|
|
708
|
-
async function resolveCodexMcpServers(plugin, scope) {
|
|
709
|
-
if (!plugin.manifest.mcpServers)
|
|
710
|
-
return {};
|
|
711
|
-
const relative = plugin.manifest.mcpServers.replace(/^\.\//u, '');
|
|
712
|
-
const direct = path.resolve(plugin.rootDir, relative);
|
|
713
|
-
const mcpPath = await fs.pathExists(direct)
|
|
714
|
-
? direct
|
|
715
|
-
: path.resolve(plugin.rootDir, 'codex', relative);
|
|
716
|
-
const servers = await readRenderedCodexMcpServersFile(mcpPath);
|
|
717
|
-
return Object.fromEntries(Object.entries(servers).map(([key, server]) => {
|
|
718
|
-
const existingEnv = server.env;
|
|
719
|
-
if (existingEnv !== undefined && (typeof existingEnv !== 'object' || Array.isArray(existingEnv))) {
|
|
720
|
-
throw new Error(`MCP server ${key} 的 env 必须是 object。`);
|
|
721
|
-
}
|
|
722
|
-
const env = (existingEnv ?? {});
|
|
723
|
-
const ownerKey = createSharedOwnerToken(pluginOwnerIdentity(plugin, internalArtifactKey('mcp', key), 'codex', scope));
|
|
724
|
-
if (env[MCP_OWNER_ENV] !== undefined
|
|
725
|
-
&& env[MCP_OWNER_ENV] !== plugin.manifest.name
|
|
726
|
-
&& env[MCP_OWNER_ENV] !== ownerKey) {
|
|
727
|
-
throw new Error(`MCP server ${key} 不能覆盖保留的 ${MCP_OWNER_ENV} owner marker。`);
|
|
728
|
-
}
|
|
729
|
-
return [key, {
|
|
730
|
-
...server,
|
|
731
|
-
env: { ...env, [MCP_OWNER_ENV]: ownerKey },
|
|
732
|
-
}];
|
|
733
|
-
}));
|
|
734
|
-
}
|
|
735
782
|
async function resolveDynamicArtifacts(options) {
|
|
736
783
|
const declarations = (options.plugin.manifest.artifacts?.targets ?? []).filter((target) => target.platform === options.platform && target.scopes.includes(options.scope));
|
|
737
784
|
if (declarations.length === 0)
|
|
@@ -845,9 +892,7 @@ async function buildBundledCodexSkillWrites(options) {
|
|
|
845
892
|
const dynamicKeys = new Set((options.plugin.manifest.artifacts?.targets ?? [])
|
|
846
893
|
.filter((target) => target.target === 'codex.skill' && target.skillKey)
|
|
847
894
|
.map((target) => target.skillKey));
|
|
848
|
-
const targetRoot = options.
|
|
849
|
-
? path.join(options.repoRoot, '.agents', 'skills')
|
|
850
|
-
: path.join(options.marketplaceRoot ?? resolveAwHome(options.env), 'plugins', options.plugin.manifest.name, ...relativeRoot.split('/'));
|
|
895
|
+
const targetRoot = await canonicalizePathThroughExistingAncestor(path.join(options.marketplaceRoot ?? resolveAwHome(options.env), 'plugins', options.plugin.manifest.name, ...relativeRoot.split('/')));
|
|
851
896
|
const writes = [];
|
|
852
897
|
for (const entry of await fs.readdir(sourceRoot, { withFileTypes: true })) {
|
|
853
898
|
if (entry.isSymbolicLink()) {
|
|
@@ -869,9 +914,9 @@ async function buildBundledCodexSkillWrites(options) {
|
|
|
869
914
|
sourceRequirementsReceiptPolicy: 'allow',
|
|
870
915
|
});
|
|
871
916
|
const skillPath = path.join(targetRoot, entry.name);
|
|
872
|
-
const canRecoverPristine =
|
|
873
|
-
|
|
874
|
-
const identity = ownerIdentity(options.plugin.manifest.name, entry.name, 'codex',
|
|
917
|
+
const canRecoverPristine = await canonicalizePathThroughExistingAncestor(skillSource)
|
|
918
|
+
!== path.resolve(skillPath);
|
|
919
|
+
const identity = ownerIdentity(options.plugin.manifest.name, entry.name, 'codex', 'user');
|
|
875
920
|
const component = pathComponent({
|
|
876
921
|
kind: 'skill-directory',
|
|
877
922
|
path: skillPath,
|
|
@@ -971,16 +1016,15 @@ async function buildCodexWrites(options) {
|
|
|
971
1016
|
const dynamicRoles = dynamic.artifacts.filter((entry) => entry.target === 'codex.agentRole');
|
|
972
1017
|
const dynamicSkills = dynamic.artifacts.filter((entry) => entry.target === 'codex.skill');
|
|
973
1018
|
const repoRoot = await resolveRepoRoot(options.cwd);
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
}
|
|
981
|
-
const relativeSkillsRoot = await resolveCodexSkillsRelativeRoot(options.plugin);
|
|
1019
|
+
if (options.scope === 'user') {
|
|
1020
|
+
writes.push(...await buildBundledCodexSkillWrites({
|
|
1021
|
+
plugin: options.plugin,
|
|
1022
|
+
env: options.env,
|
|
1023
|
+
marketplaceRoot: options.marketplaceRoot,
|
|
1024
|
+
}));
|
|
1025
|
+
}
|
|
982
1026
|
const skillRoot = options.scope === 'user'
|
|
983
|
-
? path.join(
|
|
1027
|
+
? path.join(getCodexHome(options.env), 'skills')
|
|
984
1028
|
: path.join(repoRoot, '.agents', 'skills');
|
|
985
1029
|
for (const artifact of dynamicSkills) {
|
|
986
1030
|
const tree = await createCodexSkillExpectedTree({
|
|
@@ -1002,7 +1046,6 @@ async function buildCodexWrites(options) {
|
|
|
1002
1046
|
const staticInstructions = dynamicInstructions.length > 0
|
|
1003
1047
|
? []
|
|
1004
1048
|
: resolvePlatformEntries(options.plugin.manifest.extensions?.instructions, 'codex');
|
|
1005
|
-
const mcpServers = await resolveCodexMcpServers(options.plugin, options.scope);
|
|
1006
1049
|
const hasInstructionOutput = dynamicInstructions.length > 0 || staticInstructions.length > 0;
|
|
1007
1050
|
if (hasInstructionOutput) {
|
|
1008
1051
|
const component = createCodexInstructionComponent({
|
|
@@ -1052,34 +1095,6 @@ async function buildCodexWrites(options) {
|
|
|
1052
1095
|
});
|
|
1053
1096
|
}
|
|
1054
1097
|
}
|
|
1055
|
-
for (const [serverKey, server] of Object.entries(mcpServers)) {
|
|
1056
|
-
const component = createCodexMcpComponent({
|
|
1057
|
-
identity: pluginOwnerIdentity(options.plugin, internalArtifactKey('mcp', serverKey), 'codex', options.scope),
|
|
1058
|
-
scope: options.scope,
|
|
1059
|
-
configPath: paths.configTomlPath,
|
|
1060
|
-
serverKey,
|
|
1061
|
-
owned: false,
|
|
1062
|
-
});
|
|
1063
|
-
writes.push({
|
|
1064
|
-
artifact: {
|
|
1065
|
-
path: paths.configTomlPath,
|
|
1066
|
-
kind: 'mcpServer',
|
|
1067
|
-
platform: 'codex',
|
|
1068
|
-
artifactKey: serverKey,
|
|
1069
|
-
},
|
|
1070
|
-
component,
|
|
1071
|
-
write: async () => {
|
|
1072
|
-
await applyConditionalCodexConfigTransactionFile(paths.configTomlPath, (snapshot) => {
|
|
1073
|
-
const actual = snapshot.mcpServers[serverKey];
|
|
1074
|
-
const actualOwner = codexMcpOwner(actual);
|
|
1075
|
-
if (actual && actualOwner !== createSharedOwnerToken(component.identity)) {
|
|
1076
|
-
throw new Error(`Codex MCP server "${serverKey}" is foreign and cannot be overwritten by plugin "${options.plugin.manifest.name}".`);
|
|
1077
|
-
}
|
|
1078
|
-
return { upsertMcpServers: { [serverKey]: server } };
|
|
1079
|
-
});
|
|
1080
|
-
},
|
|
1081
|
-
});
|
|
1082
|
-
}
|
|
1083
1098
|
for (const artifact of dynamicRoles) {
|
|
1084
1099
|
const roleKey = artifact.roleKey;
|
|
1085
1100
|
const rolePath = path.join(paths.configDir, 'agents', `${buildGeneratedName(options.plugin.manifest.name, roleKey)}.toml`);
|
|
@@ -1179,42 +1194,40 @@ async function buildCodexWrites(options) {
|
|
|
1179
1194
|
kind: 'agent',
|
|
1180
1195
|
}));
|
|
1181
1196
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
});
|
|
1217
|
-
}
|
|
1197
|
+
const codexPluginId = `${options.plugin.manifest.name}@${options.marketplaceName ?? options.plugin.marketplaceName ?? MARKETPLACE_NAME}`;
|
|
1198
|
+
const nativeSurface = analyzeCodexSurface(options.plugin.manifest).nativeKinds.length > 0;
|
|
1199
|
+
const userActivationDesired = options.scope === 'user'
|
|
1200
|
+
|| options.userActivationDesired?.has(codexPluginId) === true;
|
|
1201
|
+
for (const activation of !nativeSurface ? [] : options.scope === 'project'
|
|
1202
|
+
? [
|
|
1203
|
+
{
|
|
1204
|
+
scope: 'user',
|
|
1205
|
+
desired: userActivationDesired,
|
|
1206
|
+
configPath: resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath,
|
|
1207
|
+
},
|
|
1208
|
+
{ scope: 'project', desired: true, configPath: paths.configTomlPath },
|
|
1209
|
+
]
|
|
1210
|
+
: [{ scope: 'user', desired: true, configPath: paths.configTomlPath }]) {
|
|
1211
|
+
const component = createCodexActivationComponent({
|
|
1212
|
+
identity: pluginOwnerIdentity(options.plugin, internalArtifactKey('activation', activation.scope), 'codex', options.scope),
|
|
1213
|
+
configPath: activation.configPath,
|
|
1214
|
+
codexPluginId,
|
|
1215
|
+
desired: activation.desired,
|
|
1216
|
+
});
|
|
1217
|
+
writes.push({
|
|
1218
|
+
artifact: {
|
|
1219
|
+
path: activation.configPath,
|
|
1220
|
+
kind: 'instruction',
|
|
1221
|
+
platform: 'codex',
|
|
1222
|
+
artifactKey: internalArtifactKey('activation', activation.scope),
|
|
1223
|
+
},
|
|
1224
|
+
component,
|
|
1225
|
+
write: async () => {
|
|
1226
|
+
await applyCodexConfigTransactionFile(activation.configPath, {
|
|
1227
|
+
upsertPluginActivations: { [codexPluginId]: activation.desired },
|
|
1228
|
+
});
|
|
1229
|
+
},
|
|
1230
|
+
});
|
|
1218
1231
|
}
|
|
1219
1232
|
return writes;
|
|
1220
1233
|
}
|
|
@@ -1278,13 +1291,13 @@ async function buildInspectionWrites(options) {
|
|
|
1278
1291
|
}
|
|
1279
1292
|
const paths = resolveCodexPaths(options.cwd, options.scope, options.env);
|
|
1280
1293
|
const repoRoot = await resolveRepoRoot(options.cwd);
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
}
|
|
1294
|
+
if (options.scope === 'user') {
|
|
1295
|
+
writes.push(...await buildBundledCodexSkillWrites({
|
|
1296
|
+
plugin: options.plugin,
|
|
1297
|
+
env: options.env,
|
|
1298
|
+
marketplaceRoot: options.marketplaceRoot,
|
|
1299
|
+
}));
|
|
1300
|
+
}
|
|
1288
1301
|
const instructions = resolvePlatformEntries(options.plugin.manifest.extensions?.instructions, 'codex');
|
|
1289
1302
|
const targets = (options.plugin.manifest.artifacts?.targets ?? []).filter((target) => target.platform === 'codex' && target.scopes.includes(options.scope));
|
|
1290
1303
|
const instructionTargets = targets.filter((target) => target.target === 'codex.developerInstructionsBlock');
|
|
@@ -1304,28 +1317,8 @@ async function buildInspectionWrites(options) {
|
|
|
1304
1317
|
write: async () => undefined,
|
|
1305
1318
|
});
|
|
1306
1319
|
}
|
|
1307
|
-
const mcpServers = await resolveCodexMcpServers(options.plugin, options.scope);
|
|
1308
|
-
for (const serverKey of Object.keys(mcpServers)) {
|
|
1309
|
-
writes.push({
|
|
1310
|
-
artifact: {
|
|
1311
|
-
path: paths.configTomlPath,
|
|
1312
|
-
kind: 'mcpServer',
|
|
1313
|
-
platform: 'codex',
|
|
1314
|
-
artifactKey: serverKey,
|
|
1315
|
-
},
|
|
1316
|
-
component: createCodexMcpComponent({
|
|
1317
|
-
identity: pluginOwnerIdentity(options.plugin, internalArtifactKey('mcp', serverKey), 'codex', options.scope),
|
|
1318
|
-
scope: options.scope,
|
|
1319
|
-
configPath: paths.configTomlPath,
|
|
1320
|
-
serverKey,
|
|
1321
|
-
owned: false,
|
|
1322
|
-
}),
|
|
1323
|
-
write: async () => undefined,
|
|
1324
|
-
});
|
|
1325
|
-
}
|
|
1326
|
-
const relativeSkillsRoot = await resolveCodexSkillsRelativeRoot(options.plugin);
|
|
1327
1320
|
const dynamicSkillRoot = options.scope === 'user'
|
|
1328
|
-
? path.join(
|
|
1321
|
+
? path.join(getCodexHome(options.env), 'skills')
|
|
1329
1322
|
: path.join(repoRoot, '.agents', 'skills');
|
|
1330
1323
|
for (const target of targets) {
|
|
1331
1324
|
const state = target.states.includes('present') ? 'present' : 'disabled';
|
|
@@ -1410,32 +1403,35 @@ async function buildInspectionWrites(options) {
|
|
|
1410
1403
|
kind: 'agent',
|
|
1411
1404
|
}));
|
|
1412
1405
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
scope:
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1406
|
+
const nativeSurface = analyzeCodexSurface(options.plugin.manifest).nativeKinds.length > 0;
|
|
1407
|
+
const codexPluginId = `${options.plugin.manifest.name}@${options.marketplaceName ?? options.plugin.marketplaceName ?? MARKETPLACE_NAME}`;
|
|
1408
|
+
const userActivationDesired = options.scope === 'user'
|
|
1409
|
+
|| options.userActivationDesired?.has(codexPluginId) === true;
|
|
1410
|
+
for (const activation of !nativeSurface ? [] : options.scope === 'project'
|
|
1411
|
+
? [
|
|
1412
|
+
{
|
|
1413
|
+
scope: 'user',
|
|
1414
|
+
desired: userActivationDesired,
|
|
1415
|
+
configPath: resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath,
|
|
1416
|
+
},
|
|
1417
|
+
{ scope: 'project', desired: true, configPath: paths.configTomlPath },
|
|
1418
|
+
]
|
|
1419
|
+
: [{ scope: 'user', desired: true, configPath: paths.configTomlPath }]) {
|
|
1420
|
+
writes.push({
|
|
1421
|
+
artifact: {
|
|
1422
|
+
path: activation.configPath,
|
|
1423
|
+
kind: 'instruction',
|
|
1424
|
+
platform: 'codex',
|
|
1425
|
+
artifactKey: internalArtifactKey('activation', activation.scope),
|
|
1426
|
+
},
|
|
1427
|
+
component: createCodexActivationComponent({
|
|
1428
|
+
identity: pluginOwnerIdentity(options.plugin, internalArtifactKey('activation', activation.scope), 'codex', options.scope),
|
|
1429
|
+
configPath: activation.configPath,
|
|
1430
|
+
codexPluginId,
|
|
1431
|
+
desired: activation.desired,
|
|
1432
|
+
}),
|
|
1433
|
+
write: async () => undefined,
|
|
1434
|
+
});
|
|
1439
1435
|
}
|
|
1440
1436
|
return writes;
|
|
1441
1437
|
}
|
|
@@ -1457,24 +1453,35 @@ async function buildHistoricalComponents(options) {
|
|
|
1457
1453
|
continue;
|
|
1458
1454
|
if (artifact.state === 'disabled')
|
|
1459
1455
|
continue;
|
|
1460
|
-
if (
|
|
1456
|
+
if (options.platform === 'codex'
|
|
1457
|
+
&& artifact.artifactKey !== undefined
|
|
1458
|
+
&& new Set([
|
|
1459
|
+
internalArtifactKey('activation', 'user'),
|
|
1460
|
+
internalArtifactKey('activation', 'project'),
|
|
1461
|
+
]).has(artifact.artifactKey)) {
|
|
1462
|
+
continue;
|
|
1463
|
+
}
|
|
1464
|
+
if (options.command === 'cleanup'
|
|
1465
|
+
&& artifact.kind === 'hook'
|
|
1466
|
+
&& options.platform === 'codex') {
|
|
1461
1467
|
for (const identity of artifact.hookGroups ?? []) {
|
|
1462
|
-
components.push(
|
|
1468
|
+
components.push(createLegacyCodexHookCleanupComponent({
|
|
1463
1469
|
pluginName: options.pluginName,
|
|
1464
1470
|
scope: options.scope,
|
|
1465
|
-
hooksPath: resolveCodexPaths(options.cwd, options.scope, options.env).
|
|
1471
|
+
hooksPath: path.join(resolveCodexPaths(options.cwd, options.scope, options.env).configDir, 'hooks.json'),
|
|
1466
1472
|
identity,
|
|
1467
1473
|
}));
|
|
1468
1474
|
}
|
|
1469
1475
|
continue;
|
|
1470
1476
|
}
|
|
1471
|
-
if (
|
|
1472
|
-
|
|
1477
|
+
if (options.command === 'cleanup'
|
|
1478
|
+
&& artifact.kind === 'mcpServer'
|
|
1479
|
+
&& options.platform === 'codex'
|
|
1480
|
+
&& artifact.artifactKey) {
|
|
1481
|
+
components.push(createLegacyCodexMcpCleanupComponent({
|
|
1473
1482
|
identity: historicalIdentity(internalArtifactKey('mcp', artifact.artifactKey)),
|
|
1474
|
-
scope: options.scope,
|
|
1475
1483
|
configPath: resolveCodexPaths(options.cwd, options.scope, options.env).configTomlPath,
|
|
1476
1484
|
serverKey: artifact.artifactKey,
|
|
1477
|
-
owned: false,
|
|
1478
1485
|
}));
|
|
1479
1486
|
continue;
|
|
1480
1487
|
}
|
|
@@ -1538,7 +1545,7 @@ async function readdirCodexResidueIfPresent(directory) {
|
|
|
1538
1545
|
}
|
|
1539
1546
|
return fs.readdir(directory, { withFileTypes: true });
|
|
1540
1547
|
}
|
|
1541
|
-
async function inspectCodexNativeResidue(options) {
|
|
1548
|
+
export async function inspectCodexNativeResidue(options) {
|
|
1542
1549
|
const marketplaceName = options.marketplaceName ?? MARKETPLACE_NAME;
|
|
1543
1550
|
const marketplaceRoot = options.marketplaceRoot ?? resolveAwHome(options.env);
|
|
1544
1551
|
const pluginId = `${options.pluginName}@${marketplaceName}`;
|
|
@@ -1626,6 +1633,186 @@ async function inspectCodexNativeResidue(options) {
|
|
|
1626
1633
|
installedVersions: [], cacheVersions: [], enabled: false,
|
|
1627
1634
|
};
|
|
1628
1635
|
}
|
|
1636
|
+
function isPathWithin(root, candidate) {
|
|
1637
|
+
const relative = path.relative(path.resolve(root), path.resolve(candidate));
|
|
1638
|
+
return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
|
|
1639
|
+
}
|
|
1640
|
+
async function codexPayloadPathSafe(payloadRoot) {
|
|
1641
|
+
const rootStats = await lstatCodexResidueIfPresent(payloadRoot);
|
|
1642
|
+
if (!rootStats?.isDirectory() || rootStats.isSymbolicLink())
|
|
1643
|
+
return false;
|
|
1644
|
+
const pending = [payloadRoot];
|
|
1645
|
+
while (pending.length > 0) {
|
|
1646
|
+
const current = pending.pop();
|
|
1647
|
+
for (const entry of await fs.readdir(current, { withFileTypes: true })) {
|
|
1648
|
+
const entryPath = path.join(current, entry.name);
|
|
1649
|
+
if (entry.isSymbolicLink()) {
|
|
1650
|
+
const target = path.resolve(path.dirname(entryPath), await fs.readlink(entryPath));
|
|
1651
|
+
if (!isPathWithin(payloadRoot, target))
|
|
1652
|
+
return false;
|
|
1653
|
+
}
|
|
1654
|
+
else if (entry.isDirectory()) {
|
|
1655
|
+
pending.push(entryPath);
|
|
1656
|
+
}
|
|
1657
|
+
else if (!entry.isFile()) {
|
|
1658
|
+
return false;
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
return true;
|
|
1663
|
+
}
|
|
1664
|
+
/**
|
|
1665
|
+
* Read-only filesystem projection for the payload-aware shared-native classifier.
|
|
1666
|
+
* It does not invoke Codex or create native/config state.
|
|
1667
|
+
*/
|
|
1668
|
+
export async function inspectCodexSharedNativeLive(options) {
|
|
1669
|
+
const marketplaceName = options.marketplaceName ?? MARKETPLACE_NAME;
|
|
1670
|
+
const marketplaceRoot = options.marketplaceRoot ?? resolveAwHome(options.env);
|
|
1671
|
+
const pluginId = `${options.pluginName}@${marketplaceName}`;
|
|
1672
|
+
const config = await readCodexConfigFile(resolveCodexPaths(options.cwd, 'user', options.env).configTomlPath);
|
|
1673
|
+
const cacheRoot = path.join(getCodexHome(options.env), 'plugins', 'cache', marketplaceName, options.pluginName);
|
|
1674
|
+
const installedManifestPath = path.join(getCodexHome(options.env), 'plugins', pluginId, 'manifest.json');
|
|
1675
|
+
const cacheRootStats = await lstatCodexResidueIfPresent(cacheRoot);
|
|
1676
|
+
let unsafeCachePath = Boolean(cacheRootStats
|
|
1677
|
+
&& (!cacheRootStats.isDirectory() || cacheRootStats.isSymbolicLink()));
|
|
1678
|
+
const candidates = [];
|
|
1679
|
+
if (cacheRootStats?.isDirectory() && !cacheRootStats.isSymbolicLink()) {
|
|
1680
|
+
for (const entry of await fs.readdir(cacheRoot, { withFileTypes: true })) {
|
|
1681
|
+
const payloadPath = path.join(cacheRoot, entry.name);
|
|
1682
|
+
if (!entry.isDirectory() || entry.isSymbolicLink()) {
|
|
1683
|
+
unsafeCachePath = true;
|
|
1684
|
+
continue;
|
|
1685
|
+
}
|
|
1686
|
+
const manifestPaths = [
|
|
1687
|
+
path.join(payloadPath, 'plugin.json'),
|
|
1688
|
+
path.join(payloadPath, '.codex-plugin', 'plugin.json'),
|
|
1689
|
+
];
|
|
1690
|
+
const existingManifestPaths = [];
|
|
1691
|
+
for (const manifestPath of manifestPaths) {
|
|
1692
|
+
const stats = await lstatCodexResidueIfPresent(manifestPath);
|
|
1693
|
+
if (stats)
|
|
1694
|
+
existingManifestPaths.push(manifestPath);
|
|
1695
|
+
}
|
|
1696
|
+
if (existingManifestPaths.length !== 1) {
|
|
1697
|
+
candidates.push({
|
|
1698
|
+
version: entry.name,
|
|
1699
|
+
manifest: existingManifestPaths.length === 0 ? { state: 'absent' } : { state: 'unreadable' },
|
|
1700
|
+
payloadPath,
|
|
1701
|
+
});
|
|
1702
|
+
continue;
|
|
1703
|
+
}
|
|
1704
|
+
const manifestPath = existingManifestPaths[0];
|
|
1705
|
+
const manifestStats = await lstatCodexResidueIfPresent(manifestPath);
|
|
1706
|
+
if (!manifestStats?.isFile() || manifestStats.isSymbolicLink()) {
|
|
1707
|
+
candidates.push({ version: entry.name, manifest: { state: 'unreadable' }, payloadPath });
|
|
1708
|
+
continue;
|
|
1709
|
+
}
|
|
1710
|
+
let manifest;
|
|
1711
|
+
try {
|
|
1712
|
+
const value = await fs.readJson(manifestPath);
|
|
1713
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
1714
|
+
throw new Error('invalid manifest');
|
|
1715
|
+
manifest = value;
|
|
1716
|
+
}
|
|
1717
|
+
catch {
|
|
1718
|
+
candidates.push({ version: entry.name, manifest: { state: 'unreadable' }, payloadPath });
|
|
1719
|
+
continue;
|
|
1720
|
+
}
|
|
1721
|
+
const pathSafe = await codexPayloadPathSafe(payloadPath);
|
|
1722
|
+
let payloadDigest = options.expectedPayloadDigest;
|
|
1723
|
+
if (pathSafe) {
|
|
1724
|
+
try {
|
|
1725
|
+
payloadDigest = await calculateCodexSharedNativePayloadDigest(payloadPath);
|
|
1726
|
+
}
|
|
1727
|
+
catch {
|
|
1728
|
+
candidates.push({ version: entry.name, manifest: { state: 'unreadable' }, payloadPath });
|
|
1729
|
+
continue;
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
candidates.push({
|
|
1733
|
+
version: typeof manifest.version === 'string' ? manifest.version : entry.name,
|
|
1734
|
+
payloadPath,
|
|
1735
|
+
manifest: {
|
|
1736
|
+
state: 'present',
|
|
1737
|
+
identity: typeof manifest.id === 'string'
|
|
1738
|
+
? manifest.id
|
|
1739
|
+
: typeof manifest.name === 'string' ? manifest.name : '',
|
|
1740
|
+
version: typeof manifest.version === 'string' ? manifest.version : '',
|
|
1741
|
+
payloadDigest,
|
|
1742
|
+
payloadPath,
|
|
1743
|
+
pathSafe,
|
|
1744
|
+
},
|
|
1745
|
+
});
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
let installedVersion;
|
|
1749
|
+
const installedStats = await lstatCodexResidueIfPresent(installedManifestPath);
|
|
1750
|
+
if (installedStats?.isFile() && !installedStats.isSymbolicLink()) {
|
|
1751
|
+
try {
|
|
1752
|
+
const installed = await fs.readJson(installedManifestPath);
|
|
1753
|
+
if (typeof installed.version === 'string')
|
|
1754
|
+
installedVersion = installed.version;
|
|
1755
|
+
}
|
|
1756
|
+
catch {
|
|
1757
|
+
installedVersion = undefined;
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
const selected = candidates.find((candidate) => candidate.version === installedVersion)
|
|
1761
|
+
?? candidates.find((candidate) => candidate.version === options.desiredVersion)
|
|
1762
|
+
?? (candidates.length === 1 ? candidates[0] : undefined);
|
|
1763
|
+
const pluginListEntries = candidates.map((candidate) => ({
|
|
1764
|
+
pluginId,
|
|
1765
|
+
enabled: config.snapshot.plugins[pluginId]?.enabled === true,
|
|
1766
|
+
...(candidate.version ? { version: candidate.version } : {}),
|
|
1767
|
+
path: candidate.payloadPath,
|
|
1768
|
+
}));
|
|
1769
|
+
if (installedStats && pluginListEntries.length === 0) {
|
|
1770
|
+
pluginListEntries.push({
|
|
1771
|
+
pluginId,
|
|
1772
|
+
enabled: config.snapshot.plugins[pluginId]?.enabled === true,
|
|
1773
|
+
...(installedVersion ? { version: installedVersion } : {}),
|
|
1774
|
+
path: path.dirname(installedManifestPath),
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
if (unsafeCachePath && pluginListEntries.length === 0) {
|
|
1778
|
+
pluginListEntries.push({
|
|
1779
|
+
pluginId,
|
|
1780
|
+
enabled: config.snapshot.plugins[pluginId]?.enabled === true,
|
|
1781
|
+
version: options.desiredVersion,
|
|
1782
|
+
path: cacheRoot,
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
const manifest = unsafeCachePath
|
|
1786
|
+
? {
|
|
1787
|
+
state: 'present',
|
|
1788
|
+
identity: pluginId,
|
|
1789
|
+
version: options.desiredVersion,
|
|
1790
|
+
payloadDigest: options.expectedPayloadDigest,
|
|
1791
|
+
payloadPath: cacheRoot,
|
|
1792
|
+
pathSafe: false,
|
|
1793
|
+
}
|
|
1794
|
+
: selected?.manifest ?? { state: 'absent' };
|
|
1795
|
+
const sharedNative = inspectCodexSharedNative({
|
|
1796
|
+
pluginName: options.pluginName,
|
|
1797
|
+
pluginId,
|
|
1798
|
+
marketplaceName,
|
|
1799
|
+
canonicalMarketplaceRoot: marketplaceRoot,
|
|
1800
|
+
desiredVersion: options.desiredVersion,
|
|
1801
|
+
expectedPayloadDigest: options.expectedPayloadDigest,
|
|
1802
|
+
marketplaceRegistration: config.snapshot.marketplaces[marketplaceName],
|
|
1803
|
+
marketplacePathSafe: true,
|
|
1804
|
+
pluginListEntries,
|
|
1805
|
+
manifest,
|
|
1806
|
+
managedByCurrentIntent: options.managedByCurrentIntent && manifest.state === 'present',
|
|
1807
|
+
});
|
|
1808
|
+
return {
|
|
1809
|
+
sharedNative,
|
|
1810
|
+
...(manifest.state === 'present' ? {
|
|
1811
|
+
actualPayloadDigest: manifest.payloadDigest,
|
|
1812
|
+
payloadPath: manifest.payloadPath,
|
|
1813
|
+
} : {}),
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1629
1816
|
export async function codexNativeRemovalRequired(options) {
|
|
1630
1817
|
const residue = await inspectCodexNativeResidue(options);
|
|
1631
1818
|
if (residue.state === 'present')
|
|
@@ -2457,6 +2644,8 @@ function createNativeDriver(options) {
|
|
|
2457
2644
|
const codexManager = options.context.codexManager;
|
|
2458
2645
|
const mirrorTransactions = new WeakMap();
|
|
2459
2646
|
const finalizeNamespacedCleanup = async (target) => {
|
|
2647
|
+
if (target.identity.platform === 'codex')
|
|
2648
|
+
return;
|
|
2460
2649
|
const { sourceId, sourceIdentity, marketplaceRoot, marketplaceName } = target.identity;
|
|
2461
2650
|
if (!sourceId || !sourceIdentity || !marketplaceRoot)
|
|
2462
2651
|
return;
|
|
@@ -2530,7 +2719,7 @@ function createNativeDriver(options) {
|
|
|
2530
2719
|
});
|
|
2531
2720
|
return state.pluginState;
|
|
2532
2721
|
}
|
|
2533
|
-
if (target.identity.platform === 'codex'
|
|
2722
|
+
if (target.identity.platform === 'codex') {
|
|
2534
2723
|
const residue = await inspectCodexNativeResidue({
|
|
2535
2724
|
pluginName: target.identity.pluginName,
|
|
2536
2725
|
cwd: target.identity.cwd,
|
|
@@ -2562,7 +2751,7 @@ function createNativeDriver(options) {
|
|
|
2562
2751
|
return state.pluginState;
|
|
2563
2752
|
return state.pluginEnabled ? 'present' : 'partial';
|
|
2564
2753
|
}
|
|
2565
|
-
if (target.identity.platform === 'codex'
|
|
2754
|
+
if (target.identity.platform === 'codex') {
|
|
2566
2755
|
const residue = await inspectCodexNativeResidue({
|
|
2567
2756
|
pluginName: target.identity.pluginName,
|
|
2568
2757
|
cwd: target.identity.cwd,
|
|
@@ -2572,7 +2761,34 @@ function createNativeDriver(options) {
|
|
|
2572
2761
|
});
|
|
2573
2762
|
if (residue.state !== 'present')
|
|
2574
2763
|
return residue.state;
|
|
2575
|
-
|
|
2764
|
+
if (target.identity.scope !== 'project' || !codexManager)
|
|
2765
|
+
return 'present';
|
|
2766
|
+
const userConfig = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath);
|
|
2767
|
+
const projectConfig = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'project', options.context.env).configTomlPath);
|
|
2768
|
+
const pluginId = `${target.identity.pluginName}@${target.identity.marketplaceName ?? MARKETPLACE_NAME}`;
|
|
2769
|
+
const neutralCwd = await fs.mkdtemp(path.join(os.tmpdir(), 'aw-codex-postcheck-'));
|
|
2770
|
+
try {
|
|
2771
|
+
const invocations = buildCodexProjectPluginListInvocations(codexManager, {
|
|
2772
|
+
exactCwd: target.identity.cwd,
|
|
2773
|
+
neutralCwd,
|
|
2774
|
+
env: options.context.env,
|
|
2775
|
+
});
|
|
2776
|
+
const [exactResult, neutralResult] = await Promise.all([
|
|
2777
|
+
runNativeCliChecked(invocations.exact, runner),
|
|
2778
|
+
runNativeCliChecked(invocations.neutral, runner),
|
|
2779
|
+
]);
|
|
2780
|
+
postcheckCodexProjectPluginActivation({
|
|
2781
|
+
pluginId,
|
|
2782
|
+
userEnabled: userConfig.snapshot.plugins[pluginId]?.enabled === true,
|
|
2783
|
+
projectEnabled: projectConfig.snapshot.plugins[pluginId]?.enabled === true,
|
|
2784
|
+
exactResult,
|
|
2785
|
+
neutralResult,
|
|
2786
|
+
});
|
|
2787
|
+
}
|
|
2788
|
+
finally {
|
|
2789
|
+
await fs.remove(neutralCwd);
|
|
2790
|
+
}
|
|
2791
|
+
return 'present';
|
|
2576
2792
|
}
|
|
2577
2793
|
return 'absent';
|
|
2578
2794
|
},
|
|
@@ -2645,6 +2861,21 @@ function createNativeDriver(options) {
|
|
|
2645
2861
|
throw new Error('Missing rebuild integration plan.');
|
|
2646
2862
|
const marketplaceName = target.identity.marketplaceName ?? MARKETPLACE_NAME;
|
|
2647
2863
|
const expectedMarketplaceRoot = target.identity.marketplaceRoot ?? resolveAwHome(options.context.env);
|
|
2864
|
+
if (target.identity.platform === 'codex') {
|
|
2865
|
+
const before = await inspectCodexNativeResidue({
|
|
2866
|
+
pluginName: target.identity.pluginName,
|
|
2867
|
+
cwd: target.identity.cwd,
|
|
2868
|
+
env: options.context.env,
|
|
2869
|
+
marketplaceName,
|
|
2870
|
+
marketplaceRoot: expectedMarketplaceRoot,
|
|
2871
|
+
});
|
|
2872
|
+
if (before.state === 'foreign') {
|
|
2873
|
+
throw new Error(`codexSharedNativeForeign: ${before.labels.join(', ')}`);
|
|
2874
|
+
}
|
|
2875
|
+
if (before.state === 'present') {
|
|
2876
|
+
await runNativeCliChecked(buildCodexPluginRemoveInvocation(requireCodexManager(codexManager), `${target.identity.pluginName}@${marketplaceName}`, target.identity.cwd, options.context.env), runner);
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2648
2879
|
const writeGeneration = async () => {
|
|
2649
2880
|
if (mirrorTransactions.has(target)) {
|
|
2650
2881
|
throw new Error('Marketplace mirror transaction is already active for this target.');
|
|
@@ -2677,18 +2908,22 @@ function createNativeDriver(options) {
|
|
|
2677
2908
|
const marketplaceRoot = mirrored.rootDir;
|
|
2678
2909
|
const pluginId = `${target.identity.pluginName}@${marketplaceName}`;
|
|
2679
2910
|
if (target.identity.platform === 'codex') {
|
|
2911
|
+
const nativePluginRoot = await canonicalizePathThroughExistingAncestor(path.join(marketplaceRoot, 'plugins', target.identity.pluginName));
|
|
2680
2912
|
const requiredSkillWrites = plan.writes.filter((entry) => !entry.optional
|
|
2681
2913
|
&& entry.component?.kind === 'skill-directory'
|
|
2682
2914
|
&& entry.artifact.kind === 'skill'
|
|
2683
2915
|
&& entry.artifact.state !== 'disabled'
|
|
2684
|
-
&& target.identity.scope === 'user'
|
|
2916
|
+
&& target.identity.scope === 'user'
|
|
2917
|
+
&& !path.relative(nativePluginRoot, entry.component.path).startsWith('..'));
|
|
2685
2918
|
for (const write of requiredSkillWrites) {
|
|
2686
2919
|
await write.write();
|
|
2687
2920
|
}
|
|
2688
2921
|
const skillsRelativeRoot = await ensureCodexActiveSkillsManifest({
|
|
2689
2922
|
plugin: target.plugin,
|
|
2690
2923
|
marketplaceRoot,
|
|
2691
|
-
hasRequiredSkills:
|
|
2924
|
+
hasRequiredSkills: target.identity.scope === 'project'
|
|
2925
|
+
? target.plugin.manifest.skills !== undefined
|
|
2926
|
+
: requiredSkillWrites.length > 0,
|
|
2692
2927
|
});
|
|
2693
2928
|
const config = await readCodexConfigFile(resolveCodexPaths(target.identity.cwd, 'user', options.context.env).configTomlPath);
|
|
2694
2929
|
const marketplace = config.snapshot.marketplaces[marketplaceName];
|
|
@@ -2796,13 +3031,10 @@ export function createRebuildIntegration(options) {
|
|
|
2796
3031
|
cwd: input.cwd,
|
|
2797
3032
|
rootNames: input.platform === 'codex' ? ['.agents', '.codex'] : ['.claude'],
|
|
2798
3033
|
managedPaths: input.platform === 'codex'
|
|
2799
|
-
? ['.codex/config.toml'
|
|
3034
|
+
? ['.codex/config.toml']
|
|
2800
3035
|
: ['.claude/settings.json'],
|
|
2801
3036
|
});
|
|
2802
3037
|
input = { ...input, cwd: canonicalRepoRoot };
|
|
2803
|
-
if (materializing && input.platform === 'codex' && input.plugin.manifest.apps) {
|
|
2804
|
-
throw new Error(`Codex project scope 不支持 apps:${input.plugin.manifest.name}`);
|
|
2805
|
-
}
|
|
2806
3038
|
}
|
|
2807
3039
|
if (materializing && input.platform === 'codex') {
|
|
2808
3040
|
await preflightCodexWriteSafety({
|
|
@@ -2829,9 +3061,15 @@ export function createRebuildIntegration(options) {
|
|
|
2829
3061
|
env: options.context.env,
|
|
2830
3062
|
loadPlugin,
|
|
2831
3063
|
});
|
|
3064
|
+
if (input.expectedLifecycleMetadataIntegrityDigest !== undefined
|
|
3065
|
+
&& active?.metadata.metadataIntegrityDigest
|
|
3066
|
+
!== input.expectedLifecycleMetadataIntegrityDigest) {
|
|
3067
|
+
throw new Error('stateInventoryFailed: lifecycle active generation changed after verified inventory.');
|
|
3068
|
+
}
|
|
2832
3069
|
historicalArtifacts = active?.artifacts ?? [];
|
|
2833
3070
|
}
|
|
2834
3071
|
let writes = [];
|
|
3072
|
+
let currentRemovalComponents = [];
|
|
2835
3073
|
if (input.command !== 'cleanup' && input.command !== 'uninstall') {
|
|
2836
3074
|
if (input.readOnlyInspectionPlan) {
|
|
2837
3075
|
writes = await buildInspectionWrites({
|
|
@@ -2841,6 +3079,8 @@ export function createRebuildIntegration(options) {
|
|
|
2841
3079
|
cwd: input.cwd,
|
|
2842
3080
|
env: options.context.env,
|
|
2843
3081
|
marketplaceRoot: input.marketplaceRoot,
|
|
3082
|
+
marketplaceName: input.marketplaceName,
|
|
3083
|
+
userActivationDesired: options.context.codexUserActivationDesired,
|
|
2844
3084
|
});
|
|
2845
3085
|
}
|
|
2846
3086
|
else if (input.platform === 'codex') {
|
|
@@ -2850,6 +3090,8 @@ export function createRebuildIntegration(options) {
|
|
|
2850
3090
|
event,
|
|
2851
3091
|
historicalArtifacts,
|
|
2852
3092
|
scriptTimeoutMs: options.scriptTimeoutMs,
|
|
3093
|
+
marketplaceName: input.marketplaceName,
|
|
3094
|
+
userActivationDesired: options.context.codexUserActivationDesired,
|
|
2853
3095
|
});
|
|
2854
3096
|
}
|
|
2855
3097
|
else {
|
|
@@ -2862,6 +3104,22 @@ export function createRebuildIntegration(options) {
|
|
|
2862
3104
|
});
|
|
2863
3105
|
}
|
|
2864
3106
|
}
|
|
3107
|
+
else if (input.platform === 'codex' && input.scope === 'project') {
|
|
3108
|
+
const currentRemovalWrites = await buildInspectionWrites({
|
|
3109
|
+
plugin: input.plugin,
|
|
3110
|
+
platform: input.platform,
|
|
3111
|
+
scope: input.scope,
|
|
3112
|
+
cwd: input.cwd,
|
|
3113
|
+
env: options.context.env,
|
|
3114
|
+
marketplaceRoot: input.marketplaceRoot,
|
|
3115
|
+
marketplaceName: input.marketplaceName,
|
|
3116
|
+
userActivationDesired: options.context.codexUserActivationDesired,
|
|
3117
|
+
});
|
|
3118
|
+
currentRemovalComponents = currentRemovalWrites.flatMap((write) => [
|
|
3119
|
+
...(write.additionalComponents ?? []),
|
|
3120
|
+
...(write.component ? [write.component] : []),
|
|
3121
|
+
]);
|
|
3122
|
+
}
|
|
2865
3123
|
const desiredComponents = writes.filter((write) => !write.optional).flatMap((write) => [
|
|
2866
3124
|
...(write.additionalComponents ?? []),
|
|
2867
3125
|
...(write.component ? [write.component] : []),
|
|
@@ -2889,8 +3147,13 @@ export function createRebuildIntegration(options) {
|
|
|
2889
3147
|
})))).flat();
|
|
2890
3148
|
const skillRoots = input.platform === 'codex'
|
|
2891
3149
|
? input.scope === 'project'
|
|
2892
|
-
? [
|
|
2893
|
-
: [
|
|
3150
|
+
? []
|
|
3151
|
+
: [
|
|
3152
|
+
path.join(getCodexHome(options.context.env), 'skills'),
|
|
3153
|
+
...(input.preserveCodexSharedNativePayload
|
|
3154
|
+
? []
|
|
3155
|
+
: [await canonicalizePathThroughExistingAncestor(path.join(input.marketplaceRoot ?? resolveAwHome(options.context.env), 'plugins', input.plugin.manifest.name))]),
|
|
3156
|
+
]
|
|
2894
3157
|
: [path.join(configDir, 'skills')];
|
|
2895
3158
|
const scanSkills = () => Promise.all(skillRoots.map((root) => scanOwnedSkills({
|
|
2896
3159
|
sourceId: input.sourceId,
|
|
@@ -2901,7 +3164,7 @@ export function createRebuildIntegration(options) {
|
|
|
2901
3164
|
scope: input.scope,
|
|
2902
3165
|
})));
|
|
2903
3166
|
const scannedSkills = (await scanSkills()).flat();
|
|
2904
|
-
|
|
3167
|
+
let historicalComponents = await buildHistoricalComponents({
|
|
2905
3168
|
sourceId: input.sourceId,
|
|
2906
3169
|
sourceIdentity: input.sourceIdentity,
|
|
2907
3170
|
pluginId: input.pluginId,
|
|
@@ -2911,7 +3174,24 @@ export function createRebuildIntegration(options) {
|
|
|
2911
3174
|
cwd: input.cwd,
|
|
2912
3175
|
env: options.context.env,
|
|
2913
3176
|
artifacts: historicalArtifacts,
|
|
3177
|
+
command: input.command,
|
|
2914
3178
|
});
|
|
3179
|
+
if (input.preserveCodexSharedNativePayload) {
|
|
3180
|
+
const nativePluginRoots = await Promise.all([
|
|
3181
|
+
path.join(input.marketplaceRoot ?? resolveAwHome(options.context.env), 'plugins', input.plugin.manifest.name),
|
|
3182
|
+
path.join(getCodexHome(options.context.env), 'plugins', 'cache', input.marketplaceName ?? MARKETPLACE_NAME, input.plugin.manifest.name),
|
|
3183
|
+
].map((root) => canonicalizePathThroughExistingAncestor(root)));
|
|
3184
|
+
historicalComponents = historicalComponents.filter((component) => {
|
|
3185
|
+
if (!('path' in component))
|
|
3186
|
+
return true;
|
|
3187
|
+
return nativePluginRoots.every((nativePluginRoot) => {
|
|
3188
|
+
const relative = path.relative(nativePluginRoot, path.resolve(component.path));
|
|
3189
|
+
return relative === '..'
|
|
3190
|
+
|| relative.startsWith(`..${path.sep}`)
|
|
3191
|
+
|| path.isAbsolute(relative);
|
|
3192
|
+
});
|
|
3193
|
+
});
|
|
3194
|
+
}
|
|
2915
3195
|
const sharedComponents = input.platform === 'codex'
|
|
2916
3196
|
? await scanCodexSharedComponents({
|
|
2917
3197
|
sourceId: input.sourceId,
|
|
@@ -2921,7 +3201,6 @@ export function createRebuildIntegration(options) {
|
|
|
2921
3201
|
scope: input.scope,
|
|
2922
3202
|
paths: resolveCodexPaths(input.cwd, input.scope, options.context.env),
|
|
2923
3203
|
scanConfig: true,
|
|
2924
|
-
scanHooks: true,
|
|
2925
3204
|
ignoreReadErrors: true,
|
|
2926
3205
|
})
|
|
2927
3206
|
: await scanClaudeSharedComponents({
|
|
@@ -2933,10 +3212,28 @@ export function createRebuildIntegration(options) {
|
|
|
2933
3212
|
settingsPath: resolveClaudePaths(input.cwd, input.scope, options.context.env).settingsPath,
|
|
2934
3213
|
});
|
|
2935
3214
|
const nativeRequired = input.platform === 'claude'
|
|
2936
|
-
|| (input.
|
|
2937
|
-
const
|
|
3215
|
+
|| analyzeCodexSurface(input.plugin.manifest).nativeKinds.length > 0;
|
|
3216
|
+
const nativeDesired = nativeRequired
|
|
3217
|
+
&& input.command !== 'cleanup'
|
|
3218
|
+
&& input.command !== 'uninstall';
|
|
3219
|
+
const codexNative = input.platform === 'codex' && nativeRequired
|
|
3220
|
+
? await inspectCodexNativeResidue({
|
|
3221
|
+
pluginName: input.plugin.manifest.name,
|
|
3222
|
+
cwd: input.cwd,
|
|
3223
|
+
env: options.context.env,
|
|
3224
|
+
marketplaceName: input.marketplaceName,
|
|
3225
|
+
marketplaceRoot: input.marketplaceRoot,
|
|
3226
|
+
})
|
|
3227
|
+
: undefined;
|
|
3228
|
+
const codexNativeExact = codexNative?.state === 'present'
|
|
3229
|
+
&& codexNative.versions.length > 0
|
|
3230
|
+
&& codexNative.versions.every((version) => version === input.plugin.manifest.version);
|
|
3231
|
+
const materializeNativeRequired = nativeDesired && (input.platform === 'claude'
|
|
3232
|
+
|| input.command === 'reinstall'
|
|
3233
|
+
|| !codexNativeExact);
|
|
3234
|
+
const nativeCleanupCandidate = input.platform === 'claude'
|
|
2938
3235
|
&& input.includeNativeCleanupCandidate !== false;
|
|
2939
|
-
const nativePlugin = (
|
|
3236
|
+
const nativePlugin = (nativeDesired || nativeCleanupCandidate)
|
|
2940
3237
|
? [{
|
|
2941
3238
|
kind: 'native-plugin',
|
|
2942
3239
|
identity: ownerIdentity(input.plugin.manifest.name, 'native', input.platform, input.scope),
|
|
@@ -2946,7 +3243,7 @@ export function createRebuildIntegration(options) {
|
|
|
2946
3243
|
const marketplaceDesired = nativeRequired
|
|
2947
3244
|
&& input.command !== 'cleanup'
|
|
2948
3245
|
&& input.command !== 'uninstall';
|
|
2949
|
-
const marketplace = input.platform === 'codex' && input.scope === 'user'
|
|
3246
|
+
const marketplace = input.platform === 'codex' && input.scope === 'user' && nativeDesired
|
|
2950
3247
|
? [createCodexMarketplaceComponent({
|
|
2951
3248
|
pluginName: input.plugin.manifest.name,
|
|
2952
3249
|
codexManager: options.context.codexManager,
|
|
@@ -2969,20 +3266,48 @@ export function createRebuildIntegration(options) {
|
|
|
2969
3266
|
desired: marketplaceDesired,
|
|
2970
3267
|
})]
|
|
2971
3268
|
: [];
|
|
2972
|
-
if (
|
|
2973
|
-
&& input.command !== 'cleanup'
|
|
2974
|
-
&& input.command !== 'uninstall')
|
|
3269
|
+
if (nativeDesired)
|
|
2975
3270
|
desiredComponents.push(...nativePlugin, ...marketplace);
|
|
3271
|
+
const activationCleanup = input.platform === 'codex'
|
|
3272
|
+
&& nativeRequired
|
|
3273
|
+
&& (input.command === 'cleanup' || input.command === 'uninstall')
|
|
3274
|
+
? (input.scope === 'project'
|
|
3275
|
+
? [
|
|
3276
|
+
createCodexActivationComponent({
|
|
3277
|
+
identity: ownerIdentity(input.plugin.manifest.name, internalArtifactKey('activation', 'project'), 'codex', input.scope),
|
|
3278
|
+
configPath: resolveCodexPaths(input.cwd, 'project', options.context.env).configTomlPath,
|
|
3279
|
+
codexPluginId: `${input.plugin.manifest.name}@${input.marketplaceName ?? MARKETPLACE_NAME}`,
|
|
3280
|
+
desired: true,
|
|
3281
|
+
}),
|
|
3282
|
+
]
|
|
3283
|
+
: [createCodexActivationComponent({
|
|
3284
|
+
identity: ownerIdentity(input.plugin.manifest.name, internalArtifactKey('activation', 'user'), 'codex', input.scope),
|
|
3285
|
+
configPath: resolveCodexPaths(input.cwd, 'user', options.context.env).configTomlPath,
|
|
3286
|
+
codexPluginId: `${input.plugin.manifest.name}@${input.marketplaceName ?? MARKETPLACE_NAME}`,
|
|
3287
|
+
desired: true,
|
|
3288
|
+
})])
|
|
3289
|
+
: [];
|
|
3290
|
+
const explicitActivationCleanup = input.platform === 'codex'
|
|
3291
|
+
? (input.codexActivationCleanup ?? []).map((activation) => createCodexActivationCleanupComponent({
|
|
3292
|
+
identity: ownerIdentity(input.plugin.manifest.name, internalArtifactKey('activation', activation.scope), 'codex', input.scope),
|
|
3293
|
+
configPath: resolveCodexPaths(input.cwd, activation.scope, options.context.env).configTomlPath,
|
|
3294
|
+
codexPluginId: activation.codexPluginId,
|
|
3295
|
+
disposition: activation.disposition,
|
|
3296
|
+
}))
|
|
3297
|
+
: [];
|
|
2976
3298
|
const allComponents = dedupeComponents([
|
|
2977
3299
|
...desiredComponents,
|
|
2978
3300
|
...optionalComponents,
|
|
2979
3301
|
...(input.platform === 'codex' ? sharedComponents : []),
|
|
2980
3302
|
...scannedText,
|
|
2981
3303
|
...scannedSkills,
|
|
3304
|
+
...currentRemovalComponents,
|
|
2982
3305
|
...historicalComponents,
|
|
2983
3306
|
...(input.platform === 'claude' ? sharedComponents : []),
|
|
2984
3307
|
...nativePlugin,
|
|
2985
3308
|
...marketplace,
|
|
3309
|
+
...activationCleanup,
|
|
3310
|
+
...explicitActivationCleanup,
|
|
2986
3311
|
]);
|
|
2987
3312
|
bindNamespacedOwnerIdentity(allComponents, input);
|
|
2988
3313
|
const desiredKeys = new Set(desiredComponents.map(componentIdentityKey));
|
|
@@ -3005,11 +3330,15 @@ export function createRebuildIntegration(options) {
|
|
|
3005
3330
|
ownedComponents: allComponents,
|
|
3006
3331
|
desiredOwnedComponents: allComponents.filter((component) => desiredKeys.has(componentIdentityKey(component))),
|
|
3007
3332
|
optionalOwnedComponents: allComponents.filter((component) => optionalComponentKeys.has(componentIdentityKey(component))),
|
|
3008
|
-
requiresNative:
|
|
3009
|
-
|
|
3010
|
-
&& input.command !== 'uninstall',
|
|
3333
|
+
requiresNative: nativeDesired,
|
|
3334
|
+
writeNativeRequired: materializeNativeRequired,
|
|
3011
3335
|
artifacts: writes.map((write) => write.artifact),
|
|
3012
3336
|
lifecycleSource: input.plugin,
|
|
3337
|
+
...(input.expectedLifecycleMetadataIntegrityDigest
|
|
3338
|
+
? {
|
|
3339
|
+
historicalLifecycleMetadataIntegrityDigest: input.expectedLifecycleMetadataIntegrityDigest,
|
|
3340
|
+
}
|
|
3341
|
+
: {}),
|
|
3013
3342
|
};
|
|
3014
3343
|
plans.set(target, {
|
|
3015
3344
|
catalog: input.catalog,
|
|
@@ -3022,7 +3351,13 @@ export function createRebuildIntegration(options) {
|
|
|
3022
3351
|
};
|
|
3023
3352
|
}
|
|
3024
3353
|
export async function inspectRebuildTarget(options) {
|
|
3025
|
-
const integration = createRebuildIntegration({
|
|
3354
|
+
const integration = createRebuildIntegration({
|
|
3355
|
+
context: {
|
|
3356
|
+
...options.context,
|
|
3357
|
+
codexUserActivationDesired: options.userActivationDesired
|
|
3358
|
+
?? options.context.codexUserActivationDesired,
|
|
3359
|
+
},
|
|
3360
|
+
});
|
|
3026
3361
|
const target = await integration.buildTarget({
|
|
3027
3362
|
...(options.sourceId ? {
|
|
3028
3363
|
sourceId: options.sourceId,
|
|
@@ -3042,7 +3377,9 @@ export async function inspectRebuildTarget(options) {
|
|
|
3042
3377
|
});
|
|
3043
3378
|
const inspection = await inspectOwnedComponents({
|
|
3044
3379
|
components: target.ownedComponents,
|
|
3045
|
-
nativeProbe:
|
|
3380
|
+
nativeProbe: options.nativeProbe
|
|
3381
|
+
?? integration.driver.probeNativeHealth
|
|
3382
|
+
?? integration.driver.probeNative,
|
|
3046
3383
|
verifyHealth: true,
|
|
3047
3384
|
});
|
|
3048
3385
|
if (!target.requiresNative)
|
|
@@ -3141,7 +3478,7 @@ export async function discoverRebuildTargetNames(options) {
|
|
|
3141
3478
|
cwd: options.cwd,
|
|
3142
3479
|
rootNames: options.platform === 'codex' ? ['.agents', '.codex'] : ['.claude'],
|
|
3143
3480
|
managedPaths: options.platform === 'codex'
|
|
3144
|
-
? ['.codex/config.toml'
|
|
3481
|
+
? ['.codex/config.toml']
|
|
3145
3482
|
: ['.claude/settings.json'],
|
|
3146
3483
|
});
|
|
3147
3484
|
options = { ...options, cwd: canonicalRepoRoot };
|
|
@@ -3155,7 +3492,7 @@ export async function discoverRebuildTargetNames(options) {
|
|
|
3155
3492
|
names.add(name);
|
|
3156
3493
|
const skillRoots = options.platform === 'codex'
|
|
3157
3494
|
? options.scope === 'project'
|
|
3158
|
-
? [
|
|
3495
|
+
? []
|
|
3159
3496
|
: [path.join(resolveAwHome(options.env), 'plugins')]
|
|
3160
3497
|
: [path.join(paths.configDir, 'skills')];
|
|
3161
3498
|
if (options.platform === 'codex' && options.scope === 'user') {
|
|
@@ -3190,24 +3527,14 @@ export async function discoverRebuildTargetNames(options) {
|
|
|
3190
3527
|
for (const name of Object.keys(config.snapshot.developerInstructions.managedBlocks)) {
|
|
3191
3528
|
names.add(sharedOwnerPluginName(name));
|
|
3192
3529
|
}
|
|
3193
|
-
for (const server of Object.values(config.snapshot.mcpServers)) {
|
|
3194
|
-
const envValue = server.value?.env;
|
|
3195
|
-
const owner = envValue
|
|
3196
|
-
&& typeof envValue === 'object'
|
|
3197
|
-
&& !Array.isArray(envValue)
|
|
3198
|
-
&& typeof envValue[MCP_OWNER_ENV] === 'string'
|
|
3199
|
-
? envValue[MCP_OWNER_ENV]
|
|
3200
|
-
: null;
|
|
3201
|
-
if (owner)
|
|
3202
|
-
names.add(sharedOwnerPluginName(owner));
|
|
3203
|
-
}
|
|
3204
3530
|
if (options.scope === 'project') {
|
|
3205
|
-
const
|
|
3206
|
-
const
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3531
|
+
const suffix = `@${options.marketplaceName ?? MARKETPLACE_NAME}`;
|
|
3532
|
+
for (const pluginId of Object.keys(config.snapshot.plugins)) {
|
|
3533
|
+
if (!pluginId.endsWith(suffix))
|
|
3534
|
+
continue;
|
|
3535
|
+
const name = pluginId.slice(0, -suffix.length);
|
|
3536
|
+
if (/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name))
|
|
3537
|
+
names.add(name);
|
|
3211
3538
|
}
|
|
3212
3539
|
}
|
|
3213
3540
|
else {
|