@lesliechan721/aw 0.1.0-beta.54 → 0.1.0-beta.56
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/lib/plugin-version-check.mjs +1 -1
- package/dist/authoring/scripts/validate-plugin.mjs +208 -31
- package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
- package/dist/scripts/bundle-plugins.js +8 -3
- package/dist/scripts/bundle-plugins.js.map +1 -1
- package/dist/src/adapters/helpers.d.ts +7 -0
- package/dist/src/adapters/helpers.js.map +1 -1
- package/dist/src/commands/aw-capabilities.d.ts +1 -1
- package/dist/src/commands/aw-capabilities.js +2 -0
- package/dist/src/commands/aw-capabilities.js.map +1 -1
- package/dist/src/commands/managed-commands.d.ts +26 -0
- package/dist/src/commands/managed-commands.js +277 -0
- package/dist/src/commands/managed-commands.js.map +1 -0
- package/dist/src/commands/operation-plan.d.ts +30 -1
- package/dist/src/commands/operation-plan.js.map +1 -1
- package/dist/src/commands/operation-planner.d.ts +1 -0
- package/dist/src/commands/operation-planner.js +1 -0
- package/dist/src/commands/operation-planner.js.map +1 -1
- package/dist/src/commands/plugin-capabilities.js +29 -1
- package/dist/src/commands/plugin-capabilities.js.map +1 -1
- package/dist/src/commands/rebuild-command.d.ts +2 -1
- package/dist/src/commands/rebuild-command.js +66 -26
- package/dist/src/commands/rebuild-command.js.map +1 -1
- package/dist/src/commands/rebuild-integration.d.ts +13 -1
- package/dist/src/commands/rebuild-integration.js +535 -61
- package/dist/src/commands/rebuild-integration.js.map +1 -1
- package/dist/src/commands/rebuild-operations.d.ts +3 -1
- package/dist/src/commands/rebuild-operations.js +9 -5
- package/dist/src/commands/rebuild-operations.js.map +1 -1
- package/dist/src/generators/local-plugin.js +16 -0
- package/dist/src/generators/local-plugin.js.map +1 -1
- package/dist/src/generators/permissions.d.ts +5 -2
- package/dist/src/generators/permissions.js +28 -15
- package/dist/src/generators/permissions.js.map +1 -1
- package/dist/src/index.js +11 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/output/command-receipt.d.ts +3 -1
- package/dist/src/output/command-receipt.js +30 -9
- package/dist/src/output/command-receipt.js.map +1 -1
- package/dist/src/output/rebuild-progress.js +14 -0
- package/dist/src/output/rebuild-progress.js.map +1 -1
- package/dist/src/portable/materializer.js +3 -0
- package/dist/src/portable/materializer.js.map +1 -1
- package/dist/src/review/skill-materialization.d.ts +14 -1
- package/dist/src/review/skill-materialization.js +138 -0
- package/dist/src/review/skill-materialization.js.map +1 -1
- package/dist/src/tui/console-app-v4.js +19 -7
- package/dist/src/tui/console-app-v4.js.map +1 -1
- package/dist/src/tui/controller-runtime.js +23 -9
- package/dist/src/tui/controller-runtime.js.map +1 -1
- package/dist/src/tui/controller.d.ts +5 -1
- package/dist/src/tui/controller.js +24 -3
- package/dist/src/tui/controller.js.map +1 -1
- package/dist/src/tui/diagnostic-projection.js +34 -6
- package/dist/src/tui/diagnostic-projection.js.map +1 -1
- package/dist/src/tui/operation-review-planner.js +102 -16
- package/dist/src/tui/operation-review-planner.js.map +1 -1
- package/dist/src/types.d.ts +22 -2
- package/dist/src/types.js.map +1 -1
- package/dist/src/utils/artifact-plan.d.ts +4 -3
- package/dist/src/utils/artifact-plan.js +50 -7
- package/dist/src/utils/artifact-plan.js.map +1 -1
- package/dist/src/utils/artifact-renderer.d.ts +7 -3
- package/dist/src/utils/artifact-renderer.js +19 -6
- package/dist/src/utils/artifact-renderer.js.map +1 -1
- package/dist/src/utils/artifact-request.d.ts +16 -5
- package/dist/src/utils/artifact-request.js +27 -15
- package/dist/src/utils/artifact-request.js.map +1 -1
- package/dist/src/utils/artifact-sources.d.ts +4 -0
- package/dist/src/utils/artifact-sources.js +17 -0
- package/dist/src/utils/artifact-sources.js.map +1 -1
- package/dist/src/utils/bundle-layout.js +7 -0
- package/dist/src/utils/bundle-layout.js.map +1 -1
- package/dist/src/utils/claude-skills-root.d.ts +8 -0
- package/dist/src/utils/claude-skills-root.js +76 -0
- package/dist/src/utils/claude-skills-root.js.map +1 -0
- package/dist/src/utils/codex-skills-root.d.ts +4 -0
- package/dist/src/utils/codex-skills-root.js +25 -0
- package/dist/src/utils/codex-skills-root.js.map +1 -0
- package/dist/src/utils/dynamic-skill-tree.d.ts +1 -0
- package/dist/src/utils/dynamic-skill-tree.js +25 -6
- package/dist/src/utils/dynamic-skill-tree.js.map +1 -1
- package/dist/src/utils/local-marketplace.d.ts +9 -0
- package/dist/src/utils/local-marketplace.js +170 -90
- package/dist/src/utils/local-marketplace.js.map +1 -1
- package/dist/src/utils/managed-command-closure.d.ts +3 -0
- package/dist/src/utils/managed-command-closure.js +35 -0
- package/dist/src/utils/managed-command-closure.js.map +1 -0
- package/dist/src/utils/managed-command-evidence.d.ts +7 -0
- package/dist/src/utils/managed-command-evidence.js +86 -0
- package/dist/src/utils/managed-command-evidence.js.map +1 -0
- package/dist/src/utils/managed-command-materialization.d.ts +106 -0
- package/dist/src/utils/managed-command-materialization.js +307 -0
- package/dist/src/utils/managed-command-materialization.js.map +1 -0
- package/dist/src/utils/managed-command-runtime.d.ts +32 -0
- package/dist/src/utils/managed-command-runtime.js +110 -0
- package/dist/src/utils/managed-command-runtime.js.map +1 -0
- package/dist/src/utils/managed-command-store.d.ts +124 -0
- package/dist/src/utils/managed-command-store.js +576 -0
- package/dist/src/utils/managed-command-store.js.map +1 -0
- package/dist/src/utils/ownership.d.ts +2 -0
- package/dist/src/utils/plugin-config-impact.d.ts +8 -1
- package/dist/src/utils/plugin-config-impact.js +101 -26
- package/dist/src/utils/plugin-config-impact.js.map +1 -1
- package/dist/src/utils/plugin-config-write.js +142 -60
- package/dist/src/utils/plugin-config-write.js.map +1 -1
- package/dist/src/utils/rendered-contract.js +18 -1
- package/dist/src/utils/rendered-contract.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import fs from 'fs-extra';
|
|
4
|
+
import { catalogFileMode } from '@lesliechan721/aw-plugin-core';
|
|
5
|
+
import { COMMAND_PRODUCER_RECORD_FILE, getManagedCommandArtifactTargets, getCommandPackageId } from '@lesliechan721/aw-plugin-core/manifest';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import { commandSetsForProducer, createManagedCommandReferenceResolver, discoverRetainedManagedCommandPackages, retainedManagedCommandPackageResult, managedCommandProducerIdentity, prepareManagedCommandPackage, resolveSkillManagedCommandReferences, } from '../utils/managed-command-materialization.js';
|
|
8
|
+
import { assertManagedCommandBindingIdentities, normalizeInstalledCommandProducer, managedCommandDiagnostic, ManagedCommandError, publishManagedCommandBindings, revokeManagedCommandBindings, syncManagedCommandPackage, readInstalledCommandProducer, listManagedCommands } from '../utils/managed-command-store.js';
|
|
4
9
|
import { injectInstalledRuntimeConfig, carryRuntimeConfigNativeReceipt } from '../utils/runtime-config.js';
|
|
10
|
+
import { resolveCodexSkillsRelativeRoot } from '../utils/codex-skills-root.js';
|
|
11
|
+
import { resolveClaudeSkillsRelativeRoot } from '../utils/claude-skills-root.js';
|
|
5
12
|
import { generateAgentArtifacts } from '../generators/agents.js';
|
|
6
13
|
import { generateInstructionArtifacts, renderCodexManagedInstructionBlock, } from '../generators/instructions.js';
|
|
7
14
|
import { generateCodexPermissionArtifacts, mergeClaudePermissions, } from '../generators/permissions.js';
|
|
@@ -24,7 +31,7 @@ import { validateRuntimeSkillClosure } from '../utils/runtime-skill-closure.js';
|
|
|
24
31
|
import { findSensitiveTokenPath } from '../utils/sensitive-content.js';
|
|
25
32
|
import { atomicRemoveConfigFile, atomicReplaceConfigFile, withConfigFileMutation, } from '../utils/plugin-config.js';
|
|
26
33
|
import { readCatalogSourcesConfigSnapshot } from '../utils/catalog-sources.js';
|
|
27
|
-
import { assertSafeNamespacedMarketplacePluginRoot, getNamespacedMarketplaceName, MarketplaceRootValidationError, projectMarketplaceManifestForPlatform, readStrictNamespacedMarketplaceRoot, replayNamespacedMarketplacePluginProjections, replayNamespacedCodexSkillOverlays, } from '../utils/local-marketplace.js';
|
|
34
|
+
import { assertSafeNamespacedMarketplacePluginRoot, getNamespacedMarketplaceName, MarketplaceRootValidationError, projectMarketplaceManifestForPlatform, readStrictNamespacedMarketplaceRoot, replayNamespacedMarketplacePluginProjections, refreshPreservedPlatformReceipt, replayNamespacedCodexSkillOverlays, } from '../utils/local-marketplace.js';
|
|
28
35
|
import { commitMarketplaceGenerationBackup } from '../utils/orphan-marketplace-cleanup.js';
|
|
29
36
|
import { readClaudeSettings, writeClaudeSettings } from '../adapters/helpers.js';
|
|
30
37
|
import { analyzeCodexSurface } from '../utils/codex-surface-analysis.js';
|
|
@@ -61,6 +68,155 @@ function withoutVerifiedRequirementsReceipt(plugin, files) {
|
|
|
61
68
|
delete output[AW_PLUGIN_REQUIREMENTS_RECEIPT_PATH];
|
|
62
69
|
return output;
|
|
63
70
|
}
|
|
71
|
+
const commandPackagePreparationCaches = new WeakMap();
|
|
72
|
+
const staticCommandPreparationCaches = new WeakMap();
|
|
73
|
+
async function prepareStaticCommandArtifacts(options) {
|
|
74
|
+
const targets = getManagedCommandArtifactTargets(options.plugin.manifest).filter((target) => target.producer.kind === 'static-skill' && target.platform === options.platform);
|
|
75
|
+
if (!targets.length)
|
|
76
|
+
return [];
|
|
77
|
+
const config = await resolveArtifactRenderConfig({ sourceId: options.plugin.sourceId, sourceIdentity: options.plugin.sourceIdentity, logicalIdentity: options.plugin.catalogLogicalIdentity, pluginName: options.plugin.manifest.name, manifest: options.plugin.manifest, scope: 'user', cwd: options.cwd, env: options.env });
|
|
78
|
+
const key = JSON.stringify([options.plugin.rootDir, options.platform, config]);
|
|
79
|
+
let cache = staticCommandPreparationCaches.get(options.context);
|
|
80
|
+
if (!cache) {
|
|
81
|
+
cache = new Map();
|
|
82
|
+
staticCommandPreparationCaches.set(options.context, cache);
|
|
83
|
+
}
|
|
84
|
+
let prepared = cache.get(key);
|
|
85
|
+
if (!prepared) {
|
|
86
|
+
prepared = (async () => {
|
|
87
|
+
const rendered = await runArtifactRenderer({ plugin: options.plugin, event: 'install', platform: options.platform, scope: 'user', configScope: 'user', cwd: options.cwd, commandEnv: options.env, requestedArtifactIds: targets.map((target) => target.id) });
|
|
88
|
+
return validateArtifactPlan({ manifest: options.plugin.manifest, plan: rendered.plan, platform: options.platform, scope: 'user', requestedArtifactIds: rendered.context.requestedArtifactIds, renderConfig: rendered.context.config, rendererDigest: rendered.target.rendererDigest, manifestDigest: rendered.target.manifestDigest }).artifacts;
|
|
89
|
+
})();
|
|
90
|
+
cache.set(key, prepared);
|
|
91
|
+
}
|
|
92
|
+
return prepared;
|
|
93
|
+
}
|
|
94
|
+
/** Normal write preparation compares its real candidate; read-only inspection never calls this. */
|
|
95
|
+
export async function hasPreparedStaticNativeCommandDrift(options) {
|
|
96
|
+
const artifacts = await prepareStaticCommandArtifacts({ ...options, platform: 'codex' });
|
|
97
|
+
if (!artifacts.length)
|
|
98
|
+
return false;
|
|
99
|
+
const pluginRoot = path.join(options.marketplaceRoot, 'plugins', options.plugin.manifest.name);
|
|
100
|
+
const skillsRoot = path.join(pluginRoot, await resolveCodexSkillsRelativeRoot({ ...options.plugin, rootDir: pluginRoot }));
|
|
101
|
+
for (const set of options.plugin.manifest.managedCommands?.sets ?? []) {
|
|
102
|
+
if (set.producer.kind !== 'static-skill' || set.producer.platform !== 'codex' || set.mode !== 'dynamic')
|
|
103
|
+
continue;
|
|
104
|
+
const producer = await managedCommandProducerIdentity({ ...options, producer: set.producer, platform: 'codex', scope: 'user' });
|
|
105
|
+
const expected = commandSetsForProducer({ plugin: options.plugin, producer: set.producer, artifacts });
|
|
106
|
+
const previous = await readInstalledCommandProducer(path.join(skillsRoot, set.producer.skillKey)).catch(() => null);
|
|
107
|
+
if (!previous || JSON.stringify(previous.producer) !== JSON.stringify(producer) || JSON.stringify(previous.sets) !== JSON.stringify(expected))
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
async function commandPackageActions(options) {
|
|
113
|
+
const actions = [];
|
|
114
|
+
const candidates = [];
|
|
115
|
+
for (const declaration of options.plugin.manifest.managedCommands?.packages ?? []) {
|
|
116
|
+
if (!declaration.platforms.includes(options.platform))
|
|
117
|
+
continue;
|
|
118
|
+
const producer = await managedCommandProducerIdentity({ ...options, scope: declaration.scope, producer: { kind: 'package', packageKey: declaration.key } });
|
|
119
|
+
let cache = commandPackagePreparationCaches.get(options.context);
|
|
120
|
+
if (!cache) {
|
|
121
|
+
cache = new Map();
|
|
122
|
+
commandPackagePreparationCaches.set(options.context, cache);
|
|
123
|
+
}
|
|
124
|
+
let preparation = cache.get(getCommandPackageId(producer));
|
|
125
|
+
if (!preparation) {
|
|
126
|
+
preparation = (async () => {
|
|
127
|
+
const prepared = await prepareManagedCommandPackage({ ...options, packageKey: declaration.key, scope: declaration.scope });
|
|
128
|
+
try {
|
|
129
|
+
const files = [];
|
|
130
|
+
async function visit(directory, prefix) {
|
|
131
|
+
for (const entry of await fs.readdir(directory, { withFileTypes: true })) {
|
|
132
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
133
|
+
const absolute = path.join(directory, entry.name);
|
|
134
|
+
if (entry.isDirectory())
|
|
135
|
+
await visit(absolute, relativePath);
|
|
136
|
+
else {
|
|
137
|
+
const content = await fs.readFile(absolute);
|
|
138
|
+
files.push({ relativePath, contentBase64: content.toString('base64'), contentDigest: `sha256:${crypto.createHash('sha256').update(content).digest('hex')}`, mode: (await fs.stat(absolute)).mode & 0o777 });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
await visit(prepared.resourceRoot, '');
|
|
143
|
+
files.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
144
|
+
return { action: { kind: 'syncManagedCommandPackage', pluginId: pluginActionId(options.plugin), scope: declaration.scope, producer: prepared.producer, sets: prepared.sets, files, contentDigest: `sha256:${crypto.createHash('sha256').update(JSON.stringify(files)).digest('hex')}` }, candidate: { producer: prepared.producer, sets: prepared.sets } };
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
await prepared.cleanup();
|
|
148
|
+
}
|
|
149
|
+
})();
|
|
150
|
+
cache.set(getCommandPackageId(producer), preparation);
|
|
151
|
+
}
|
|
152
|
+
const prepared = await preparation;
|
|
153
|
+
actions.push(prepared.action);
|
|
154
|
+
candidates.push(prepared.candidate);
|
|
155
|
+
}
|
|
156
|
+
return { actions, candidates };
|
|
157
|
+
}
|
|
158
|
+
async function prepareSkillCommandCandidate(options) {
|
|
159
|
+
const sets = options.sets ?? commandSetsForProducer(options);
|
|
160
|
+
if (sets.length === 0)
|
|
161
|
+
return undefined;
|
|
162
|
+
const producer = await managedCommandProducerIdentity(options);
|
|
163
|
+
if (Object.keys(options.tree.files).length === 0)
|
|
164
|
+
return { producer, sets: sets.map((set) => ({ ...set, state: 'disabled', entries: [] })) };
|
|
165
|
+
if (Object.hasOwn(options.tree.files, COMMAND_PRODUCER_RECORD_FILE))
|
|
166
|
+
throw new Error('commandProducerReservedResource: plugin cannot provide the command producer record.');
|
|
167
|
+
for (const set of sets)
|
|
168
|
+
for (const entry of set.entries) {
|
|
169
|
+
if (!options.tree.files[entry.entry])
|
|
170
|
+
throw new Error(`commandResourceInvalid: command entry ${entry.entry} is missing from the final Skill.`);
|
|
171
|
+
if (process.platform !== 'win32' && entry.runner === 'executable' && options.tree.fileModes?.[entry.entry] !== 0o755)
|
|
172
|
+
throw new Error(`commandRunnerUnsupported: command entry ${entry.entry} has no executable source file.`);
|
|
173
|
+
}
|
|
174
|
+
options.tree.files[COMMAND_PRODUCER_RECORD_FILE] = Buffer.from(`${JSON.stringify({ schemaVersion: 1, producer, sets }, null, 2)}\n`);
|
|
175
|
+
return { producer, sets };
|
|
176
|
+
}
|
|
177
|
+
async function prepareStaticNativeSkillActions(options) {
|
|
178
|
+
if (!options.plugin.manifest.skills)
|
|
179
|
+
return { actions: [], candidates: [] };
|
|
180
|
+
if (!options.plugin.manifest.requires?.aw.capabilities?.includes('aw.managed-commands.v1'))
|
|
181
|
+
return { actions: [], candidates: [] };
|
|
182
|
+
const scope = 'user';
|
|
183
|
+
const root = path.join(options.plugin.rootDir, options.platform, options.plugin.manifest.skills.replace(/^\.\//u, ''));
|
|
184
|
+
const trees = [];
|
|
185
|
+
const candidates = [];
|
|
186
|
+
let artifacts = [...options.artifacts];
|
|
187
|
+
const missingTargets = getManagedCommandArtifactTargets(options.plugin.manifest).filter((target) => {
|
|
188
|
+
const producer = target.producer;
|
|
189
|
+
return producer.kind === 'static-skill' && target.platform === options.platform && !artifacts.some((artifact) => artifact.id === target.id) && !options.commandCandidates.some((candidate) => candidate.producer.kind === 'static-skill' && candidate.producer.key === producer.skillKey && candidate.producer.platform === options.platform && candidate.producer.scope === scope);
|
|
190
|
+
});
|
|
191
|
+
if (missingTargets.length) {
|
|
192
|
+
const rendered = await runArtifactRenderer({ plugin: options.plugin, event: 'install', platform: options.platform, scope, configScope: scope, cwd: options.cwd, commandEnv: options.env, requestedArtifactIds: missingTargets.map((target) => target.id) });
|
|
193
|
+
artifacts.push(...validateArtifactPlan({ manifest: options.plugin.manifest, plan: rendered.plan, platform: options.platform, scope, requestedArtifactIds: rendered.context.requestedArtifactIds, renderConfig: rendered.context.config, rendererDigest: rendered.target.rendererDigest, manifestDigest: rendered.target.manifestDigest }).artifacts);
|
|
194
|
+
}
|
|
195
|
+
for (const entry of await fs.readdir(root, { withFileTypes: true })) {
|
|
196
|
+
if (!entry.isDirectory())
|
|
197
|
+
continue;
|
|
198
|
+
const tree = await readDynamicSkillTree(path.join(root, entry.name), `${options.platform}.skill`);
|
|
199
|
+
if (!tree)
|
|
200
|
+
continue;
|
|
201
|
+
const previous = options.commandCandidates.find((candidate) => candidate.producer.kind === 'static-skill' && candidate.producer.key === tree.skillKey && candidate.producer.platform === options.platform && candidate.producer.scope === scope);
|
|
202
|
+
const candidate = await prepareSkillCommandCandidate({ ...options, scope, producer: { kind: 'static-skill', platform: options.platform, skillKey: tree.skillKey }, tree, artifacts, ...(previous ? { sets: previous.sets } : {}) });
|
|
203
|
+
if (candidate)
|
|
204
|
+
candidates.push(candidate);
|
|
205
|
+
trees.push(tree);
|
|
206
|
+
}
|
|
207
|
+
const resolver = await createManagedCommandReferenceResolver({ ...options, scope, candidates: [...options.commandCandidates, ...candidates] });
|
|
208
|
+
const actions = [];
|
|
209
|
+
for (const tree of trees) {
|
|
210
|
+
const hasCommandReferences = Object.entries(tree.files).some(([relative, content]) => relative.endsWith('.md') && content.includes('{{aw.command:'));
|
|
211
|
+
const hasCommandProducer = Object.hasOwn(tree.files, COMMAND_PRODUCER_RECORD_FILE);
|
|
212
|
+
if (!hasCommandReferences && !hasCommandProducer)
|
|
213
|
+
continue;
|
|
214
|
+
resolveSkillManagedCommandReferences(tree, resolver);
|
|
215
|
+
const files = Object.entries(tree.files).map(([relativePath, content]) => ({ relativePath, contentBase64: content.toString('base64'), contentDigest: `sha256:${crypto.createHash('sha256').update(content).digest('hex')}`, ...(tree.fileModes?.[relativePath] ? { mode: tree.fileModes[relativePath] } : {}) })).sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
216
|
+
actions.push({ kind: 'materializeNativeSkill', pluginId: pluginActionId(options.plugin), platform: options.platform, scope, skillKey: tree.skillKey, files, contentDigest: `sha256:${crypto.createHash('sha256').update(JSON.stringify(files)).digest('hex')}` });
|
|
217
|
+
}
|
|
218
|
+
return { actions, candidates };
|
|
219
|
+
}
|
|
64
220
|
export class CodexActivationPostcheckError extends Error {
|
|
65
221
|
code = 'codexActivationPostcheckFailed';
|
|
66
222
|
codexPluginIds;
|
|
@@ -356,6 +512,7 @@ function pristineTreeDescriptor(tree) {
|
|
|
356
512
|
.map(([relativePath, content]) => ({
|
|
357
513
|
relativePath,
|
|
358
514
|
contentDigest: `sha256:${crypto.createHash('sha256').update(content).digest('hex')}`,
|
|
515
|
+
...(tree.fileModes?.[relativePath] ? { mode: tree.fileModes[relativePath] } : {}),
|
|
359
516
|
}));
|
|
360
517
|
}
|
|
361
518
|
function pluginActionId(plugin) {
|
|
@@ -367,6 +524,7 @@ function skillWriteAction(options) {
|
|
|
367
524
|
relativePath,
|
|
368
525
|
contentBase64: content.toString('base64'),
|
|
369
526
|
contentDigest: `sha256:${crypto.createHash('sha256').update(content).digest('hex')}`,
|
|
527
|
+
...(options.tree.fileModes?.[relativePath] ? { mode: options.tree.fileModes[relativePath] } : {}),
|
|
370
528
|
})),
|
|
371
529
|
{
|
|
372
530
|
relativePath: '.agent-workflows-owner.json',
|
|
@@ -455,6 +613,9 @@ function mergePermissionStates(values) {
|
|
|
455
613
|
allow: [...new Set(values.flatMap((value) => value?.allow ?? []))],
|
|
456
614
|
ask: [...new Set(values.flatMap((value) => value?.ask ?? []))],
|
|
457
615
|
deny: [...new Set(values.flatMap((value) => value?.deny ?? []))],
|
|
616
|
+
...(values.some((value) => value?.excludedCommands !== undefined) ? {
|
|
617
|
+
excludedCommands: [...new Set(values.flatMap((value) => value?.excludedCommands ?? []))],
|
|
618
|
+
} : {}),
|
|
458
619
|
};
|
|
459
620
|
}
|
|
460
621
|
function subtractPermissionState(value, generated) {
|
|
@@ -467,6 +628,9 @@ function subtractPermissionState(value, generated) {
|
|
|
467
628
|
allow: (value?.allow ?? []).filter((entry) => !remove.allow.has(entry)),
|
|
468
629
|
ask: (value?.ask ?? []).filter((entry) => !remove.ask.has(entry)),
|
|
469
630
|
deny: (value?.deny ?? []).filter((entry) => !remove.deny.has(entry)),
|
|
631
|
+
...(value?.excludedCommands ? {
|
|
632
|
+
excludedCommands: value.excludedCommands.filter((entry) => !generated.excludedCommands?.includes(entry)),
|
|
633
|
+
} : {}),
|
|
470
634
|
};
|
|
471
635
|
}
|
|
472
636
|
function intersectPermissionState(value, generated) {
|
|
@@ -479,11 +643,17 @@ function intersectPermissionState(value, generated) {
|
|
|
479
643
|
allow: (value?.allow ?? []).filter((entry) => keep.allow.has(entry)),
|
|
480
644
|
ask: (value?.ask ?? []).filter((entry) => keep.ask.has(entry)),
|
|
481
645
|
deny: (value?.deny ?? []).filter((entry) => keep.deny.has(entry)),
|
|
646
|
+
...(value?.excludedCommands ? {
|
|
647
|
+
excludedCommands: value.excludedCommands.filter((entry) => generated.excludedCommands?.includes(entry)),
|
|
648
|
+
} : {}),
|
|
482
649
|
};
|
|
483
650
|
}
|
|
484
651
|
function rebuildClaudePermissions(settings, ownerKey, desired) {
|
|
485
652
|
const previousGenerated = mergePermissionStates(Object.values(settings.awGenerated?.permissions ?? {}));
|
|
486
|
-
const currentPermissions = mergePermissionStates([
|
|
653
|
+
const currentPermissions = mergePermissionStates([{
|
|
654
|
+
...settings.permissions,
|
|
655
|
+
...(settings.sandbox?.excludedCommands ? { excludedCommands: settings.sandbox.excludedCommands } : {}),
|
|
656
|
+
}]);
|
|
487
657
|
const manual = mergePermissionStates([
|
|
488
658
|
subtractPermissionState(currentPermissions, previousGenerated),
|
|
489
659
|
intersectPermissionState(intersectPermissionState(settings.awGenerated?.manualPermissions, previousGenerated), currentPermissions),
|
|
@@ -497,16 +667,22 @@ function rebuildClaudePermissions(settings, ownerKey, desired) {
|
|
|
497
667
|
if (Object.keys(settings.awGenerated.permissions).length === 0) {
|
|
498
668
|
delete settings.awGenerated.permissions;
|
|
499
669
|
}
|
|
500
|
-
if (manual.allow.length || manual.ask.length || manual.deny.length) {
|
|
670
|
+
if (manual.allow.length || manual.ask.length || manual.deny.length || manual.excludedCommands?.length) {
|
|
501
671
|
settings.awGenerated.manualPermissions = manual;
|
|
502
672
|
}
|
|
503
673
|
else {
|
|
504
674
|
delete settings.awGenerated.manualPermissions;
|
|
505
675
|
}
|
|
506
|
-
|
|
676
|
+
const combined = mergePermissionStates([
|
|
507
677
|
manual,
|
|
508
678
|
...Object.values(settings.awGenerated.permissions ?? {}),
|
|
509
679
|
]);
|
|
680
|
+
settings.permissions = {
|
|
681
|
+
...settings.permissions, allow: combined.allow, ask: combined.ask, deny: combined.deny,
|
|
682
|
+
};
|
|
683
|
+
if (combined.excludedCommands !== undefined) {
|
|
684
|
+
settings.sandbox = { ...settings.sandbox, excludedCommands: combined.excludedCommands };
|
|
685
|
+
}
|
|
510
686
|
if (settings.awGenerated.permissions === undefined
|
|
511
687
|
&& settings.awGenerated.manualPermissions === undefined
|
|
512
688
|
&& settings.awGenerated.hooks === undefined
|
|
@@ -946,14 +1122,17 @@ function renderCodexRole(options) {
|
|
|
946
1122
|
].join('\n');
|
|
947
1123
|
}
|
|
948
1124
|
async function resolveDynamicArtifacts(options) {
|
|
949
|
-
const
|
|
1125
|
+
const preparedArtifacts = options.platform === 'codex' && options.context
|
|
1126
|
+
? await prepareStaticCommandArtifacts({ ...options, context: options.context }) : [];
|
|
1127
|
+
const preparedIds = new Set(preparedArtifacts.map((artifact) => artifact.id));
|
|
1128
|
+
const requestBatches = (options.requestedArtifactIds
|
|
950
1129
|
? [options.requestedArtifactIds]
|
|
951
1130
|
: resolveAwDeliveredArtifactRequestBatches({
|
|
952
1131
|
manifest: options.plugin.manifest,
|
|
953
1132
|
platform: options.platform,
|
|
954
1133
|
scope: options.scope,
|
|
955
|
-
});
|
|
956
|
-
if (requestBatches.length === 0) {
|
|
1134
|
+
})).map((batch) => batch.filter((id) => !preparedIds.has(id))).filter((batch) => batch.length > 0);
|
|
1135
|
+
if (requestBatches.length === 0 && preparedArtifacts.length === 0) {
|
|
957
1136
|
return { artifacts: [], sourceDirs: () => [], sensitiveValues: [] };
|
|
958
1137
|
}
|
|
959
1138
|
const previousArtifacts = [...options.previousArtifacts];
|
|
@@ -981,7 +1160,7 @@ async function resolveDynamicArtifacts(options) {
|
|
|
981
1160
|
cwd: options.cwd,
|
|
982
1161
|
env: options.env,
|
|
983
1162
|
});
|
|
984
|
-
const artifacts = [];
|
|
1163
|
+
const artifacts = [...preparedArtifacts];
|
|
985
1164
|
const sourceDirsByArtifact = new Map();
|
|
986
1165
|
for (const render of renders) {
|
|
987
1166
|
artifacts.push(...validateArtifactPlan({
|
|
@@ -1029,7 +1208,7 @@ function dynamicSkillWrite(options) {
|
|
|
1029
1208
|
id: options.artifact.id,
|
|
1030
1209
|
target: options.artifact.target,
|
|
1031
1210
|
state: options.artifact.state,
|
|
1032
|
-
...(options.artifact.disabledReason
|
|
1211
|
+
...('disabledReason' in options.artifact && options.artifact.disabledReason
|
|
1033
1212
|
? { disabledReason: options.artifact.disabledReason }
|
|
1034
1213
|
: {}),
|
|
1035
1214
|
refreshKeys: [...options.artifact.refreshKeys],
|
|
@@ -1053,28 +1232,6 @@ function dynamicSkillWrite(options) {
|
|
|
1053
1232
|
} : {}),
|
|
1054
1233
|
};
|
|
1055
1234
|
}
|
|
1056
|
-
async function resolveCodexSkillsRelativeRoot(plugin, marketplaceFallbackRoot) {
|
|
1057
|
-
const manifestPaths = [
|
|
1058
|
-
path.join(plugin.rootDir, '.codex-plugin', 'plugin.json'),
|
|
1059
|
-
...(marketplaceFallbackRoot
|
|
1060
|
-
? [path.join(marketplaceFallbackRoot, '.codex-plugin', 'plugin.json')]
|
|
1061
|
-
: []),
|
|
1062
|
-
];
|
|
1063
|
-
let manifest = null;
|
|
1064
|
-
for (const manifestPath of manifestPaths) {
|
|
1065
|
-
manifest = await fs.readJson(manifestPath).catch(() => null);
|
|
1066
|
-
if (manifest)
|
|
1067
|
-
break;
|
|
1068
|
-
}
|
|
1069
|
-
const raw = typeof manifest?.skills === 'string' && manifest.skills.trim() !== ''
|
|
1070
|
-
? manifest.skills
|
|
1071
|
-
: plugin.manifest.skills ?? './codex/skills';
|
|
1072
|
-
const normalized = path.posix.normalize(raw.replace(/^\.\//u, ''));
|
|
1073
|
-
if (normalized === '..' || normalized.startsWith('../') || path.isAbsolute(normalized)) {
|
|
1074
|
-
throw new Error(`Codex plugin skills root 越界:${raw}`);
|
|
1075
|
-
}
|
|
1076
|
-
return normalized;
|
|
1077
|
-
}
|
|
1078
1235
|
async function buildClaudeWrites(options) {
|
|
1079
1236
|
const paths = resolveClaudePaths(options.cwd, options.scope, options.env);
|
|
1080
1237
|
const writes = [];
|
|
@@ -1083,8 +1240,9 @@ async function buildClaudeWrites(options) {
|
|
|
1083
1240
|
platform: 'claude',
|
|
1084
1241
|
previousArtifacts: options.historicalArtifacts,
|
|
1085
1242
|
});
|
|
1243
|
+
const commandCandidates = [...(options.commandCandidates ?? [])];
|
|
1244
|
+
const preparedSkills = [];
|
|
1086
1245
|
for (const artifact of dynamic.artifacts.filter((entry) => entry.target === 'claude.skill')) {
|
|
1087
|
-
const skillKey = artifact.skillKey;
|
|
1088
1246
|
const tree = await createDynamicSkillExpectedTree({
|
|
1089
1247
|
artifact,
|
|
1090
1248
|
sourceDirs: dynamic.sourceDirs(artifact),
|
|
@@ -1093,6 +1251,18 @@ async function buildClaudeWrites(options) {
|
|
|
1093
1251
|
sourceRequirementsReceiptPolicy: 'allow',
|
|
1094
1252
|
});
|
|
1095
1253
|
await injectInstalledRuntimeConfig(tree, dynamic.runtimeConfigSnapshots?.[artifact.id]);
|
|
1254
|
+
const candidate = await prepareSkillCommandCandidate({ ...options, producer: { kind: 'artifact', artifactId: artifact.id }, platform: 'claude', tree, artifacts: dynamic.artifacts });
|
|
1255
|
+
if (candidate)
|
|
1256
|
+
commandCandidates.push(candidate);
|
|
1257
|
+
preparedSkills.push({ artifact, tree });
|
|
1258
|
+
}
|
|
1259
|
+
const staticSkills = await prepareStaticNativeSkillActions({ ...options, platform: 'claude', artifacts: dynamic.artifacts, commandCandidates });
|
|
1260
|
+
commandCandidates.push(...staticSkills.candidates);
|
|
1261
|
+
options.nativeSkillActions?.push(...staticSkills.actions);
|
|
1262
|
+
const commandResolver = await createManagedCommandReferenceResolver({ ...options, platform: 'claude', candidates: commandCandidates });
|
|
1263
|
+
for (const { artifact, tree } of preparedSkills) {
|
|
1264
|
+
const skillKey = artifact.skillKey;
|
|
1265
|
+
resolveSkillManagedCommandReferences(tree, commandResolver);
|
|
1096
1266
|
assertNoSensitiveConfigInFiles(withoutVerifiedRequirementsReceipt(options.plugin, tree.files), dynamic.sensitiveValues, 'artifactRendererSensitiveConfigExposed: Final dynamic Skill contains sensitive config.');
|
|
1097
1267
|
writes.push(dynamicSkillWrite({
|
|
1098
1268
|
...options,
|
|
@@ -1121,7 +1291,7 @@ async function buildClaudeWrites(options) {
|
|
|
1121
1291
|
scope: options.scope,
|
|
1122
1292
|
settingsPath: paths.settingsPath,
|
|
1123
1293
|
});
|
|
1124
|
-
const desiredPermissions = mergeClaudePermissions(permissions);
|
|
1294
|
+
const desiredPermissions = mergeClaudePermissions(permissions, commandResolver);
|
|
1125
1295
|
const mutation = { kind: 'claude-permissions', permissions: desiredPermissions };
|
|
1126
1296
|
writes.push({
|
|
1127
1297
|
artifact: { path: paths.settingsPath, kind: 'permission', platform: 'claude' },
|
|
@@ -1151,6 +1321,8 @@ async function buildCodexWrites(options) {
|
|
|
1151
1321
|
const dynamicInstructions = dynamic.artifacts.filter((entry) => entry.target === 'codex.developerInstructionsBlock');
|
|
1152
1322
|
const dynamicRoles = dynamic.artifacts.filter((entry) => entry.target === 'codex.agentRole');
|
|
1153
1323
|
const dynamicSkills = dynamic.artifacts.filter((entry) => entry.target === 'codex.skill');
|
|
1324
|
+
const commandCandidates = [...(options.commandCandidates ?? [])];
|
|
1325
|
+
const preparedSkills = [];
|
|
1154
1326
|
const repoRoot = await resolveRepoRoot(options.cwd);
|
|
1155
1327
|
const skillRoot = options.scope === 'user'
|
|
1156
1328
|
? path.join(getCodexHome(options.env), 'skills')
|
|
@@ -1164,6 +1336,17 @@ async function buildCodexWrites(options) {
|
|
|
1164
1336
|
sourceRequirementsReceiptPolicy: 'allow',
|
|
1165
1337
|
});
|
|
1166
1338
|
await injectInstalledRuntimeConfig(tree, dynamic.runtimeConfigSnapshots?.[artifact.id]);
|
|
1339
|
+
const candidate = await prepareSkillCommandCandidate({ ...options, producer: { kind: 'artifact', artifactId: artifact.id }, platform: 'codex', tree, artifacts: dynamic.artifacts });
|
|
1340
|
+
if (candidate)
|
|
1341
|
+
commandCandidates.push(candidate);
|
|
1342
|
+
preparedSkills.push({ artifact, tree });
|
|
1343
|
+
}
|
|
1344
|
+
const staticSkills = await prepareStaticNativeSkillActions({ ...options, platform: 'codex', artifacts: dynamic.artifacts, commandCandidates });
|
|
1345
|
+
commandCandidates.push(...staticSkills.candidates);
|
|
1346
|
+
options.nativeSkillActions?.push(...staticSkills.actions);
|
|
1347
|
+
const commandResolver = await createManagedCommandReferenceResolver({ ...options, platform: 'codex', candidates: commandCandidates });
|
|
1348
|
+
for (const { artifact, tree } of preparedSkills) {
|
|
1349
|
+
resolveSkillManagedCommandReferences(tree, commandResolver);
|
|
1167
1350
|
assertNoSensitiveConfigInFiles(withoutVerifiedRequirementsReceipt(options.plugin, tree.files), dynamic.sensitiveValues, 'artifactRendererSensitiveConfigExposed: Final dynamic Skill contains sensitive config.');
|
|
1168
1351
|
writes.push(dynamicSkillWrite({
|
|
1169
1352
|
...options,
|
|
@@ -1320,7 +1503,7 @@ async function buildCodexWrites(options) {
|
|
|
1320
1503
|
} : {}),
|
|
1321
1504
|
});
|
|
1322
1505
|
}
|
|
1323
|
-
for (const artifact of generateCodexPermissionArtifacts(options.plugin.manifest.name, resolvePlatformEntries(options.plugin.manifest.extensions?.permissions, 'codex'))) {
|
|
1506
|
+
for (const artifact of generateCodexPermissionArtifacts(options.plugin.manifest.name, resolvePlatformEntries(options.plugin.manifest.extensions?.permissions, 'codex'), commandResolver)) {
|
|
1324
1507
|
writes.push(generatedTextWrite({
|
|
1325
1508
|
...options,
|
|
1326
1509
|
platform: 'codex',
|
|
@@ -1525,15 +1708,16 @@ async function buildInspectionWrites(options) {
|
|
|
1525
1708
|
});
|
|
1526
1709
|
}
|
|
1527
1710
|
}
|
|
1528
|
-
for (const
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1711
|
+
for (const permission of resolvePlatformEntries(options.plugin.manifest.extensions?.permissions, 'codex')) {
|
|
1712
|
+
const artifactKey = buildGeneratedName(options.plugin.manifest.name, permission.name);
|
|
1713
|
+
const artifactPath = path.join(paths.configDir, 'rules', `${artifactKey}.rules`);
|
|
1714
|
+
writes.push({
|
|
1715
|
+
artifact: { path: artifactPath, kind: 'permission', platform: 'codex', artifactKey },
|
|
1716
|
+
component: pathComponent({
|
|
1717
|
+
kind: 'owned-text-file', path: artifactPath, allowedRoot: paths.configDir,
|
|
1718
|
+
identity: ownerIdentity(options.plugin.manifest.name, artifactKey, 'codex', options.scope),
|
|
1719
|
+
}),
|
|
1720
|
+
});
|
|
1537
1721
|
}
|
|
1538
1722
|
for (const artifact of generateAgentArtifacts(options.plugin.manifest.name, resolvePlatformEntries(options.plugin.manifest.extensions?.agents, 'codex'), 'codex')) {
|
|
1539
1723
|
writes.push(generatedTextWrite({
|
|
@@ -2713,9 +2897,20 @@ async function prepareCodexNativeRender(options) {
|
|
|
2713
2897
|
.filter((target) => !requested || requested.has(target.id));
|
|
2714
2898
|
if (targets.length === 0)
|
|
2715
2899
|
return null;
|
|
2900
|
+
let commandCandidates = [...(options.commandCandidates ?? [])];
|
|
2901
|
+
if (!options.commandCandidates) {
|
|
2902
|
+
for (const declaration of options.plugin.manifest.managedCommands?.packages ?? []) {
|
|
2903
|
+
if (declaration.scope !== 'user' || !declaration.platforms.includes('codex'))
|
|
2904
|
+
continue;
|
|
2905
|
+
const prepared = await prepareManagedCommandPackage({ ...options, packageKey: declaration.key, scope: 'user', cwd: options.plugin.rootDir });
|
|
2906
|
+
commandCandidates.push({ producer: prepared.producer, sets: prepared.sets });
|
|
2907
|
+
await prepared.cleanup();
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2716
2910
|
const byScope = new Map();
|
|
2717
2911
|
for (const target of targets) {
|
|
2718
|
-
const
|
|
2912
|
+
const ownsCommandSet = options.plugin.manifest.managedCommands?.sets.some((set) => set.producer.kind === 'artifact' && set.producer.artifactId === target.id);
|
|
2913
|
+
const requestScope = ownsCommandSet || target.scopes.includes('user') ? 'user' : 'project';
|
|
2719
2914
|
const group = byScope.get(requestScope) ?? [];
|
|
2720
2915
|
group.push(target);
|
|
2721
2916
|
byScope.set(requestScope, group);
|
|
@@ -2739,7 +2934,10 @@ async function prepareCodexNativeRender(options) {
|
|
|
2739
2934
|
cwd: options.plugin.rootDir,
|
|
2740
2935
|
commandEnv: options.env,
|
|
2741
2936
|
previousArtifacts: [],
|
|
2742
|
-
requestedArtifactIds: group.map((target) => target.id),
|
|
2937
|
+
requestedArtifactIds: [...group.map((target) => target.id), ...getManagedCommandArtifactTargets(options.plugin.manifest).filter((target) => {
|
|
2938
|
+
const producer = target.producer;
|
|
2939
|
+
return producer.kind === 'artifact' && group.some((skill) => skill.id === producer.artifactId);
|
|
2940
|
+
}).map((target) => target.id)],
|
|
2743
2941
|
scriptTimeoutMs: options.scriptTimeoutMs,
|
|
2744
2942
|
});
|
|
2745
2943
|
rendererDigest = rendered.target.rendererDigest;
|
|
@@ -2754,6 +2952,8 @@ async function prepareCodexNativeRender(options) {
|
|
|
2754
2952
|
manifestDigest: rendered.target.manifestDigest,
|
|
2755
2953
|
});
|
|
2756
2954
|
for (const artifact of validated.artifacts) {
|
|
2955
|
+
if (artifact.target === 'aw.command-set')
|
|
2956
|
+
continue;
|
|
2757
2957
|
if (artifact.target !== 'codex.skill') {
|
|
2758
2958
|
throw new Error(`Codex native renderer returned non-Skill artifact "${artifact.id}".`);
|
|
2759
2959
|
}
|
|
@@ -2766,6 +2966,9 @@ async function prepareCodexNativeRender(options) {
|
|
|
2766
2966
|
sourceRequirementsReceiptPolicy: 'allow',
|
|
2767
2967
|
});
|
|
2768
2968
|
await injectInstalledRuntimeConfig(tree, rendered.runtimeConfigSnapshots[artifact.id], { nativeReceiptPath });
|
|
2969
|
+
const candidate = await prepareSkillCommandCandidate({ plugin: options.plugin, producer: { kind: 'artifact', artifactId: artifact.id }, platform: 'codex', scope: 'user', cwd: options.plugin.rootDir, tree, artifacts: validated.artifacts });
|
|
2970
|
+
if (candidate)
|
|
2971
|
+
commandCandidates.push(candidate);
|
|
2769
2972
|
assertNoSensitiveConfigInFiles(withoutVerifiedRequirementsReceipt(options.plugin, tree.files), collectSensitivePluginConfigValues(options.plugin.manifest, rendered.context.config.values), 'artifactRendererSensitiveConfigExposed: Final dynamic Skill contains sensitive config.');
|
|
2770
2973
|
const skillKey = artifact.skillKey;
|
|
2771
2974
|
renderedArtifacts.push({
|
|
@@ -2786,6 +2989,18 @@ async function prepareCodexNativeRender(options) {
|
|
|
2786
2989
|
if (!rendererDigest || renderedArtifacts.length !== targets.length) {
|
|
2787
2990
|
throw new Error('Codex native renderer did not produce the complete declared target set.');
|
|
2788
2991
|
}
|
|
2992
|
+
if (options.prepareCommandReferences)
|
|
2993
|
+
commandCandidates = await options.prepareCommandReferences(renderedArtifacts.map((entry) => entry.tree), commandCandidates);
|
|
2994
|
+
else {
|
|
2995
|
+
const staticSkills = await prepareStaticNativeSkillActions({ plugin: options.plugin, platform: 'codex', scope: 'user', cwd: options.plugin.rootDir, env: options.env, artifacts: [], commandCandidates });
|
|
2996
|
+
commandCandidates.push(...staticSkills.candidates);
|
|
2997
|
+
}
|
|
2998
|
+
const commandResolver = await createManagedCommandReferenceResolver({ plugin: options.plugin, platform: 'codex', scope: 'user', cwd: options.plugin.rootDir, env: options.env, candidates: commandCandidates });
|
|
2999
|
+
for (const rendered of renderedArtifacts) {
|
|
3000
|
+
resolveSkillManagedCommandReferences(rendered.tree, commandResolver);
|
|
3001
|
+
const receiptTarget = receiptTargets.find((target) => target.id === rendered.artifact.id);
|
|
3002
|
+
receiptTarget.artifactDigest = createCodexSkillStagingTreeContentDigest(rendered.tree);
|
|
3003
|
+
}
|
|
2789
3004
|
const logicalPluginId = options.plugin.pluginId
|
|
2790
3005
|
?? (options.plugin.sourceId ? `${options.plugin.sourceId}/${options.plugin.manifest.name}` : undefined);
|
|
2791
3006
|
if (!logicalPluginId || !options.plugin.sourceIdentity) {
|
|
@@ -2799,6 +3014,7 @@ async function prepareCodexNativeRender(options) {
|
|
|
2799
3014
|
targets: receiptTargets,
|
|
2800
3015
|
});
|
|
2801
3016
|
return {
|
|
3017
|
+
commandCandidates,
|
|
2802
3018
|
logicalPluginId,
|
|
2803
3019
|
sourceIdentity: options.plugin.sourceIdentity,
|
|
2804
3020
|
rendererDigest,
|
|
@@ -2818,6 +3034,7 @@ function prepareCodexNativeRenderCached(options) {
|
|
|
2818
3034
|
plugin: options.plugin,
|
|
2819
3035
|
env: options.context.env,
|
|
2820
3036
|
scriptTimeoutMs: options.scriptTimeoutMs,
|
|
3037
|
+
commandCandidates: options.context.managedCommandCandidates?.get(pluginActionId(options.plugin)),
|
|
2821
3038
|
});
|
|
2822
3039
|
cache.set(options.plugin, preparation);
|
|
2823
3040
|
}
|
|
@@ -2875,8 +3092,12 @@ export async function materializeReviewNativeSkills(options) {
|
|
|
2875
3092
|
if (options.skills.length === 0) {
|
|
2876
3093
|
throw new Error('Review materialization requires at least one selected Skill.');
|
|
2877
3094
|
}
|
|
3095
|
+
const { prepareIsolatedSkillCommandReferences } = await import('../review/skill-materialization.js');
|
|
2878
3096
|
const skillsRoot = path.join(options.outputRoot, 'skills');
|
|
2879
3097
|
await fs.ensureDir(skillsRoot);
|
|
3098
|
+
let commandCandidates = [];
|
|
3099
|
+
const staticTrees = [];
|
|
3100
|
+
const directTrees = [];
|
|
2880
3101
|
const declarations = options.plugin.manifest.artifacts?.targets ?? [];
|
|
2881
3102
|
const dynamicSelections = options.skills.filter((skill) => skill.targetId !== null);
|
|
2882
3103
|
const selectedDeclarations = dynamicSelections.map((selection) => {
|
|
@@ -2904,6 +3125,9 @@ export async function materializeReviewNativeSkills(options) {
|
|
|
2904
3125
|
errorOnExist: true,
|
|
2905
3126
|
dereference: false,
|
|
2906
3127
|
});
|
|
3128
|
+
staticTrees.push(await validateSkillDirectory(destination, `${options.platform}.skill`, {
|
|
3129
|
+
requirementsReceiptPolicy: 'allow',
|
|
3130
|
+
}));
|
|
2907
3131
|
}
|
|
2908
3132
|
const directTargetIds = requiredDeclarations
|
|
2909
3133
|
.filter((target) => target.delivery?.mode !== 'codex-native')
|
|
@@ -2917,10 +3141,18 @@ export async function materializeReviewNativeSkills(options) {
|
|
|
2917
3141
|
env: options.env,
|
|
2918
3142
|
event: 'install',
|
|
2919
3143
|
previousArtifacts: [],
|
|
2920
|
-
requestedArtifactIds:
|
|
3144
|
+
requestedArtifactIds: [
|
|
3145
|
+
...directTargetIds,
|
|
3146
|
+
...getManagedCommandArtifactTargets(options.plugin.manifest)
|
|
3147
|
+
.filter((target) => target.producer.kind === 'artifact'
|
|
3148
|
+
&& directTargetIds.includes(target.producer.artifactId))
|
|
3149
|
+
.map((target) => target.id),
|
|
3150
|
+
],
|
|
2921
3151
|
scriptTimeoutMs: options.scriptTimeoutMs,
|
|
2922
3152
|
});
|
|
2923
3153
|
for (const artifact of dynamic.artifacts) {
|
|
3154
|
+
if (artifact.target === 'aw.command-set')
|
|
3155
|
+
continue;
|
|
2924
3156
|
if (artifact.state !== 'present') {
|
|
2925
3157
|
throw new Error(`Selected Skill target "${artifact.id}" did not produce a present Skill.`);
|
|
2926
3158
|
}
|
|
@@ -2951,9 +3183,22 @@ export async function materializeReviewNativeSkills(options) {
|
|
|
2951
3183
|
}
|
|
2952
3184
|
await injectInstalledRuntimeConfig(tree, dynamic.runtimeConfigSnapshots?.[artifact.id]);
|
|
2953
3185
|
assertNoSensitiveConfigInFiles(withoutVerifiedRequirementsReceipt(options.plugin, tree.files), dynamic.sensitiveValues, 'artifactRendererSensitiveConfigExposed: Final dynamic Skill contains sensitive config.');
|
|
2954
|
-
await
|
|
3186
|
+
const candidate = await prepareSkillCommandCandidate({
|
|
3187
|
+
...options, producer: { kind: 'artifact', artifactId: artifact.id },
|
|
3188
|
+
tree, artifacts: dynamic.artifacts,
|
|
3189
|
+
});
|
|
3190
|
+
if (candidate)
|
|
3191
|
+
commandCandidates.push(candidate);
|
|
3192
|
+
directTrees.push(tree);
|
|
2955
3193
|
}
|
|
2956
3194
|
}
|
|
3195
|
+
const prepareReferences = async (trees, scope, copy, candidates = commandCandidates) => prepareIsolatedSkillCommandReferences({
|
|
3196
|
+
...options, scope, candidates,
|
|
3197
|
+
trees: copy ? trees.map((tree) => ({ ...tree, files: { ...tree.files } })) : trees,
|
|
3198
|
+
});
|
|
3199
|
+
const staticScope = 'user';
|
|
3200
|
+
commandCandidates = await prepareReferences(staticTrees, staticScope, true);
|
|
3201
|
+
commandCandidates = await prepareReferences(directTrees, options.scope, true);
|
|
2957
3202
|
const nativeTargetIds = requiredDeclarations
|
|
2958
3203
|
.filter((target) => target.delivery?.mode === 'codex-native')
|
|
2959
3204
|
.map((target) => target.id);
|
|
@@ -2973,10 +3218,13 @@ export async function materializeReviewNativeSkills(options) {
|
|
|
2973
3218
|
env: options.env,
|
|
2974
3219
|
requestedArtifactIds: nativeTargetIds,
|
|
2975
3220
|
scriptTimeoutMs: options.scriptTimeoutMs,
|
|
3221
|
+
commandCandidates,
|
|
3222
|
+
prepareCommandReferences: (trees, candidates) => prepareReferences(trees, 'user', false, candidates),
|
|
2976
3223
|
});
|
|
2977
3224
|
if (!prepared) {
|
|
2978
3225
|
throw new Error('Selected Codex native Skill targets did not produce a projection.');
|
|
2979
3226
|
}
|
|
3227
|
+
commandCandidates = prepared.commandCandidates;
|
|
2980
3228
|
const staged = await materializeCodexNativeRender({
|
|
2981
3229
|
plugin: options.plugin,
|
|
2982
3230
|
stagingRoot: marketplaceRoot,
|
|
@@ -3007,6 +3255,18 @@ export async function materializeReviewNativeSkills(options) {
|
|
|
3007
3255
|
}
|
|
3008
3256
|
await fs.remove(marketplaceRoot);
|
|
3009
3257
|
}
|
|
3258
|
+
commandCandidates = await prepareReferences(staticTrees, staticScope, false);
|
|
3259
|
+
commandCandidates = await prepareReferences(directTrees, options.scope, false);
|
|
3260
|
+
for (const tree of staticTrees) {
|
|
3261
|
+
for (const [relativePath, content] of Object.entries(tree.files)) {
|
|
3262
|
+
if (relativePath.endsWith('.md')) {
|
|
3263
|
+
await fs.writeFile(path.join(skillsRoot, tree.skillKey, ...relativePath.split('/')), content);
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
for (const tree of directTrees) {
|
|
3268
|
+
await writeDynamicSkillTree(path.join(skillsRoot, tree.skillKey), tree);
|
|
3269
|
+
}
|
|
3010
3270
|
const skills = [];
|
|
3011
3271
|
for (const skill of selectedNames.sort((left, right) => left.localeCompare(right))) {
|
|
3012
3272
|
const skillRoot = path.join(skillsRoot, skill);
|
|
@@ -3020,7 +3280,7 @@ export async function materializeReviewNativeSkills(options) {
|
|
|
3020
3280
|
}
|
|
3021
3281
|
return { rootDir: options.outputRoot, skills };
|
|
3022
3282
|
}
|
|
3023
|
-
async function mirrorCatalog(catalog, env, target, logicalIdentity, prepareStaging, rematerializedPluginIds = new Set()) {
|
|
3283
|
+
async function mirrorCatalog(catalog, env, target, logicalIdentity, prepareStaging, rematerializedPluginIds = new Set(), rematerializedPluginPlatforms) {
|
|
3024
3284
|
if (!target?.sourceId || !target.marketplaceRoot) {
|
|
3025
3285
|
const rootDir = resolveAwHome(env);
|
|
3026
3286
|
await fs.ensureDir(rootDir);
|
|
@@ -3076,6 +3336,7 @@ async function mirrorCatalog(catalog, env, target, logicalIdentity, prepareStagi
|
|
|
3076
3336
|
return false;
|
|
3077
3337
|
throw error;
|
|
3078
3338
|
});
|
|
3339
|
+
const retainedReceipts = [];
|
|
3079
3340
|
if (destinationExists) {
|
|
3080
3341
|
await replayNamespacedCodexSkillOverlays({
|
|
3081
3342
|
sourceRoot: destination,
|
|
@@ -3091,9 +3352,13 @@ async function mirrorCatalog(catalog, env, target, logicalIdentity, prepareStagi
|
|
|
3091
3352
|
sourceIdentity: target.sourceIdentity,
|
|
3092
3353
|
marketplaceName,
|
|
3093
3354
|
rematerializedPluginIds,
|
|
3355
|
+
rematerializedPluginPlatforms,
|
|
3356
|
+
onPreservedPlatformReceipt: (receipt) => retainedReceipts.push(receipt),
|
|
3094
3357
|
});
|
|
3095
3358
|
}
|
|
3096
3359
|
await prepareStaging?.(staging);
|
|
3360
|
+
for (const receipt of retainedReceipts)
|
|
3361
|
+
await refreshPreservedPlatformReceipt(staging, receipt);
|
|
3097
3362
|
const backup = `${destination}.backup-${crypto.randomUUID()}`;
|
|
3098
3363
|
if (destinationExists)
|
|
3099
3364
|
await fs.move(destination, backup);
|
|
@@ -3187,6 +3452,7 @@ async function applyPlannedOperationAction(action) {
|
|
|
3187
3452
|
if (digest !== action.contentDigest)
|
|
3188
3453
|
throw new Error('Skill action content digest mismatch.');
|
|
3189
3454
|
const treeFiles = {};
|
|
3455
|
+
const fileModes = {};
|
|
3190
3456
|
for (const file of files) {
|
|
3191
3457
|
const content = Buffer.from(file.contentBase64, 'base64');
|
|
3192
3458
|
if (`sha256:${crypto.createHash('sha256').update(content).digest('hex')}` !== file.contentDigest) {
|
|
@@ -3194,10 +3460,15 @@ async function applyPlannedOperationAction(action) {
|
|
|
3194
3460
|
}
|
|
3195
3461
|
if (file.relativePath !== '.agent-workflows-owner.json')
|
|
3196
3462
|
treeFiles[file.relativePath] = content;
|
|
3463
|
+
if (file.mode !== undefined && file.mode !== 0o644 && file.mode !== 0o755)
|
|
3464
|
+
throw new Error('Skill action file mode is invalid.');
|
|
3465
|
+
if (file.mode === 0o755 && file.relativePath !== '.agent-workflows-owner.json')
|
|
3466
|
+
fileModes[file.relativePath] = 0o755;
|
|
3197
3467
|
}
|
|
3198
3468
|
await writeDynamicSkillTree(action.path, {
|
|
3199
3469
|
skillKey: action.owner.artifactKey,
|
|
3200
3470
|
files: treeFiles,
|
|
3471
|
+
...(Object.keys(fileModes).length > 0 ? { fileModes } : {}),
|
|
3201
3472
|
}, {
|
|
3202
3473
|
allowedRoot: action.allowedRoot,
|
|
3203
3474
|
identity: action.owner,
|
|
@@ -3265,6 +3536,88 @@ async function applyPlannedOperationAction(action) {
|
|
|
3265
3536
|
}
|
|
3266
3537
|
function createNativeDriver(options) {
|
|
3267
3538
|
const runner = options.context.nativeCliRunner;
|
|
3539
|
+
const preflightCommandBindings = async (targets) => {
|
|
3540
|
+
const producers = [];
|
|
3541
|
+
for (const target of targets)
|
|
3542
|
+
for (const action of target.operationActions ?? []) {
|
|
3543
|
+
if (action.kind === 'syncManagedCommandPackage') {
|
|
3544
|
+
producers.push({ producer: action.producer, sets: action.sets });
|
|
3545
|
+
}
|
|
3546
|
+
else if (action.kind === 'materializeNativeSkill'
|
|
3547
|
+
|| (action.kind === 'writeOwnedArtifact' && action.artifactKind === 'skill-directory')) {
|
|
3548
|
+
const file = action.files?.find((entry) => entry.relativePath === COMMAND_PRODUCER_RECORD_FILE);
|
|
3549
|
+
if (file)
|
|
3550
|
+
producers.push(normalizeInstalledCommandProducer(JSON.parse(Buffer.from(file.contentBase64, 'base64').toString('utf8'))));
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
await assertManagedCommandBindingIdentities({ producers, env: options.context.env });
|
|
3554
|
+
};
|
|
3555
|
+
const revokeAtRoot = async (root, recursive = false, platform) => {
|
|
3556
|
+
const listed = await listManagedCommands(options.context.env);
|
|
3557
|
+
const producers = new Map();
|
|
3558
|
+
for (const command of listed.commands) {
|
|
3559
|
+
if (command.owner.kind !== 'plugin' || !command.resourceRoot)
|
|
3560
|
+
continue;
|
|
3561
|
+
if (platform && command.owner.producer.platform !== platform)
|
|
3562
|
+
continue;
|
|
3563
|
+
const relative = path.relative(path.resolve(root), path.resolve(command.resourceRoot));
|
|
3564
|
+
if (relative !== '' && (!recursive || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)))
|
|
3565
|
+
continue;
|
|
3566
|
+
producers.set(getCommandPackageId(command.owner.producer), command.owner.producer);
|
|
3567
|
+
}
|
|
3568
|
+
for (const producer of producers.values()) {
|
|
3569
|
+
const removedCommandIds = await revokeManagedCommandBindings(producer, options.context.env);
|
|
3570
|
+
options.context.commandResults ??= [];
|
|
3571
|
+
options.context.commandResults.push({ producer, state: 'completed', commandIds: [], removedCommandIds, errors: [], recoveryActions: [] });
|
|
3572
|
+
}
|
|
3573
|
+
};
|
|
3574
|
+
const publishPackages = async (target) => {
|
|
3575
|
+
const cache = options.context.managedCommandProducerCache ??= new Map();
|
|
3576
|
+
for (const action of target.operationActions ?? []) {
|
|
3577
|
+
if (action.kind !== 'syncManagedCommandPackage')
|
|
3578
|
+
continue;
|
|
3579
|
+
const key = getCommandPackageId(action.producer);
|
|
3580
|
+
let publication = cache.get(key);
|
|
3581
|
+
if (!publication) {
|
|
3582
|
+
publication = (async () => {
|
|
3583
|
+
await options.context.reporter.event({ type: 'managed-command:producer', source: { plugin: action.producer.pluginName, scope: action.producer.scope }, data: { packageKey: action.producer.key, phase: 'start' } });
|
|
3584
|
+
const temporary = await fs.mkdtemp(path.join(os.tmpdir(), 'aw-command-publish-'));
|
|
3585
|
+
try {
|
|
3586
|
+
const digest = `sha256:${crypto.createHash('sha256').update(JSON.stringify(action.files)).digest('hex')}`;
|
|
3587
|
+
if (digest !== action.contentDigest)
|
|
3588
|
+
throw new ManagedCommandError('commandResourceInvalid', 'The prepared command package digest changed.');
|
|
3589
|
+
const resourceRoot = path.join(temporary, 'resource');
|
|
3590
|
+
await fs.ensureDir(resourceRoot);
|
|
3591
|
+
for (const file of action.files) {
|
|
3592
|
+
const content = Buffer.from(file.contentBase64, 'base64');
|
|
3593
|
+
if (`sha256:${crypto.createHash('sha256').update(content).digest('hex')}` !== file.contentDigest)
|
|
3594
|
+
throw new ManagedCommandError('commandResourceInvalid', 'The prepared command package file digest changed.');
|
|
3595
|
+
const destination = path.join(resourceRoot, ...file.relativePath.split('/'));
|
|
3596
|
+
await fs.outputFile(destination, content);
|
|
3597
|
+
await fs.chmod(destination, file.mode);
|
|
3598
|
+
}
|
|
3599
|
+
const result = await syncManagedCommandPackage({ producer: action.producer, sourceDirectory: resourceRoot, sets: action.sets, env: options.context.env });
|
|
3600
|
+
await options.context.reporter.event({ type: 'managed-command:producer', source: { plugin: action.producer.pluginName, scope: action.producer.scope }, data: { packageKey: action.producer.key, phase: 'success' } });
|
|
3601
|
+
return { producer: action.producer, state: 'completed', commandIds: result.commandIds, removedCommandIds: result.removedCommandIds, errors: [], recoveryActions: [] };
|
|
3602
|
+
}
|
|
3603
|
+
catch (error) {
|
|
3604
|
+
await options.context.reporter.event({ type: 'managed-command:producer', source: { plugin: action.producer.pluginName, scope: action.producer.scope }, data: { packageKey: action.producer.key, phase: 'failure' } });
|
|
3605
|
+
return { producer: action.producer, state: 'failed', commandIds: [], removedCommandIds: [], errors: [managedCommandDiagnostic(error)], recoveryActions: [`aw command package sync ${action.producer.sourceId}/${action.producer.pluginName} ${action.producer.key} --scope ${action.producer.scope} --yes`] };
|
|
3606
|
+
}
|
|
3607
|
+
finally {
|
|
3608
|
+
await fs.remove(temporary);
|
|
3609
|
+
}
|
|
3610
|
+
})();
|
|
3611
|
+
cache.set(key, publication);
|
|
3612
|
+
}
|
|
3613
|
+
const result = await publication;
|
|
3614
|
+
options.context.commandResults ??= [];
|
|
3615
|
+
if (!options.context.commandResults.some((entry) => getCommandPackageId(entry.producer) === key))
|
|
3616
|
+
options.context.commandResults.push(result);
|
|
3617
|
+
if (result.state !== 'completed')
|
|
3618
|
+
throw new Error('commandPackagePublishFailed: sync the command package and retry the plugin operation.');
|
|
3619
|
+
}
|
|
3620
|
+
};
|
|
3268
3621
|
const currentCodexManager = () => options.context.codexManager;
|
|
3269
3622
|
const nativeGroupPreparations = new Map();
|
|
3270
3623
|
const nativeTargetPreparations = new WeakMap();
|
|
@@ -3279,7 +3632,6 @@ function createNativeDriver(options) {
|
|
|
3279
3632
|
target.identity.sourceIdentity ?? '',
|
|
3280
3633
|
target.identity.marketplaceName ?? MARKETPLACE_NAME,
|
|
3281
3634
|
path.normalize(path.resolve(target.identity.marketplaceRoot ?? resolveAwHome(options.context.env))),
|
|
3282
|
-
target.identity.platform,
|
|
3283
3635
|
].join('\0');
|
|
3284
3636
|
const observationScopeFor = (target) => codexObservationScope(options.context, target.identity.marketplaceName ?? MARKETPLACE_NAME, target.identity.scope);
|
|
3285
3637
|
const groupTargets = (target) => {
|
|
@@ -3318,11 +3670,12 @@ function createNativeDriver(options) {
|
|
|
3318
3670
|
preparation = (async () => {
|
|
3319
3671
|
const members = groupTargets(target);
|
|
3320
3672
|
const materializing = members.filter((candidate) => candidate.writeNativeRequired ?? candidate.requiresNative);
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
const
|
|
3673
|
+
const codexMembers = members.filter((candidate) => candidate.identity.platform === 'codex');
|
|
3674
|
+
if (codexMembers.length) {
|
|
3675
|
+
const observationScope = codexObservationScope(options.context, target.identity.marketplaceName ?? MARKETPLACE_NAME, codexMembers.some((candidate) => candidate.identity.scope === 'project') ? 'project' : 'user');
|
|
3676
|
+
const nativePluginIds = [...new Set(codexMembers.flatMap((candidate) => candidate.ownedComponents.flatMap((component) => component.kind === 'native-plugin' ? [component.pluginId] : [])))].sort();
|
|
3324
3677
|
for (const pluginId of nativePluginIds) {
|
|
3325
|
-
const member =
|
|
3678
|
+
const member = codexMembers.find((candidate) => candidate.ownedComponents.some((component) => component.kind === 'native-plugin' && component.pluginId === pluginId));
|
|
3326
3679
|
const registration = await inspectCodexPluginRegistrationLive({
|
|
3327
3680
|
pluginId,
|
|
3328
3681
|
cwd: member.identity.cwd,
|
|
@@ -3337,6 +3690,7 @@ function createNativeDriver(options) {
|
|
|
3337
3690
|
}
|
|
3338
3691
|
if (materializing.length === 0)
|
|
3339
3692
|
return;
|
|
3693
|
+
await preflightCommandBindings(materializing);
|
|
3340
3694
|
const first = materializing[0];
|
|
3341
3695
|
if (!first.catalog)
|
|
3342
3696
|
throw new Error('Missing rebuild target catalog data.');
|
|
@@ -3353,8 +3707,35 @@ function createNativeDriver(options) {
|
|
|
3353
3707
|
right.identity.scope,
|
|
3354
3708
|
right.identity.cwd,
|
|
3355
3709
|
].join('\0'), 'en'));
|
|
3356
|
-
const
|
|
3710
|
+
const requestedPlatforms = new Map();
|
|
3711
|
+
for (const candidate of materializationTargets) {
|
|
3712
|
+
const pluginId = candidate.identity.pluginId ?? `${candidate.identity.sourceId}/${candidate.identity.pluginName}`;
|
|
3713
|
+
const platforms = requestedPlatforms.get(pluginId) ?? new Set();
|
|
3714
|
+
platforms.add(candidate.identity.platform);
|
|
3715
|
+
requestedPlatforms.set(pluginId, platforms);
|
|
3716
|
+
}
|
|
3717
|
+
const transaction = await mirrorCatalog(first.catalog, options.context.env, first.identity, first.plugin.catalogLogicalIdentity, async (stagingRoot) => {
|
|
3718
|
+
const renderedReceipts = [];
|
|
3357
3719
|
for (const candidate of materializationTargets) {
|
|
3720
|
+
for (const action of candidate.operationActions ?? []) {
|
|
3721
|
+
if (action.kind !== 'materializeNativeSkill' || action.artifactId !== undefined)
|
|
3722
|
+
continue;
|
|
3723
|
+
const skillsRoot = path.join(stagingRoot, 'plugins', candidate.identity.pluginName, action.platform, candidate.plugin.manifest.skills.replace(/^\.\//u, ''), action.skillKey);
|
|
3724
|
+
if (`sha256:${crypto.createHash('sha256').update(JSON.stringify(action.files)).digest('hex')}` !== action.contentDigest)
|
|
3725
|
+
throw new Error('commandResourceInvalid: prepared static Skill snapshot changed.');
|
|
3726
|
+
for (const file of action.files) {
|
|
3727
|
+
const content = Buffer.from(file.contentBase64, 'base64');
|
|
3728
|
+
if (`sha256:${crypto.createHash('sha256').update(content).digest('hex')}` !== file.contentDigest)
|
|
3729
|
+
throw new Error('commandResourceInvalid: prepared static Skill file digest changed.');
|
|
3730
|
+
if (file.mode !== undefined && file.mode !== 0o644 && file.mode !== 0o755)
|
|
3731
|
+
throw new Error('Skill action file mode is invalid.');
|
|
3732
|
+
const destination = path.join(skillsRoot, ...file.relativePath.split('/'));
|
|
3733
|
+
await fs.outputFile(destination, content);
|
|
3734
|
+
await fs.chmod(destination, file.mode ?? 0o644);
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3737
|
+
if (candidate.identity.platform !== 'codex')
|
|
3738
|
+
continue;
|
|
3358
3739
|
const hasStaticSkills = candidate.plugin.manifest.skills !== undefined;
|
|
3359
3740
|
const preparedNativeRender = await requirePreparedCodexNativeRender({
|
|
3360
3741
|
context: options.context,
|
|
@@ -3368,6 +3749,8 @@ function createNativeDriver(options) {
|
|
|
3368
3749
|
hasStaticSkills,
|
|
3369
3750
|
})
|
|
3370
3751
|
: null;
|
|
3752
|
+
if (nativeRender)
|
|
3753
|
+
renderedReceipts.push({ pluginName: candidate.identity.pluginName, receipt: nativeRender.receipt });
|
|
3371
3754
|
if (!nativeRender) {
|
|
3372
3755
|
await ensureCodexActiveSkillsManifest({
|
|
3373
3756
|
plugin: candidate.plugin,
|
|
@@ -3385,7 +3768,9 @@ function createNativeDriver(options) {
|
|
|
3385
3768
|
});
|
|
3386
3769
|
}
|
|
3387
3770
|
}
|
|
3388
|
-
|
|
3771
|
+
for (const receipt of renderedReceipts)
|
|
3772
|
+
await refreshPreservedPlatformReceipt(stagingRoot, receipt);
|
|
3773
|
+
}, new Set(materializationTargets.map((candidate) => candidate.identity.pluginId ?? `${candidate.identity.sourceId}/${candidate.identity.pluginName}`)), requestedPlatforms);
|
|
3389
3774
|
try {
|
|
3390
3775
|
await transaction.commit();
|
|
3391
3776
|
}
|
|
@@ -3401,6 +3786,8 @@ function createNativeDriver(options) {
|
|
|
3401
3786
|
throw error;
|
|
3402
3787
|
}
|
|
3403
3788
|
committedNativeGroups.add(key);
|
|
3789
|
+
for (const candidate of materializationTargets)
|
|
3790
|
+
await revokeAtRoot(path.join(candidate.identity.marketplaceRoot ?? resolveAwHome(options.context.env), 'plugins', candidate.identity.pluginName), true, candidate.identity.platform);
|
|
3404
3791
|
})().catch((error) => {
|
|
3405
3792
|
throw asNativeGroupFailure(error);
|
|
3406
3793
|
});
|
|
@@ -3631,7 +4018,7 @@ function createNativeDriver(options) {
|
|
|
3631
4018
|
await validateSkillDirectory(component.path, component.identity.platform === 'codex' ? 'codex.skill' : 'claude.skill', { ownerMarkerPolicy: 'ignore', requirementsReceiptPolicy: 'allow' });
|
|
3632
4019
|
},
|
|
3633
4020
|
skillDirectoryMatchesPristineTree: async ({ actualPath, files }) => {
|
|
3634
|
-
const expectedFiles = new Map(files.map((entry) => [entry.relativePath, entry
|
|
4021
|
+
const expectedFiles = new Map(files.map((entry) => [entry.relativePath, entry]));
|
|
3635
4022
|
const expectedDirectories = new Set(['']);
|
|
3636
4023
|
for (const relativePath of expectedFiles.keys()) {
|
|
3637
4024
|
const segments = relativePath.split('/');
|
|
@@ -3657,7 +4044,11 @@ function createNativeDriver(options) {
|
|
|
3657
4044
|
if (!entry.isFile())
|
|
3658
4045
|
return false;
|
|
3659
4046
|
const digest = `sha256:${crypto.createHash('sha256').update(await fs.readFile(absolutePath)).digest('hex')}`;
|
|
3660
|
-
|
|
4047
|
+
const expected = expectedFiles.get(relativePath);
|
|
4048
|
+
if (expected?.contentDigest !== digest)
|
|
4049
|
+
return false;
|
|
4050
|
+
if (catalogFileMode(relativePath, (await fs.stat(absolutePath)).mode)
|
|
4051
|
+
!== catalogFileMode(relativePath, expected.mode ?? 0o644))
|
|
3661
4052
|
return false;
|
|
3662
4053
|
seen.add(relativePath);
|
|
3663
4054
|
}
|
|
@@ -3667,6 +4058,10 @@ function createNativeDriver(options) {
|
|
|
3667
4058
|
};
|
|
3668
4059
|
return {
|
|
3669
4060
|
ownership,
|
|
4061
|
+
beforeCleanupMutation: async (component) => {
|
|
4062
|
+
if (component.kind === 'skill-directory')
|
|
4063
|
+
await revokeAtRoot(component.path);
|
|
4064
|
+
},
|
|
3670
4065
|
setNativeMarketplaceEligibleTargets: (eligibleTargets) => {
|
|
3671
4066
|
nativeMarketplaceEligibleTargets = new Set(eligibleTargets);
|
|
3672
4067
|
},
|
|
@@ -3760,6 +4155,7 @@ function createNativeDriver(options) {
|
|
|
3760
4155
|
: 'user'))));
|
|
3761
4156
|
},
|
|
3762
4157
|
preflightMutation: async (target) => {
|
|
4158
|
+
await preflightCommandBindings([target]);
|
|
3763
4159
|
if (!target.codexNativeObservationExpectation
|
|
3764
4160
|
&& !target.codexNativeSensitivePreflightRequired)
|
|
3765
4161
|
return;
|
|
@@ -3827,7 +4223,15 @@ function createNativeDriver(options) {
|
|
|
3827
4223
|
});
|
|
3828
4224
|
}
|
|
3829
4225
|
},
|
|
3830
|
-
finalizeCleanup: async () =>
|
|
4226
|
+
finalizeCleanup: async (target) => {
|
|
4227
|
+
for (const action of target.operationActions ?? []) {
|
|
4228
|
+
if (action.kind !== 'retainManagedCommandPackage')
|
|
4229
|
+
continue;
|
|
4230
|
+
options.context.commandResults ??= [];
|
|
4231
|
+
if (!options.context.commandResults.some((result) => getCommandPackageId(result.producer) === action.packageId))
|
|
4232
|
+
options.context.commandResults.push(retainedManagedCommandPackageResult(action));
|
|
4233
|
+
}
|
|
4234
|
+
},
|
|
3831
4235
|
probeNative: async (component) => {
|
|
3832
4236
|
const target = findTarget(component);
|
|
3833
4237
|
if (!target)
|
|
@@ -3915,8 +4319,10 @@ function createNativeDriver(options) {
|
|
|
3915
4319
|
throw new Error('codexNativeObservationUnavailable: native group precheck is missing.');
|
|
3916
4320
|
}
|
|
3917
4321
|
const removalRequired = before === 'present';
|
|
3918
|
-
if (!removalRequired)
|
|
4322
|
+
if (!removalRequired) {
|
|
4323
|
+
await revokeAtRoot(path.join(target.identity.marketplaceRoot ?? resolveAwHome(options.context.env), 'plugins', target.identity.pluginName), true, 'codex');
|
|
3919
4324
|
return;
|
|
4325
|
+
}
|
|
3920
4326
|
try {
|
|
3921
4327
|
codexMutationMarketplaces.add(marketplaceName);
|
|
3922
4328
|
await runObservedCodexMutation({
|
|
@@ -3945,6 +4351,8 @@ function createNativeDriver(options) {
|
|
|
3945
4351
|
? `codexNativeRemoveIncomplete: ${CODEX_REMOVE_RECOVERY}`
|
|
3946
4352
|
: `codexNativeInstallIncomplete: ${component.pluginId} is absent from Codex plugin list.`);
|
|
3947
4353
|
}
|
|
4354
|
+
if (expected === 'absent')
|
|
4355
|
+
await revokeAtRoot(path.join(target.identity.marketplaceRoot ?? resolveAwHome(options.context.env), 'plugins', target.identity.pluginName), true, 'codex');
|
|
3948
4356
|
};
|
|
3949
4357
|
if (options.operationScopedNativePhases) {
|
|
3950
4358
|
deferredPostchecks.push({ target, postcheck });
|
|
@@ -3980,6 +4388,7 @@ function createNativeDriver(options) {
|
|
|
3980
4388
|
}
|
|
3981
4389
|
},
|
|
3982
4390
|
writeNative: async (target) => {
|
|
4391
|
+
await publishPackages(target);
|
|
3983
4392
|
if (!target.catalog)
|
|
3984
4393
|
throw new Error('Missing rebuild target catalog data.');
|
|
3985
4394
|
const marketplaceName = target.identity.marketplaceName ?? MARKETPLACE_NAME;
|
|
@@ -4082,13 +4491,48 @@ function createNativeDriver(options) {
|
|
|
4082
4491
|
throw new Error(`Claude native install postcheck 失败:declaredMarketplace=${after.marketplaceDeclaredState},materializedMarketplace=${after.marketplaceMaterializedState},plugin=${after.pluginState},enabled=${after.pluginEnabled},versions=${after.pluginVersions.join(',')}。`);
|
|
4083
4492
|
}
|
|
4084
4493
|
}
|
|
4494
|
+
const pluginRoot = path.join(expectedMarketplaceRoot, 'plugins', target.identity.pluginName);
|
|
4495
|
+
if (target.identity.platform === 'codex' || target.operationActions?.some((action) => action.kind === 'materializeNativeSkill' && action.platform === 'claude')) {
|
|
4496
|
+
const relativeRoot = target.identity.platform === 'codex'
|
|
4497
|
+
? await resolveCodexSkillsRelativeRoot(target.plugin, expectedMarketplaceRoot)
|
|
4498
|
+
: await resolveClaudeSkillsRelativeRoot(pluginRoot);
|
|
4499
|
+
const skillsRoot = path.join(pluginRoot, relativeRoot);
|
|
4500
|
+
for (const entry of await fs.readdir(skillsRoot, { withFileTypes: true }).catch(() => [])) {
|
|
4501
|
+
if (!entry.isDirectory())
|
|
4502
|
+
continue;
|
|
4503
|
+
const resourceRoot = path.join(skillsRoot, entry.name);
|
|
4504
|
+
const installed = await readInstalledCommandProducer(resourceRoot).catch(() => null);
|
|
4505
|
+
if (!installed)
|
|
4506
|
+
continue;
|
|
4507
|
+
const commandIds = await publishManagedCommandBindings({ ...installed, resourceRoot, env: options.context.env });
|
|
4508
|
+
options.context.commandResults ??= [];
|
|
4509
|
+
options.context.commandResults.push({ producer: installed.producer, state: 'completed', commandIds, removedCommandIds: [], errors: [], recoveryActions: [] });
|
|
4510
|
+
}
|
|
4511
|
+
}
|
|
4085
4512
|
},
|
|
4086
4513
|
writeArtifacts: async (target) => {
|
|
4514
|
+
await preflightCommandBindings([target]);
|
|
4515
|
+
await publishPackages(target);
|
|
4087
4516
|
for (const operationAction of target.operationActions ?? []) {
|
|
4088
4517
|
if (operationAction.kind === 'writeOwnedArtifact'
|
|
4089
4518
|
|| operationAction.kind === 'updateSharedConfig'
|
|
4090
4519
|
|| operationAction.kind === 'setActivation') {
|
|
4520
|
+
if (operationAction.kind === 'writeOwnedArtifact' && operationAction.artifactKind === 'skill-directory') {
|
|
4521
|
+
const previous = await readInstalledCommandProducer(operationAction.path).catch(() => null);
|
|
4522
|
+
if (previous && (previous.producer.sourceId !== target.identity.sourceId || previous.producer.sourceIdentity !== target.identity.sourceIdentity || previous.producer.pluginName !== target.identity.pluginName))
|
|
4523
|
+
throw new Error('commandOwnerMismatch: installed command producer belongs to another source identity; clean up the previous owner first.');
|
|
4524
|
+
}
|
|
4091
4525
|
await applyPlannedOperationAction(operationAction);
|
|
4526
|
+
if (operationAction.kind === 'writeOwnedArtifact' && operationAction.artifactKind === 'skill-directory') {
|
|
4527
|
+
const installed = await readInstalledCommandProducer(operationAction.path).catch(() => null);
|
|
4528
|
+
if (installed) {
|
|
4529
|
+
const commandIds = await publishManagedCommandBindings({ ...installed, resourceRoot: operationAction.path, env: options.context.env });
|
|
4530
|
+
options.context.commandResults ??= [];
|
|
4531
|
+
options.context.commandResults.push({ producer: installed.producer, state: 'completed', commandIds, removedCommandIds: [], errors: [], recoveryActions: [] });
|
|
4532
|
+
}
|
|
4533
|
+
else
|
|
4534
|
+
await revokeAtRoot(operationAction.path);
|
|
4535
|
+
}
|
|
4092
4536
|
}
|
|
4093
4537
|
}
|
|
4094
4538
|
return target.artifacts;
|
|
@@ -4172,6 +4616,26 @@ export function createRebuildIntegration(options) {
|
|
|
4172
4616
|
});
|
|
4173
4617
|
}
|
|
4174
4618
|
const event = 'install';
|
|
4619
|
+
const commands = materializing ? await commandPackageActions({ ...input, env: options.context.env, context: options.context }) : { actions: [], candidates: [] };
|
|
4620
|
+
options.context.managedCommandCandidates ??= new Map();
|
|
4621
|
+
options.context.managedCommandCandidates.set(pluginActionId(input.plugin), commands.candidates);
|
|
4622
|
+
const nativeSkillActions = [];
|
|
4623
|
+
if (materializing && input.platform === 'codex' && input.plugin.manifest.managedCommands && codexNativeRenderTargets(input.plugin).length) {
|
|
4624
|
+
const native = await prepareCodexNativeRenderCached({ context: options.context, plugin: input.plugin, scriptTimeoutMs: options.scriptTimeoutMs });
|
|
4625
|
+
if (native) {
|
|
4626
|
+
commands.candidates.push(...native.commandCandidates);
|
|
4627
|
+
for (const { artifact, skillKey, tree } of native.renderedArtifacts) {
|
|
4628
|
+
const files = Object.entries(tree.files).map(([relativePath, content]) => ({
|
|
4629
|
+
relativePath, contentBase64: content.toString('base64'),
|
|
4630
|
+
contentDigest: `sha256:${crypto.createHash('sha256').update(content).digest('hex')}`,
|
|
4631
|
+
...(tree.fileModes?.[relativePath] ? { mode: tree.fileModes[relativePath] } : {}),
|
|
4632
|
+
})).sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
4633
|
+
nativeSkillActions.push({ kind: 'materializeNativeSkill', pluginId: pluginActionId(input.plugin),
|
|
4634
|
+
platform: 'codex', scope: 'user', artifactId: artifact.id, skillKey, files,
|
|
4635
|
+
contentDigest: `sha256:${crypto.createHash('sha256').update(JSON.stringify(files)).digest('hex')}` });
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
}
|
|
4175
4639
|
const historicalArtifacts = [];
|
|
4176
4640
|
let writes = [];
|
|
4177
4641
|
let currentRemovalComponents = [];
|
|
@@ -4197,6 +4661,9 @@ export function createRebuildIntegration(options) {
|
|
|
4197
4661
|
scriptTimeoutMs: options.scriptTimeoutMs,
|
|
4198
4662
|
marketplaceName: input.marketplaceName,
|
|
4199
4663
|
userActivationDesired: options.context.codexUserActivationDesired,
|
|
4664
|
+
commandCandidates: commands.candidates,
|
|
4665
|
+
nativeSkillActions,
|
|
4666
|
+
context: options.context,
|
|
4200
4667
|
});
|
|
4201
4668
|
}
|
|
4202
4669
|
else {
|
|
@@ -4206,6 +4673,8 @@ export function createRebuildIntegration(options) {
|
|
|
4206
4673
|
event,
|
|
4207
4674
|
historicalArtifacts,
|
|
4208
4675
|
scriptTimeoutMs: options.scriptTimeoutMs,
|
|
4676
|
+
commandCandidates: commands.candidates,
|
|
4677
|
+
nativeSkillActions,
|
|
4209
4678
|
});
|
|
4210
4679
|
}
|
|
4211
4680
|
}
|
|
@@ -4367,7 +4836,8 @@ export function createRebuildIntegration(options) {
|
|
|
4367
4836
|
|| codexRegistration.observation.entry.version === input.plugin.manifest.version);
|
|
4368
4837
|
const materializeNativeRequired = nativeDesired && (input.platform === 'claude'
|
|
4369
4838
|
|| input.command === 'reinstall'
|
|
4370
|
-
|| !codexNativeExact
|
|
4839
|
+
|| !codexNativeExact
|
|
4840
|
+
|| (materializing && await hasPreparedStaticNativeCommandDrift({ plugin: input.plugin, cwd: input.cwd, env: options.context.env, context: options.context, marketplaceRoot: input.marketplaceRoot ?? resolveAwHome(options.context.env) })));
|
|
4371
4841
|
const nativeCleanupCandidate = input.platform === 'claude'
|
|
4372
4842
|
? input.includeNativeCleanupCandidate !== false
|
|
4373
4843
|
: input.platform === 'codex'
|
|
@@ -4456,7 +4926,11 @@ export function createRebuildIntegration(options) {
|
|
|
4456
4926
|
]);
|
|
4457
4927
|
bindNamespacedOwnerIdentity(allComponents, input);
|
|
4458
4928
|
const desiredKeys = new Set(desiredComponents.map(componentIdentityKey));
|
|
4459
|
-
const
|
|
4929
|
+
const retainedPackages = input.command === 'cleanup' || input.command === 'uninstall'
|
|
4930
|
+
? await discoverRetainedManagedCommandPackages({ selectors: [{ sourceId: input.sourceId, pluginName: input.plugin.manifest.name }], scope: input.scope, cwd: input.cwd, env: options.context.env })
|
|
4931
|
+
: [];
|
|
4932
|
+
const operationActions = [...commands.actions, ...nativeSkillActions,
|
|
4933
|
+
...retainedPackages.map((entry) => ({ ...entry, kind: 'retainManagedCommandPackage', pluginId: `${entry.producer.sourceId}/${entry.producer.pluginName}`, scope: entry.producer.scope }))];
|
|
4460
4934
|
const preApply = input.plugin.manifest.scripts?.preApply;
|
|
4461
4935
|
if (preApply && input.command !== 'cleanup' && input.command !== 'uninstall') {
|
|
4462
4936
|
const script = path.resolve(input.plugin.rootDir, preApply.path.replace(/^\.\//u, ''));
|