@lesliechan721/aw-plugin-core 0.1.0-beta.1 → 0.1.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aw-capabilities.d.ts +27 -0
- package/dist/aw-capabilities.js +106 -0
- package/dist/aw-capabilities.js.map +1 -0
- package/dist/catalog-workspace-resolution.d.ts +3 -0
- package/dist/catalog-workspace-resolution.js +34 -0
- package/dist/catalog-workspace-resolution.js.map +1 -0
- package/dist/generated/plugin-manifest-v3.d.ts +675 -0
- package/dist/generated/{plugin-manifest-v1.js → plugin-manifest-v3.js} +1 -1
- package/dist/generated/plugin-manifest-v3.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/manifest-contract.d.ts +3 -6
- package/dist/manifest-contract.js +203 -406
- package/dist/manifest-contract.js.map +1 -1
- package/dist/manifest-type-contract.d.ts +2 -2
- package/dist/manifest.d.ts +10 -8
- package/dist/manifest.js +11 -7
- package/dist/manifest.js.map +1 -1
- package/dist/plugin-authoring-capabilities.d.ts +21 -0
- package/dist/plugin-authoring-capabilities.js +187 -0
- package/dist/plugin-authoring-capabilities.js.map +1 -0
- package/dist/plugin-config.d.ts +13 -0
- package/dist/plugin-config.js +645 -0
- package/dist/plugin-config.js.map +1 -0
- package/dist/plugin-requirements.d.ts +34 -0
- package/dist/plugin-requirements.js +248 -0
- package/dist/plugin-requirements.js.map +1 -0
- package/dist/plugin-script-contract.d.ts +10 -12
- package/dist/plugin-script-contract.js +34 -64
- package/dist/plugin-script-contract.js.map +1 -1
- package/dist/types.d.ts +142 -62
- package/dist/workspace.d.ts +54 -29
- package/dist/workspace.js +497 -269
- package/dist/workspace.js.map +1 -1
- package/package.json +8 -4
- package/schemas/plugin-manifest-v3.json +590 -0
- package/dist/generated/plugin-manifest-v1.d.ts +0 -368
- package/dist/generated/plugin-manifest-v1.js.map +0 -1
- package/dist/plugin-release.d.ts +0 -30
- package/dist/plugin-release.js +0 -187
- package/dist/plugin-release.js.map +0 -1
- package/schemas/plugin-manifest-v1.json +0 -329
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { isDeepStrictEqual } from 'node:util';
|
|
3
3
|
import fs from 'fs-extra';
|
|
4
|
+
import { computeAwCapabilitySetDigest, normalizeAwCapabilities, } from './aw-capabilities.js';
|
|
4
5
|
import { normalizePluginManifestSchemaVersion, PLUGIN_MANIFEST_SCHEMA_VERSION, SOURCE_PLUGIN_MANIFEST_CONTRACT_VERSION, } from './manifest.js';
|
|
6
|
+
import { normalizePluginConfigDefinition } from './plugin-config.js';
|
|
5
7
|
import { readJsonFile } from './read-json.js';
|
|
6
8
|
import { parsePermissionCommand } from './permission-command.js';
|
|
7
9
|
import { applyFileRules } from './file-rules.js';
|
|
8
10
|
import { resolvePlatformEntries } from './platform-entries.js';
|
|
9
11
|
import { validatePortableSkillDirectoriesRoot, validatePortableSkillName, } from './portable-skill-validation.js';
|
|
10
12
|
import { assertHookMatcherAllowed, assertNativeHookEvent, assertPublicHookEvent, normalizeRenderedHookGroupIdentity, renderHookDefinitionGroup, resolvePlatformHookDefinitions, } from './hooks-contract.js';
|
|
11
|
-
import {
|
|
13
|
+
import { PLUGIN_SCRIPT_TIMEOUT_MAX_MS, PLUGIN_SCRIPT_TIMEOUT_MIN_MS, } from './plugin-script-contract.js';
|
|
12
14
|
import { DEFAULT_SLASH_COMMANDS_PATH, resolveSlashCommands, } from './slash-commands.js';
|
|
13
15
|
import { isSkillAuthorOnlyPath } from './skill-authoring.js';
|
|
14
16
|
import { assertPluginInputTree, validateRuntimeExportSource } from './runtime-export-source.js';
|
|
@@ -29,7 +31,6 @@ const PLATFORM_PRODUCT_BY_NAME = {
|
|
|
29
31
|
};
|
|
30
32
|
const PLUGIN_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
31
33
|
const DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
32
|
-
export const DEFAULT_SOURCE_PLUGIN_AW_MIN_VERSION = '0.0.0';
|
|
33
34
|
const STRICT_SEMVER_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|(?:[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*))(?:\.(?:0|[1-9]\d*|(?:[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*)))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
|
|
34
35
|
export const STRICT_SEMVER_VALIDATION_CODE = 'invalidStrictSemVer';
|
|
35
36
|
export class StrictSemVerValidationError extends Error {
|
|
@@ -265,30 +266,26 @@ function assertCatalogChannel(value, fieldName) {
|
|
|
265
266
|
}
|
|
266
267
|
return channel;
|
|
267
268
|
}
|
|
268
|
-
function
|
|
269
|
-
return {
|
|
270
|
-
aw: {
|
|
271
|
-
minVersion,
|
|
272
|
-
},
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
function normalizePluginCompatibility(value, fieldName, options) {
|
|
269
|
+
function normalizePluginRequirements(value, fieldName, options) {
|
|
276
270
|
if (value === undefined) {
|
|
277
|
-
if (options.allowMissing)
|
|
278
|
-
return
|
|
279
|
-
}
|
|
280
|
-
throw new Error(`Invalid ${fieldName}.aw.minVersion: expected a strict SemVer 2.0.0 string. Received undefined.`);
|
|
271
|
+
if (options.allowMissing)
|
|
272
|
+
return undefined;
|
|
273
|
+
throw new Error(`Invalid ${fieldName}: expected an object.`);
|
|
281
274
|
}
|
|
282
275
|
const input = assertObject(value, fieldName);
|
|
283
276
|
assertAllowedKeys(input, fieldName, ['aw']);
|
|
284
277
|
const aw = assertObject(input.aw, `${fieldName}.aw`);
|
|
285
|
-
assertAllowedKeys(aw, `${fieldName}.aw`, ['
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
function assertPluginMinVersionWithinCatalogBounds(options) {
|
|
289
|
-
if (compareStrictSemVer(options.pluginMinVersion, options.catalogMinVersion) > 0) {
|
|
290
|
-
throw new Error(`Invalid ${options.fieldName}: plugin "${options.pluginName}" requires aw ${options.pluginMinVersion}, which is higher than catalog minimum ${options.catalogMinVersion}.`);
|
|
278
|
+
assertAllowedKeys(aw, `${fieldName}.aw`, ['capabilities']);
|
|
279
|
+
if (aw.capabilities !== undefined && !Array.isArray(aw.capabilities)) {
|
|
280
|
+
throw new Error(`Invalid ${fieldName}.aw.capabilities: expected an array.`);
|
|
291
281
|
}
|
|
282
|
+
return {
|
|
283
|
+
aw: {
|
|
284
|
+
...(aw.capabilities === undefined
|
|
285
|
+
? {}
|
|
286
|
+
: { capabilities: normalizeAwCapabilities(aw.capabilities, `${fieldName}.aw.capabilities`) }),
|
|
287
|
+
},
|
|
288
|
+
};
|
|
292
289
|
}
|
|
293
290
|
function normalizeMarketplaceProducts(value, fieldName) {
|
|
294
291
|
if (value === undefined) {
|
|
@@ -619,15 +616,20 @@ function normalizeRuntimeExports(pluginRoot, value, fieldName) {
|
|
|
619
616
|
return value.map((entry, index) => {
|
|
620
617
|
const entryField = `${fieldName}[${index}]`;
|
|
621
618
|
const input = assertObject(entry, entryField);
|
|
622
|
-
assertAllowedKeys(input, entryField, ['name', 'source']);
|
|
619
|
+
assertAllowedKeys(input, entryField, ['name', 'source', 'requires']);
|
|
623
620
|
const name = normalizeRuntimeExportName(input.name, `${entryField}.name`);
|
|
624
621
|
if (names.has(name)) {
|
|
625
622
|
throw new Error(`Invalid ${fieldName}: duplicate export name "${name}".`);
|
|
626
623
|
}
|
|
627
624
|
names.add(name);
|
|
625
|
+
const requires = normalizePluginRequirements(input.requires, `${entryField}.requires`, { allowMissing: false });
|
|
626
|
+
if (requires.aw.capabilities === undefined) {
|
|
627
|
+
throw new Error(`Invalid ${entryField}.requires.aw.capabilities: expected an array.`);
|
|
628
|
+
}
|
|
628
629
|
return {
|
|
629
630
|
name,
|
|
630
631
|
source: normalizeCanonicalPluginFilePath(pluginRoot, input.source, `${entryField}.source`),
|
|
632
|
+
requires: { aw: { capabilities: requires.aw.capabilities } },
|
|
631
633
|
};
|
|
632
634
|
});
|
|
633
635
|
}
|
|
@@ -838,12 +840,6 @@ function ensureBoolean(value, fieldName) {
|
|
|
838
840
|
}
|
|
839
841
|
return value;
|
|
840
842
|
}
|
|
841
|
-
function ensureFiniteNumber(value, fieldName) {
|
|
842
|
-
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
843
|
-
throw new Error(`Invalid ${fieldName}: expected a number.`);
|
|
844
|
-
}
|
|
845
|
-
return value;
|
|
846
|
-
}
|
|
847
843
|
function normalizeScriptTimeout(value, fieldName) {
|
|
848
844
|
if (typeof value !== 'number' ||
|
|
849
845
|
!Number.isInteger(value) ||
|
|
@@ -859,11 +855,6 @@ function assertNodeRunnablePath(value, fieldName) {
|
|
|
859
855
|
throw new Error(`Invalid ${fieldName}: expected a Node runnable file extension (.js, .mjs, or .cjs).`);
|
|
860
856
|
}
|
|
861
857
|
}
|
|
862
|
-
function assertAutoRunnablePath(value, fieldName) {
|
|
863
|
-
if (!inferLifecycleScriptRunnerFromPath(value)) {
|
|
864
|
-
throw new Error(`Invalid ${fieldName}: runner "auto" requires one of these file extensions: ${PLUGIN_AUTO_RUNNER_EXTENSIONS.join(', ')}.`);
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
858
|
function assertNotInlineCommandLike(value, fieldName) {
|
|
868
859
|
if (/\s|[;&|<>`]/.test(value) || value.includes('$(')) {
|
|
869
860
|
throw new Error(`Invalid ${fieldName}: inline command strings are not allowed.`);
|
|
@@ -874,7 +865,7 @@ function normalizePluginScriptPath(pluginRoot, value, fieldName) {
|
|
|
874
865
|
assertNotInlineCommandLike(rawPath, fieldName);
|
|
875
866
|
return normalizePluginRelativePath(pluginRoot, rawPath, fieldName);
|
|
876
867
|
}
|
|
877
|
-
function normalizeScriptEntry(pluginRoot, value, fieldName
|
|
868
|
+
function normalizeScriptEntry(pluginRoot, value, fieldName) {
|
|
878
869
|
const input = assertObject(value, fieldName);
|
|
879
870
|
if ('command' in input) {
|
|
880
871
|
throw new Error(`Invalid ${fieldName}: inline command strings are not allowed.`);
|
|
@@ -884,69 +875,29 @@ function normalizeScriptEntry(pluginRoot, value, fieldName, options = {}) {
|
|
|
884
875
|
}
|
|
885
876
|
assertAllowedKeys(input, fieldName, ['runner', 'path', 'args']);
|
|
886
877
|
const runner = ensureString(input.runner, `${fieldName}.runner`);
|
|
887
|
-
if (
|
|
888
|
-
throw new Error(`Invalid ${fieldName}.runner: unsupported runner "${runner}".`);
|
|
889
|
-
}
|
|
890
|
-
if (options.requireNodeRunner && runner !== 'node') {
|
|
878
|
+
if (runner !== 'node') {
|
|
891
879
|
throw new Error(`Invalid ${fieldName}.runner: expected "node".`);
|
|
892
880
|
}
|
|
893
881
|
const scriptPath = normalizePluginScriptPath(pluginRoot, input.path, `${fieldName}.path`);
|
|
894
|
-
if (runner === 'auto') {
|
|
895
|
-
assertAutoRunnablePath(scriptPath, `${fieldName}.path`);
|
|
896
|
-
}
|
|
897
882
|
return {
|
|
898
|
-
runner,
|
|
883
|
+
runner: 'node',
|
|
899
884
|
path: scriptPath,
|
|
900
885
|
args: input.args === undefined
|
|
901
886
|
? undefined
|
|
902
887
|
: ensureStringArray(input.args, `${fieldName}.args`),
|
|
903
888
|
};
|
|
904
889
|
}
|
|
905
|
-
function
|
|
906
|
-
if (typeof value === 'string') {
|
|
907
|
-
const scriptPath = normalizePluginScriptPath(pluginRoot, value, fieldName);
|
|
908
|
-
assertAutoRunnablePath(scriptPath, fieldName);
|
|
909
|
-
return {
|
|
910
|
-
default: {
|
|
911
|
-
runner: 'auto',
|
|
912
|
-
path: scriptPath,
|
|
913
|
-
},
|
|
914
|
-
};
|
|
915
|
-
}
|
|
916
|
-
const input = assertObject(value, fieldName);
|
|
917
|
-
if ('runner' in input || 'path' in input || 'command' in input) {
|
|
918
|
-
throw new Error(`Invalid ${fieldName}: lifecycle scripts must be declared under OS selectors.`);
|
|
919
|
-
}
|
|
920
|
-
if ('timeoutMs' in input || 'timeout' in input) {
|
|
921
|
-
throw new Error(`Invalid ${fieldName}: per-event and per-selector timeouts are not supported.`);
|
|
922
|
-
}
|
|
923
|
-
for (const key of Object.keys(input)) {
|
|
924
|
-
if (!isPluginLifecycleOsSelector(key)) {
|
|
925
|
-
throw new Error(`Invalid ${fieldName}: unknown OS selector "${key}".`);
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
const output = {};
|
|
929
|
-
for (const [selector, entry] of Object.entries(input)) {
|
|
930
|
-
output[selector] = normalizeScriptEntry(pluginRoot, entry, `${fieldName}.${selector}`);
|
|
931
|
-
}
|
|
932
|
-
for (const osName of ['linux', 'darwin', 'win32']) {
|
|
933
|
-
if (!resolveLifecycleScriptForOs(output, osName)) {
|
|
934
|
-
throw new Error(`Invalid ${fieldName}: no lifecycle entry resolves on ${osName}.`);
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
return output;
|
|
938
|
-
}
|
|
939
|
-
function normalizeLifecycleScripts(pluginRoot, value, fieldName) {
|
|
890
|
+
function normalizePreApplyScript(pluginRoot, value, fieldName) {
|
|
940
891
|
const input = assertObject(value, fieldName);
|
|
941
|
-
assertAllowedKeys(input, fieldName, [
|
|
942
|
-
const
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
892
|
+
assertAllowedKeys(input, fieldName, ['path', 'timeoutMs']);
|
|
893
|
+
const scriptPath = normalizePluginScriptPath(pluginRoot, input.path, `${fieldName}.path`);
|
|
894
|
+
assertNodeRunnablePath(scriptPath, `${fieldName}.path`);
|
|
895
|
+
return {
|
|
896
|
+
path: scriptPath,
|
|
897
|
+
...(input.timeoutMs === undefined
|
|
898
|
+
? {}
|
|
899
|
+
: { timeoutMs: normalizeScriptTimeout(input.timeoutMs, `${fieldName}.timeoutMs`) }),
|
|
900
|
+
};
|
|
950
901
|
}
|
|
951
902
|
function normalizeScriptHelpers(pluginRoot, value, fieldName) {
|
|
952
903
|
const input = assertObject(value, fieldName);
|
|
@@ -959,9 +910,7 @@ function normalizeScriptHelpers(pluginRoot, value, fieldName) {
|
|
|
959
910
|
return { config };
|
|
960
911
|
}
|
|
961
912
|
function normalizeNodeBuildScript(pluginRoot, value, fieldName) {
|
|
962
|
-
const entry = normalizeScriptEntry(pluginRoot, value, fieldName
|
|
963
|
-
requireNodeRunner: true,
|
|
964
|
-
});
|
|
913
|
+
const entry = normalizeScriptEntry(pluginRoot, value, fieldName);
|
|
965
914
|
assertNodeRunnablePath(entry.path, `${fieldName}.path`);
|
|
966
915
|
return {
|
|
967
916
|
runner: 'node',
|
|
@@ -1060,9 +1009,7 @@ function normalizeScriptTests(value, fieldName) {
|
|
|
1060
1009
|
};
|
|
1061
1010
|
}
|
|
1062
1011
|
function normalizeArtifactRenderScript(pluginRoot, value, fieldName) {
|
|
1063
|
-
const entry = normalizeScriptEntry(pluginRoot, value, fieldName
|
|
1064
|
-
requireNodeRunner: true,
|
|
1065
|
-
});
|
|
1012
|
+
const entry = normalizeScriptEntry(pluginRoot, value, fieldName);
|
|
1066
1013
|
assertNodeRunnablePath(entry.path, `${fieldName}.path`);
|
|
1067
1014
|
return {
|
|
1068
1015
|
runner: 'node',
|
|
@@ -1081,14 +1028,10 @@ function normalizeArtifactScripts(pluginRoot, value, fieldName) {
|
|
|
1081
1028
|
}
|
|
1082
1029
|
function normalizePluginScripts(pluginRoot, value, fieldName) {
|
|
1083
1030
|
const input = assertObject(value, fieldName);
|
|
1084
|
-
assertAllowedKeys(input, fieldName, ['
|
|
1085
|
-
const output = {
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
: normalizeScriptTimeout(input.timeoutMs, `${fieldName}.timeoutMs`),
|
|
1089
|
-
};
|
|
1090
|
-
if (input.lifecycle !== undefined) {
|
|
1091
|
-
output.lifecycle = normalizeLifecycleScripts(pluginRoot, input.lifecycle, `${fieldName}.lifecycle`);
|
|
1031
|
+
assertAllowedKeys(input, fieldName, ['preApply', 'helpers', 'node', 'artifacts', 'tests']);
|
|
1032
|
+
const output = {};
|
|
1033
|
+
if (input.preApply !== undefined) {
|
|
1034
|
+
output.preApply = normalizePreApplyScript(pluginRoot, input.preApply, `${fieldName}.preApply`);
|
|
1092
1035
|
}
|
|
1093
1036
|
if (input.helpers !== undefined) {
|
|
1094
1037
|
output.helpers = normalizeScriptHelpers(pluginRoot, input.helpers, `${fieldName}.helpers`);
|
|
@@ -1104,254 +1047,6 @@ function normalizePluginScripts(pluginRoot, value, fieldName) {
|
|
|
1104
1047
|
}
|
|
1105
1048
|
return output;
|
|
1106
1049
|
}
|
|
1107
|
-
function validateConfigKey(key, fieldName) {
|
|
1108
|
-
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(key)) {
|
|
1109
|
-
throw new Error(`Invalid ${fieldName}: config key "${key}" is not valid.`);
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
function validateConfigDefault(type, value, values, min, max, fieldName) {
|
|
1113
|
-
if (type === 'string' || type === 'file' || type === 'directory') {
|
|
1114
|
-
return ensureString(value, fieldName);
|
|
1115
|
-
}
|
|
1116
|
-
if (type === 'boolean') {
|
|
1117
|
-
return ensureBoolean(value, fieldName);
|
|
1118
|
-
}
|
|
1119
|
-
if (type === 'number') {
|
|
1120
|
-
const numberValue = ensureFiniteNumber(value, fieldName);
|
|
1121
|
-
if (min !== undefined && numberValue < min) {
|
|
1122
|
-
throw new Error(`Invalid ${fieldName}: default is smaller than min.`);
|
|
1123
|
-
}
|
|
1124
|
-
if (max !== undefined && numberValue > max) {
|
|
1125
|
-
throw new Error(`Invalid ${fieldName}: default is greater than max.`);
|
|
1126
|
-
}
|
|
1127
|
-
return numberValue;
|
|
1128
|
-
}
|
|
1129
|
-
if (type === 'enum') {
|
|
1130
|
-
const enumValue = ensureString(value, fieldName);
|
|
1131
|
-
if (!values?.includes(enumValue)) {
|
|
1132
|
-
throw new Error(`Invalid ${fieldName}: enum default must be one of values.`);
|
|
1133
|
-
}
|
|
1134
|
-
return enumValue;
|
|
1135
|
-
}
|
|
1136
|
-
throw new Error(`Invalid ${fieldName}: unsupported config type "${type}".`);
|
|
1137
|
-
}
|
|
1138
|
-
function normalizePluginConfigPresentation(value, schema, fieldName) {
|
|
1139
|
-
const input = assertObject(value, fieldName);
|
|
1140
|
-
assertAllowedKeys(input, fieldName, ['groups', 'fields', 'presets']);
|
|
1141
|
-
const schemaKeys = new Set(Object.keys(schema));
|
|
1142
|
-
const presentation = {};
|
|
1143
|
-
if (input.groups !== undefined) {
|
|
1144
|
-
if (!Array.isArray(input.groups)) {
|
|
1145
|
-
throw new Error(`Invalid ${fieldName}.groups: expected an array.`);
|
|
1146
|
-
}
|
|
1147
|
-
const groupIds = new Set();
|
|
1148
|
-
const groupedFields = new Set();
|
|
1149
|
-
presentation.groups = input.groups.map((rawGroup, index) => {
|
|
1150
|
-
const groupFieldName = `${fieldName}.groups[${index}]`;
|
|
1151
|
-
const group = assertObject(rawGroup, groupFieldName);
|
|
1152
|
-
assertAllowedKeys(group, groupFieldName, [
|
|
1153
|
-
'id',
|
|
1154
|
-
'title',
|
|
1155
|
-
'description',
|
|
1156
|
-
'fields',
|
|
1157
|
-
'advanced',
|
|
1158
|
-
]);
|
|
1159
|
-
const id = ensureString(group.id, `${groupFieldName}.id`);
|
|
1160
|
-
if (groupIds.has(id)) {
|
|
1161
|
-
throw new Error(`Invalid ${groupFieldName}.id: duplicate group id "${id}".`);
|
|
1162
|
-
}
|
|
1163
|
-
groupIds.add(id);
|
|
1164
|
-
const fields = ensureStringArray(group.fields, `${groupFieldName}.fields`);
|
|
1165
|
-
for (const [fieldIndex, key] of fields.entries()) {
|
|
1166
|
-
if (!schemaKeys.has(key)) {
|
|
1167
|
-
throw new Error(`Invalid ${groupFieldName}.fields[${fieldIndex}]: unknown config key "${key}".`);
|
|
1168
|
-
}
|
|
1169
|
-
if (groupedFields.has(key)) {
|
|
1170
|
-
throw new Error(`Invalid ${groupFieldName}.fields[${fieldIndex}]: config key "${key}" is assigned to more than one group.`);
|
|
1171
|
-
}
|
|
1172
|
-
groupedFields.add(key);
|
|
1173
|
-
}
|
|
1174
|
-
return {
|
|
1175
|
-
id,
|
|
1176
|
-
title: ensureString(group.title, `${groupFieldName}.title`),
|
|
1177
|
-
description: group.description === undefined
|
|
1178
|
-
? undefined
|
|
1179
|
-
: ensureString(group.description, `${groupFieldName}.description`),
|
|
1180
|
-
fields,
|
|
1181
|
-
advanced: group.advanced === undefined
|
|
1182
|
-
? undefined
|
|
1183
|
-
: ensureBoolean(group.advanced, `${groupFieldName}.advanced`),
|
|
1184
|
-
};
|
|
1185
|
-
});
|
|
1186
|
-
}
|
|
1187
|
-
if (input.fields !== undefined) {
|
|
1188
|
-
const fieldsInput = assertObject(input.fields, `${fieldName}.fields`);
|
|
1189
|
-
presentation.fields = {};
|
|
1190
|
-
for (const [key, rawPresentationField] of Object.entries(fieldsInput)) {
|
|
1191
|
-
if (!schemaKeys.has(key)) {
|
|
1192
|
-
throw new Error(`Invalid ${fieldName}.fields: unknown config key "${key}".`);
|
|
1193
|
-
}
|
|
1194
|
-
const presentationFieldName = `${fieldName}.fields.${key}`;
|
|
1195
|
-
const presentationField = assertObject(rawPresentationField, presentationFieldName);
|
|
1196
|
-
assertAllowedKeys(presentationField, presentationFieldName, [
|
|
1197
|
-
'valueLabels',
|
|
1198
|
-
'recommendedValue',
|
|
1199
|
-
'placeholder',
|
|
1200
|
-
'unit',
|
|
1201
|
-
'advanced',
|
|
1202
|
-
]);
|
|
1203
|
-
const schemaField = schema[key];
|
|
1204
|
-
let valueLabels;
|
|
1205
|
-
if (presentationField.valueLabels !== undefined) {
|
|
1206
|
-
if (schemaField.type !== 'enum') {
|
|
1207
|
-
throw new Error(`Invalid ${presentationFieldName}.valueLabels: valueLabels are only supported for enum config.`);
|
|
1208
|
-
}
|
|
1209
|
-
const labelsInput = assertObject(presentationField.valueLabels, `${presentationFieldName}.valueLabels`);
|
|
1210
|
-
valueLabels = {};
|
|
1211
|
-
for (const [token, rawLabel] of Object.entries(labelsInput)) {
|
|
1212
|
-
if (!schemaField.values?.includes(token)) {
|
|
1213
|
-
throw new Error(`Invalid ${presentationFieldName}.valueLabels: unknown enum token "${token}".`);
|
|
1214
|
-
}
|
|
1215
|
-
valueLabels[token] = ensureString(rawLabel, `${presentationFieldName}.valueLabels.${token}`);
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
presentation.fields[key] = {
|
|
1219
|
-
valueLabels,
|
|
1220
|
-
recommendedValue: presentationField.recommendedValue === undefined
|
|
1221
|
-
? undefined
|
|
1222
|
-
: validateConfigDefault(schemaField.type, presentationField.recommendedValue, schemaField.values, schemaField.min, schemaField.max, `${presentationFieldName}.recommendedValue`),
|
|
1223
|
-
placeholder: presentationField.placeholder === undefined
|
|
1224
|
-
? undefined
|
|
1225
|
-
: ensureString(presentationField.placeholder, `${presentationFieldName}.placeholder`),
|
|
1226
|
-
unit: presentationField.unit === undefined
|
|
1227
|
-
? undefined
|
|
1228
|
-
: ensureString(presentationField.unit, `${presentationFieldName}.unit`),
|
|
1229
|
-
advanced: presentationField.advanced === undefined
|
|
1230
|
-
? undefined
|
|
1231
|
-
: ensureBoolean(presentationField.advanced, `${presentationFieldName}.advanced`),
|
|
1232
|
-
};
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
if (input.presets !== undefined) {
|
|
1236
|
-
if (!Array.isArray(input.presets)) {
|
|
1237
|
-
throw new Error(`Invalid ${fieldName}.presets: expected an array.`);
|
|
1238
|
-
}
|
|
1239
|
-
const presetIds = new Set();
|
|
1240
|
-
presentation.presets = input.presets.map((rawPreset, index) => {
|
|
1241
|
-
const presetFieldName = `${fieldName}.presets[${index}]`;
|
|
1242
|
-
const preset = assertObject(rawPreset, presetFieldName);
|
|
1243
|
-
assertAllowedKeys(preset, presetFieldName, ['id', 'title', 'description', 'values']);
|
|
1244
|
-
const id = ensureString(preset.id, `${presetFieldName}.id`);
|
|
1245
|
-
if (presetIds.has(id)) {
|
|
1246
|
-
throw new Error(`Invalid ${presetFieldName}.id: duplicate preset id "${id}".`);
|
|
1247
|
-
}
|
|
1248
|
-
presetIds.add(id);
|
|
1249
|
-
const valuesInput = assertObject(preset.values, `${presetFieldName}.values`);
|
|
1250
|
-
const values = {};
|
|
1251
|
-
for (const [key, rawValue] of Object.entries(valuesInput)) {
|
|
1252
|
-
const schemaField = schema[key];
|
|
1253
|
-
if (!schemaField) {
|
|
1254
|
-
throw new Error(`Invalid ${presetFieldName}.values: unknown config key "${key}".`);
|
|
1255
|
-
}
|
|
1256
|
-
if (schemaField.sensitive === true
|
|
1257
|
-
|| schemaField.type === 'file'
|
|
1258
|
-
|| schemaField.type === 'directory') {
|
|
1259
|
-
throw new Error(`Invalid ${presetFieldName}.values.${key}: presets cannot contain sensitive, file, or directory config values.`);
|
|
1260
|
-
}
|
|
1261
|
-
values[key] = validateConfigDefault(schemaField.type, rawValue, schemaField.values, schemaField.min, schemaField.max, `${presetFieldName}.values.${key}`);
|
|
1262
|
-
}
|
|
1263
|
-
return {
|
|
1264
|
-
id,
|
|
1265
|
-
title: ensureString(preset.title, `${presetFieldName}.title`),
|
|
1266
|
-
description: preset.description === undefined
|
|
1267
|
-
? undefined
|
|
1268
|
-
: ensureString(preset.description, `${presetFieldName}.description`),
|
|
1269
|
-
values,
|
|
1270
|
-
};
|
|
1271
|
-
});
|
|
1272
|
-
}
|
|
1273
|
-
return presentation;
|
|
1274
|
-
}
|
|
1275
|
-
function normalizePluginConfigSchema(value, fieldName) {
|
|
1276
|
-
const input = assertObject(value, fieldName);
|
|
1277
|
-
assertAllowedKeys(input, fieldName, ['schema', 'presentation']);
|
|
1278
|
-
const schemaInput = assertObject(input.schema, `${fieldName}.schema`);
|
|
1279
|
-
const schema = {};
|
|
1280
|
-
const supportedTypes = new Set(['string', 'number', 'boolean', 'enum', 'file', 'directory']);
|
|
1281
|
-
for (const [key, rawField] of Object.entries(schemaInput)) {
|
|
1282
|
-
validateConfigKey(key, `${fieldName}.schema`);
|
|
1283
|
-
const schemaFieldName = `${fieldName}.schema.${key}`;
|
|
1284
|
-
const configField = assertObject(rawField, schemaFieldName);
|
|
1285
|
-
assertAllowedKeys(configField, schemaFieldName, [
|
|
1286
|
-
'type',
|
|
1287
|
-
'title',
|
|
1288
|
-
'description',
|
|
1289
|
-
'default',
|
|
1290
|
-
'required',
|
|
1291
|
-
'sensitive',
|
|
1292
|
-
'values',
|
|
1293
|
-
'min',
|
|
1294
|
-
'max',
|
|
1295
|
-
]);
|
|
1296
|
-
const type = ensureString(configField.type, `${schemaFieldName}.type`);
|
|
1297
|
-
if (!supportedTypes.has(type)) {
|
|
1298
|
-
throw new Error(`Invalid ${schemaFieldName}.type: expected string, number, boolean, enum, file, or directory.`);
|
|
1299
|
-
}
|
|
1300
|
-
const required = configField.required === undefined
|
|
1301
|
-
? undefined
|
|
1302
|
-
: ensureBoolean(configField.required, `${schemaFieldName}.required`);
|
|
1303
|
-
const sensitive = configField.sensitive === undefined
|
|
1304
|
-
? undefined
|
|
1305
|
-
: ensureBoolean(configField.sensitive, `${schemaFieldName}.sensitive`);
|
|
1306
|
-
const values = configField.values === undefined
|
|
1307
|
-
? undefined
|
|
1308
|
-
: ensureStringArray(configField.values, `${schemaFieldName}.values`);
|
|
1309
|
-
if (type !== 'enum' && values !== undefined) {
|
|
1310
|
-
throw new Error(`Invalid ${schemaFieldName}.values: values are only supported for enum config.`);
|
|
1311
|
-
}
|
|
1312
|
-
if (type === 'enum' && (!values || values.length === 0)) {
|
|
1313
|
-
throw new Error(`Invalid ${schemaFieldName}.values: enum config requires at least one value.`);
|
|
1314
|
-
}
|
|
1315
|
-
if (values && new Set(values).size !== values.length) {
|
|
1316
|
-
throw new Error(`Invalid ${schemaFieldName}.values: enum values must be unique.`);
|
|
1317
|
-
}
|
|
1318
|
-
const min = configField.min === undefined
|
|
1319
|
-
? undefined
|
|
1320
|
-
: ensureFiniteNumber(configField.min, `${schemaFieldName}.min`);
|
|
1321
|
-
const max = configField.max === undefined
|
|
1322
|
-
? undefined
|
|
1323
|
-
: ensureFiniteNumber(configField.max, `${schemaFieldName}.max`);
|
|
1324
|
-
if (type !== 'number' && (min !== undefined || max !== undefined)) {
|
|
1325
|
-
throw new Error(`Invalid ${schemaFieldName}: min/max are only supported for number config.`);
|
|
1326
|
-
}
|
|
1327
|
-
if (min !== undefined && max !== undefined && min > max) {
|
|
1328
|
-
throw new Error(`Invalid ${schemaFieldName}: min must be less than or equal to max.`);
|
|
1329
|
-
}
|
|
1330
|
-
schema[key] = {
|
|
1331
|
-
type: type,
|
|
1332
|
-
title: configField.title === undefined
|
|
1333
|
-
? undefined
|
|
1334
|
-
: ensureString(configField.title, `${schemaFieldName}.title`),
|
|
1335
|
-
description: configField.description === undefined
|
|
1336
|
-
? undefined
|
|
1337
|
-
: ensureString(configField.description, `${schemaFieldName}.description`),
|
|
1338
|
-
default: configField.default === undefined
|
|
1339
|
-
? undefined
|
|
1340
|
-
: validateConfigDefault(type, configField.default, values, min, max, `${schemaFieldName}.default`),
|
|
1341
|
-
required,
|
|
1342
|
-
sensitive,
|
|
1343
|
-
values,
|
|
1344
|
-
min,
|
|
1345
|
-
max,
|
|
1346
|
-
};
|
|
1347
|
-
}
|
|
1348
|
-
return {
|
|
1349
|
-
schema,
|
|
1350
|
-
presentation: input.presentation === undefined
|
|
1351
|
-
? undefined
|
|
1352
|
-
: normalizePluginConfigPresentation(input.presentation, schema, `${fieldName}.presentation`),
|
|
1353
|
-
};
|
|
1354
|
-
}
|
|
1355
1050
|
const ARTIFACT_TARGETS = new Set([
|
|
1356
1051
|
'codex.developerInstructionsBlock',
|
|
1357
1052
|
'codex.agentRole',
|
|
@@ -1394,13 +1089,24 @@ function normalizeArtifactRuntimeImports(value, fieldName, reservedImports) {
|
|
|
1394
1089
|
const resolvedInput = assertObject(input.resolved, `${entryField}.resolved`);
|
|
1395
1090
|
assertAllowedKeys(resolvedInput, `${entryField}.resolved`, [
|
|
1396
1091
|
'path', 'digest', 'providerPluginId', 'providerVersion', 'source',
|
|
1092
|
+
'requiredCapabilities', 'capabilitySetDigest',
|
|
1397
1093
|
]);
|
|
1094
|
+
if (!Array.isArray(resolvedInput.requiredCapabilities)) {
|
|
1095
|
+
throw new Error(`Invalid ${entryField}.resolved.requiredCapabilities: expected an array.`);
|
|
1096
|
+
}
|
|
1097
|
+
const requiredCapabilities = normalizeAwCapabilities(resolvedInput.requiredCapabilities, `${entryField}.resolved.requiredCapabilities`);
|
|
1098
|
+
const capabilitySetDigest = ensureDigestString(resolvedInput.capabilitySetDigest, `${entryField}.resolved.capabilitySetDigest`);
|
|
1099
|
+
if (capabilitySetDigest !== computeAwCapabilitySetDigest(requiredCapabilities)) {
|
|
1100
|
+
throw new Error(`Invalid ${entryField}.resolved.capabilitySetDigest: digest does not match requiredCapabilities.`);
|
|
1101
|
+
}
|
|
1398
1102
|
resolved = {
|
|
1399
1103
|
path: normalizePluginChildPath('', ensureString(resolvedInput.path, `${entryField}.resolved.path`), `${entryField}.resolved.path`),
|
|
1400
1104
|
digest: ensureDigestString(resolvedInput.digest, `${entryField}.resolved.digest`),
|
|
1401
1105
|
providerPluginId: ensureString(resolvedInput.providerPluginId, `${entryField}.resolved.providerPluginId`),
|
|
1402
1106
|
providerVersion: assertStrictSemVer(resolvedInput.providerVersion, `${entryField}.resolved.providerVersion`),
|
|
1403
1107
|
source: ensureString(resolvedInput.source, `${entryField}.resolved.source`),
|
|
1108
|
+
requiredCapabilities,
|
|
1109
|
+
capabilitySetDigest,
|
|
1404
1110
|
};
|
|
1405
1111
|
}
|
|
1406
1112
|
return {
|
|
@@ -1458,7 +1164,7 @@ function normalizeArtifactStates(value, fieldName) {
|
|
|
1458
1164
|
}
|
|
1459
1165
|
function normalizeArtifactRefreshKeys(value, fieldName, schema) {
|
|
1460
1166
|
const refreshKeys = ensureStringArray(value, fieldName);
|
|
1461
|
-
const schemaKeys = new Set(Object.keys(schema?.
|
|
1167
|
+
const schemaKeys = new Set(Object.keys(schema?.fields ?? {}));
|
|
1462
1168
|
const normalized = [];
|
|
1463
1169
|
for (const [index, key] of refreshKeys.entries()) {
|
|
1464
1170
|
if (!schemaKeys.has(key)) {
|
|
@@ -1470,6 +1176,46 @@ function normalizeArtifactRefreshKeys(value, fieldName, schema) {
|
|
|
1470
1176
|
}
|
|
1471
1177
|
return normalized;
|
|
1472
1178
|
}
|
|
1179
|
+
function configFieldContainsSensitiveValue(field) {
|
|
1180
|
+
if (field.sensitive === true)
|
|
1181
|
+
return true;
|
|
1182
|
+
if (field.type !== 'object')
|
|
1183
|
+
return false;
|
|
1184
|
+
return ((field.properties
|
|
1185
|
+
? Object.values(field.properties).some((child) => child.sensitive === true)
|
|
1186
|
+
: false)
|
|
1187
|
+
|| (field.additionalProperties !== false && field.additionalProperties?.sensitive === true));
|
|
1188
|
+
}
|
|
1189
|
+
function assertCodexNativeRefreshKeysAreNotSensitive(options) {
|
|
1190
|
+
if (options.delivery?.mode !== 'codex-native')
|
|
1191
|
+
return;
|
|
1192
|
+
for (const [index, key] of options.refreshKeys.entries()) {
|
|
1193
|
+
const field = options.schema?.fields[key];
|
|
1194
|
+
if (field && configFieldContainsSensitiveValue(field)) {
|
|
1195
|
+
throw new Error(`codexNativeRenderSensitiveRefreshKey: Invalid ${options.fieldName}[${index}]: ` +
|
|
1196
|
+
`codex-native renderers cannot read sensitive config field "${key}".`);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
function normalizeCodexSkillDelivery(value, fieldName) {
|
|
1201
|
+
const input = assertObject(value, fieldName);
|
|
1202
|
+
const mode = ensureString(input.mode, `${fieldName}.mode`);
|
|
1203
|
+
if (mode === 'aw-extension') {
|
|
1204
|
+
assertAllowedKeys(input, fieldName, ['mode']);
|
|
1205
|
+
return { mode };
|
|
1206
|
+
}
|
|
1207
|
+
if (mode === 'codex-native') {
|
|
1208
|
+
assertAllowedKeys(input, fieldName, ['mode', 'identity', 'renderProfile']);
|
|
1209
|
+
if (input.identity !== 'shared') {
|
|
1210
|
+
throw new Error(`Invalid ${fieldName}.identity: expected "shared".`);
|
|
1211
|
+
}
|
|
1212
|
+
if (input.renderProfile !== 'user') {
|
|
1213
|
+
throw new Error(`Invalid ${fieldName}.renderProfile: expected "user".`);
|
|
1214
|
+
}
|
|
1215
|
+
return { mode, identity: 'shared', renderProfile: 'user' };
|
|
1216
|
+
}
|
|
1217
|
+
throw new Error(`Invalid ${fieldName}.mode: expected "codex-native" or "aw-extension".`);
|
|
1218
|
+
}
|
|
1473
1219
|
function normalizeDynamicArtifactKind(value, fieldName) {
|
|
1474
1220
|
const kind = ensureString(value, fieldName);
|
|
1475
1221
|
if (!DYNAMIC_ARTIFACT_KINDS.has(kind)) {
|
|
@@ -1510,6 +1256,7 @@ function normalizeArtifactTargetEntry(pluginRoot, entry, fieldName, schema, rese
|
|
|
1510
1256
|
'projectManagedKind',
|
|
1511
1257
|
'states',
|
|
1512
1258
|
'refreshKeys',
|
|
1259
|
+
'delivery',
|
|
1513
1260
|
'requiresCodexProjectConfig',
|
|
1514
1261
|
'roleKey',
|
|
1515
1262
|
'skillKey',
|
|
@@ -1520,6 +1267,12 @@ function normalizeArtifactTargetEntry(pluginRoot, entry, fieldName, schema, rese
|
|
|
1520
1267
|
const id = ensureString(input.id, `${fieldName}.id`);
|
|
1521
1268
|
validateArtifactId(id, `${fieldName}.id`);
|
|
1522
1269
|
const target = normalizeArtifactTarget(input.target, `${fieldName}.target`);
|
|
1270
|
+
const delivery = target === 'codex.skill'
|
|
1271
|
+
? normalizeCodexSkillDelivery(input.delivery, `${fieldName}.delivery`)
|
|
1272
|
+
: undefined;
|
|
1273
|
+
if (target !== 'codex.skill' && input.delivery !== undefined) {
|
|
1274
|
+
throw new Error(`Invalid ${fieldName}.delivery: delivery is only supported for codex.skill targets.`);
|
|
1275
|
+
}
|
|
1523
1276
|
const portable = target === 'agent-skills.skill';
|
|
1524
1277
|
const platform = portable ? undefined : normalizePlatformName(input.platform, `${fieldName}.platform`);
|
|
1525
1278
|
const distribution = portable
|
|
@@ -1615,6 +1368,13 @@ function normalizeArtifactTargetEntry(pluginRoot, entry, fieldName, schema, rese
|
|
|
1615
1368
|
if (!portable && input.runtimeImports !== undefined) {
|
|
1616
1369
|
throw new Error(`Invalid ${fieldName}.runtimeImports: runtimeImports are only supported for agent-skills.skill targets.`);
|
|
1617
1370
|
}
|
|
1371
|
+
const refreshKeys = normalizeArtifactRefreshKeys(input.refreshKeys, `${fieldName}.refreshKeys`, schema);
|
|
1372
|
+
assertCodexNativeRefreshKeysAreNotSensitive({
|
|
1373
|
+
delivery,
|
|
1374
|
+
refreshKeys,
|
|
1375
|
+
schema,
|
|
1376
|
+
fieldName: `${fieldName}.refreshKeys`,
|
|
1377
|
+
});
|
|
1618
1378
|
return {
|
|
1619
1379
|
id,
|
|
1620
1380
|
platform,
|
|
@@ -1624,7 +1384,8 @@ function normalizeArtifactTargetEntry(pluginRoot, entry, fieldName, schema, rese
|
|
|
1624
1384
|
kind,
|
|
1625
1385
|
projectManagedKind,
|
|
1626
1386
|
states: normalizeArtifactStates(input.states, `${fieldName}.states`),
|
|
1627
|
-
refreshKeys
|
|
1387
|
+
refreshKeys,
|
|
1388
|
+
delivery,
|
|
1628
1389
|
requiresCodexProjectConfig: input.requiresCodexProjectConfig === undefined
|
|
1629
1390
|
? undefined
|
|
1630
1391
|
: ensureBoolean(input.requiresCodexProjectConfig, `${fieldName}.requiresCodexProjectConfig`),
|
|
@@ -1644,15 +1405,19 @@ function normalizeArtifacts(pluginRoot, value, fieldName, schema, reservedImport
|
|
|
1644
1405
|
if (!Array.isArray(targetsInput)) {
|
|
1645
1406
|
throw new Error(`Invalid ${fieldName}.targets: expected an array.`);
|
|
1646
1407
|
}
|
|
1647
|
-
const
|
|
1408
|
+
const seenIdsBySurfaceScope = new Set();
|
|
1648
1409
|
const seenSkillOwners = new Set();
|
|
1410
|
+
const seenNativeIds = new Set();
|
|
1411
|
+
const seenNativeSkillKeys = new Set();
|
|
1649
1412
|
return targetsInput.map((entry, index) => {
|
|
1650
1413
|
const target = normalizeArtifactTargetEntry(pluginRoot, entry, `${fieldName}.targets[${index}]`, schema, reservedImports);
|
|
1651
|
-
const
|
|
1652
|
-
|
|
1653
|
-
|
|
1414
|
+
for (const scope of target.scopes) {
|
|
1415
|
+
const surfaceScopeId = `${target.platform ?? target.distribution}:${scope}:${target.id}`;
|
|
1416
|
+
if (seenIdsBySurfaceScope.has(surfaceScopeId)) {
|
|
1417
|
+
throw new Error(`Invalid ${fieldName}.targets[${index}]: duplicate artifact id "${target.id}" for surface/scope "${target.platform ?? target.distribution}:${scope}".`);
|
|
1418
|
+
}
|
|
1419
|
+
seenIdsBySurfaceScope.add(surfaceScopeId);
|
|
1654
1420
|
}
|
|
1655
|
-
seen.add(key);
|
|
1656
1421
|
if ((target.target === 'codex.skill' || target.target === 'claude.skill' || target.target === 'agent-skills.skill') && target.skillKey) {
|
|
1657
1422
|
for (const scope of target.scopes) {
|
|
1658
1423
|
const ownerKey = `${target.platform ?? target.distribution}:${scope}:${target.skillKey}`;
|
|
@@ -1662,11 +1427,60 @@ function normalizeArtifacts(pluginRoot, value, fieldName, schema, reservedImport
|
|
|
1662
1427
|
seenSkillOwners.add(ownerKey);
|
|
1663
1428
|
}
|
|
1664
1429
|
}
|
|
1430
|
+
if (target.delivery?.mode === 'codex-native') {
|
|
1431
|
+
if (seenNativeIds.has(target.id)) {
|
|
1432
|
+
throw new Error(`Invalid ${fieldName}.targets[${index}]: duplicate codex-native artifact id "${target.id}" in the shared projection.`);
|
|
1433
|
+
}
|
|
1434
|
+
if (target.skillKey && seenNativeSkillKeys.has(target.skillKey)) {
|
|
1435
|
+
throw new Error(`Invalid ${fieldName}.targets[${index}]: duplicate codex-native skillKey "${target.skillKey}" in the shared projection.`);
|
|
1436
|
+
}
|
|
1437
|
+
seenNativeIds.add(target.id);
|
|
1438
|
+
if (target.skillKey)
|
|
1439
|
+
seenNativeSkillKeys.add(target.skillKey);
|
|
1440
|
+
}
|
|
1665
1441
|
return target;
|
|
1666
1442
|
});
|
|
1667
1443
|
})();
|
|
1668
1444
|
return { targets };
|
|
1669
1445
|
}
|
|
1446
|
+
function canonicalScopeSet(scopes) {
|
|
1447
|
+
return [...scopes].sort().join(',');
|
|
1448
|
+
}
|
|
1449
|
+
function assertCodexSharedNativeScopeConsistency(manifest) {
|
|
1450
|
+
if (!supportsPlatform(manifest, 'codex'))
|
|
1451
|
+
return;
|
|
1452
|
+
const nativeComponents = [];
|
|
1453
|
+
if (manifest.skills) {
|
|
1454
|
+
nativeComponents.push({ key: 'skills', scopes: ['project', 'user'] });
|
|
1455
|
+
}
|
|
1456
|
+
if (manifest.mcpServers) {
|
|
1457
|
+
nativeComponents.push({ key: 'mcpServers', scopes: ['project', 'user'] });
|
|
1458
|
+
}
|
|
1459
|
+
if (resolvePlatformHookDefinitions(manifest, 'codex').length > 0) {
|
|
1460
|
+
nativeComponents.push({ key: 'hooks', scopes: ['project', 'user'] });
|
|
1461
|
+
}
|
|
1462
|
+
if (manifest.apps) {
|
|
1463
|
+
nativeComponents.push({ key: 'apps', scopes: ['project', 'user'] });
|
|
1464
|
+
}
|
|
1465
|
+
for (const [index, target] of (manifest.artifacts?.targets ?? []).entries()) {
|
|
1466
|
+
if (target.delivery?.mode === 'codex-native') {
|
|
1467
|
+
nativeComponents.push({
|
|
1468
|
+
key: `artifacts.targets[${index}]`,
|
|
1469
|
+
scopes: [...target.scopes],
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
const expected = nativeComponents[0];
|
|
1474
|
+
if (!expected)
|
|
1475
|
+
return;
|
|
1476
|
+
const expectedScopes = canonicalScopeSet(expected.scopes);
|
|
1477
|
+
const mismatch = nativeComponents.find((component) => canonicalScopeSet(component.scopes) !== expectedScopes);
|
|
1478
|
+
if (mismatch) {
|
|
1479
|
+
throw new Error(`codexSharedNativeScopeMismatch: Codex native component scope sets must match exactly; ` +
|
|
1480
|
+
`${expected.key} has {${expectedScopes}} but ${mismatch.key} has ` +
|
|
1481
|
+
`{${canonicalScopeSet(mismatch.scopes)}}.`);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1670
1484
|
function normalizePlatformAgentOverrides(value, fieldName, normalizer) {
|
|
1671
1485
|
const input = assertObject(value, fieldName);
|
|
1672
1486
|
assertAllowedKeys(input, fieldName, ['claude', 'codex']);
|
|
@@ -1909,11 +1723,8 @@ export function validateMarketplaceSourceMeta(rawValue) {
|
|
|
1909
1723
|
assertAllowedKeys(ownerInput, 'marketplace source metadata.owner', ['name', 'url']);
|
|
1910
1724
|
const interfaceInput = assertObject(input.interface, 'marketplace source metadata.interface');
|
|
1911
1725
|
assertAllowedKeys(interfaceInput, 'marketplace source metadata.interface', ['displayName']);
|
|
1912
|
-
const catalogInput = input.catalog
|
|
1913
|
-
|
|
1914
|
-
: assertObject(input.catalog, 'marketplace source metadata.catalog');
|
|
1915
|
-
if (catalogInput)
|
|
1916
|
-
assertAllowedKeys(catalogInput, 'marketplace source metadata.catalog', ['version']);
|
|
1726
|
+
const catalogInput = assertObject(input.catalog, 'marketplace source metadata.catalog');
|
|
1727
|
+
assertAllowedKeys(catalogInput, 'marketplace source metadata.catalog', ['version']);
|
|
1917
1728
|
return {
|
|
1918
1729
|
owner: {
|
|
1919
1730
|
name: ensureString(ownerInput.name, 'marketplace source metadata.owner.name'),
|
|
@@ -1923,9 +1734,7 @@ export function validateMarketplaceSourceMeta(rawValue) {
|
|
|
1923
1734
|
displayName: ensureString(interfaceInput.displayName, 'marketplace source metadata.interface.displayName'),
|
|
1924
1735
|
},
|
|
1925
1736
|
catalog: {
|
|
1926
|
-
version: catalogInput
|
|
1927
|
-
? '0.1.0'
|
|
1928
|
-
: assertCatalogVersion(catalogInput.version, 'marketplace source metadata.catalog.version'),
|
|
1737
|
+
version: assertCatalogVersion(catalogInput.version, 'marketplace source metadata.catalog.version'),
|
|
1929
1738
|
},
|
|
1930
1739
|
};
|
|
1931
1740
|
}
|
|
@@ -2011,7 +1820,7 @@ export async function assertActualNativePluginCapability(pluginRoot, manifest, m
|
|
|
2011
1820
|
}
|
|
2012
1821
|
}
|
|
2013
1822
|
if (!skills && !slashCommands && !mcpServers && !apps && !bundled) {
|
|
2014
|
-
throw new Error('Invalid plugin manifest: portable-only plugins are not supported
|
|
1823
|
+
throw new Error('Invalid plugin manifest: portable-only plugins are not supported. Declare at least one non-empty native capability.');
|
|
2015
1824
|
}
|
|
2016
1825
|
}
|
|
2017
1826
|
export function validatePluginManifest(pluginRoot, rawManifest, options = {}) {
|
|
@@ -2025,7 +1834,7 @@ export function validatePluginManifest(pluginRoot, rawManifest, options = {}) {
|
|
|
2025
1834
|
'version',
|
|
2026
1835
|
'description',
|
|
2027
1836
|
'dependencies',
|
|
2028
|
-
'
|
|
1837
|
+
'requires',
|
|
2029
1838
|
'skills',
|
|
2030
1839
|
'slashCommands',
|
|
2031
1840
|
'mcpServers',
|
|
@@ -2044,12 +1853,11 @@ export function validatePluginManifest(pluginRoot, rawManifest, options = {}) {
|
|
|
2044
1853
|
name: ensureString(input.name, 'name'),
|
|
2045
1854
|
version: assertStrictSemVer(input.version, 'plugin manifest.version'),
|
|
2046
1855
|
description: ensureString(input.description, 'description'),
|
|
2047
|
-
compatibility: normalizePluginCompatibility(input.compatibility, 'compatibility', {
|
|
2048
|
-
allowMissing: (options.compatibilityMode ?? 'defaulted') === 'defaulted',
|
|
2049
|
-
defaultMinVersion: options.defaultMinVersion,
|
|
2050
|
-
}),
|
|
2051
1856
|
marketplace: normalizeMarketplaceMeta(input.marketplace, 'marketplace'),
|
|
2052
1857
|
};
|
|
1858
|
+
const requires = normalizePluginRequirements(input.requires, 'requires', { allowMissing: true });
|
|
1859
|
+
if (requires !== undefined)
|
|
1860
|
+
manifest.requires = requires;
|
|
2053
1861
|
if (input.dependencies !== undefined) {
|
|
2054
1862
|
const dependencies = ensureStringArray(input.dependencies, 'dependencies');
|
|
2055
1863
|
if (dependencies.length > 64) {
|
|
@@ -2120,7 +1928,7 @@ export function validatePluginManifest(pluginRoot, rawManifest, options = {}) {
|
|
|
2120
1928
|
throw new Error('Invalid buildConfig.cacheable: true requires scripts.node.build.');
|
|
2121
1929
|
}
|
|
2122
1930
|
if (input.config !== undefined) {
|
|
2123
|
-
manifest.config =
|
|
1931
|
+
manifest.config = normalizePluginConfigDefinition(input.config, 'config');
|
|
2124
1932
|
}
|
|
2125
1933
|
if (input.artifacts !== undefined) {
|
|
2126
1934
|
manifest.artifacts = normalizeArtifacts(pluginRoot, input.artifacts, 'artifacts', manifest.config, options.reservedImports);
|
|
@@ -2130,6 +1938,7 @@ export function validatePluginManifest(pluginRoot, rawManifest, options = {}) {
|
|
|
2130
1938
|
throw new Error('Invalid artifacts.targets: agent-skills targets require distributions.agentSkills declaration.');
|
|
2131
1939
|
}
|
|
2132
1940
|
assertManifestPlatformSupport(manifest.name, manifest);
|
|
1941
|
+
assertCodexSharedNativeScopeConsistency(manifest);
|
|
2133
1942
|
return manifest;
|
|
2134
1943
|
}
|
|
2135
1944
|
async function validateDeclaredNativeAssets(pluginRoot, manifest) {
|
|
@@ -2209,6 +2018,14 @@ async function validateSourceSkillInventory(pluginRoot, manifest) {
|
|
|
2209
2018
|
.filter((skillKey) => Boolean(skillKey))
|
|
2210
2019
|
.sort();
|
|
2211
2020
|
const dynamicSkillNameSet = new Set(dynamicSkillNames);
|
|
2021
|
+
for (const [index, target] of (manifest.artifacts?.targets ?? []).entries()) {
|
|
2022
|
+
if (target.target === 'codex.skill'
|
|
2023
|
+
&& target.delivery?.mode === 'codex-native'
|
|
2024
|
+
&& target.skillKey
|
|
2025
|
+
&& staticSkillNameSet.has(target.skillKey)) {
|
|
2026
|
+
throw new Error(`Invalid artifacts.targets[${index}].skillKey: native dynamic Skill "${target.skillKey}" conflicts with a static Skill of the same name.`);
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2212
2029
|
const portableSkillsRoot = manifest.distributions?.agentSkills?.skills
|
|
2213
2030
|
? path.join(pluginRoot, stripDotSlash(manifest.distributions.agentSkills.skills))
|
|
2214
2031
|
: null;
|
|
@@ -2294,18 +2111,7 @@ async function validateSourceJsonNativeAsset(pluginRoot, manifest, field) {
|
|
|
2294
2111
|
}
|
|
2295
2112
|
export async function validateSourcePluginManifest(pluginRoot, rawManifest, options = {}) {
|
|
2296
2113
|
await assertPluginInputTree(pluginRoot);
|
|
2297
|
-
const distributionsInput = rawManifest !== null
|
|
2298
|
-
&& typeof rawManifest === 'object'
|
|
2299
|
-
&& !Array.isArray(rawManifest)
|
|
2300
|
-
? rawManifest.distributions
|
|
2301
|
-
: undefined;
|
|
2302
|
-
const requiresExplicitMinVersion = distributionsInput !== null
|
|
2303
|
-
&& typeof distributionsInput === 'object'
|
|
2304
|
-
&& !Array.isArray(distributionsInput)
|
|
2305
|
-
&& Object.hasOwn(distributionsInput, 'agentSkills');
|
|
2306
2114
|
const manifest = validatePluginManifest(pluginRoot, rawManifest, {
|
|
2307
|
-
compatibilityMode: requiresExplicitMinVersion ? 'required' : 'defaulted',
|
|
2308
|
-
defaultMinVersion: DEFAULT_SOURCE_PLUGIN_AW_MIN_VERSION,
|
|
2309
2115
|
reservedImports: options.reservedImports,
|
|
2310
2116
|
});
|
|
2311
2117
|
const parsedVersion = parseStrictSemVer(manifest.version, 'plugin manifest.version');
|
|
@@ -2342,10 +2148,7 @@ export async function readPluginManifest(pluginRoot, options = {}) {
|
|
|
2342
2148
|
? await validateSourcePluginManifest(pluginRoot, rawManifest, {
|
|
2343
2149
|
reservedImports: options.reservedImports,
|
|
2344
2150
|
})
|
|
2345
|
-
: validatePluginManifest(pluginRoot, rawManifest, {
|
|
2346
|
-
compatibilityMode: 'required',
|
|
2347
|
-
reservedImports: options.reservedImports,
|
|
2348
|
-
});
|
|
2151
|
+
: validatePluginManifest(pluginRoot, rawManifest, { reservedImports: options.reservedImports });
|
|
2349
2152
|
if (mode === 'bundle')
|
|
2350
2153
|
await assertActualNativePluginCapability(pluginRoot, manifest, 'bundle');
|
|
2351
2154
|
return {
|
|
@@ -2443,9 +2246,6 @@ function assertCatalogChannelVersionContract(options) {
|
|
|
2443
2246
|
}
|
|
2444
2247
|
function normalizeCatalogManifestEntry(value, fieldName) {
|
|
2445
2248
|
const input = assertObject(value, fieldName);
|
|
2446
|
-
if (Object.hasOwn(input, 'dependencies')) {
|
|
2447
|
-
throw new Error(`Invalid ${fieldName}: "dependencies" is unsupported.`);
|
|
2448
|
-
}
|
|
2449
2249
|
assertAllowedKeys(input, fieldName, [
|
|
2450
2250
|
'name',
|
|
2451
2251
|
'version',
|
|
@@ -2455,7 +2255,8 @@ function normalizeCatalogManifestEntry(value, fieldName) {
|
|
|
2455
2255
|
'tags',
|
|
2456
2256
|
'strict',
|
|
2457
2257
|
'policy',
|
|
2458
|
-
'
|
|
2258
|
+
'dependencies',
|
|
2259
|
+
'requires',
|
|
2459
2260
|
'source',
|
|
2460
2261
|
'artifactDigest',
|
|
2461
2262
|
'manifestDigest',
|
|
@@ -2467,7 +2268,11 @@ function normalizeCatalogManifestEntry(value, fieldName) {
|
|
|
2467
2268
|
category: ensureString(input.category, `${fieldName}.category`),
|
|
2468
2269
|
strict: ensureBoolean(input.strict, `${fieldName}.strict`),
|
|
2469
2270
|
policy: normalizeMarketplacePolicy(input.policy, `${fieldName}.policy`),
|
|
2470
|
-
|
|
2271
|
+
dependencies: input.dependencies === undefined
|
|
2272
|
+
? []
|
|
2273
|
+
: normalizeComparableStringArray(ensureStringArray(input.dependencies, `${fieldName}.dependencies`))
|
|
2274
|
+
.map((dependency, index) => ensurePluginName(dependency, `${fieldName}.dependencies[${index}]`)),
|
|
2275
|
+
requires: normalizePluginRequirements(input.requires, `${fieldName}.requires`, {
|
|
2471
2276
|
allowMissing: false,
|
|
2472
2277
|
}),
|
|
2473
2278
|
source: normalizeCatalogRelativePath(input.source, `${fieldName}.source`),
|
|
@@ -2491,8 +2296,8 @@ export function normalizeCatalogManifestBase(input, fieldName) {
|
|
|
2491
2296
|
fieldName: `${fieldName}.catalogVersion`,
|
|
2492
2297
|
kind: 'catalog',
|
|
2493
2298
|
});
|
|
2494
|
-
const
|
|
2495
|
-
return { catalogVersion, channel,
|
|
2299
|
+
const requires = normalizePluginRequirements(input.requires, `${fieldName}.requires`, { allowMissing: false });
|
|
2300
|
+
return { catalogVersion, channel, requires };
|
|
2496
2301
|
}
|
|
2497
2302
|
export function normalizeCatalogManifestEntries(rawEntries, base, fieldName) {
|
|
2498
2303
|
if (!Array.isArray(rawEntries)) {
|
|
@@ -2513,16 +2318,10 @@ export function normalizeCatalogManifestEntries(rawEntries, base, fieldName) {
|
|
|
2513
2318
|
kind: 'plugin',
|
|
2514
2319
|
});
|
|
2515
2320
|
}
|
|
2516
|
-
assertPluginMinVersionWithinCatalogBounds({
|
|
2517
|
-
pluginName: entry.name,
|
|
2518
|
-
pluginMinVersion: entry.compatibility.aw.minVersion,
|
|
2519
|
-
catalogMinVersion: base.compatibility.aw.minVersion,
|
|
2520
|
-
fieldName: `${fieldName}.entries.${entry.name}.compatibility.aw.minVersion`,
|
|
2521
|
-
});
|
|
2522
2321
|
}
|
|
2523
2322
|
return entries;
|
|
2524
2323
|
}
|
|
2525
|
-
export function createBundleSourceManifest(plugin) {
|
|
2324
|
+
export function createBundleSourceManifest(plugin, requiredCapabilities) {
|
|
2526
2325
|
const scripts = plugin.manifest.scripts
|
|
2527
2326
|
? structuredClone(plugin.manifest.scripts)
|
|
2528
2327
|
: undefined;
|
|
@@ -2548,9 +2347,12 @@ export function createBundleSourceManifest(plugin) {
|
|
|
2548
2347
|
return {
|
|
2549
2348
|
...plugin.manifest,
|
|
2550
2349
|
buildConfig,
|
|
2551
|
-
|
|
2552
|
-
? structuredClone(plugin.manifest.
|
|
2553
|
-
:
|
|
2350
|
+
distributions: plugin.manifest.distributions
|
|
2351
|
+
? structuredClone(plugin.manifest.distributions)
|
|
2352
|
+
: undefined,
|
|
2353
|
+
requires: requiredCapabilities === undefined
|
|
2354
|
+
? structuredClone(plugin.manifest.requires)
|
|
2355
|
+
: { aw: { capabilities: normalizeAwCapabilities(requiredCapabilities) } },
|
|
2554
2356
|
extensions: plugin.manifest.extensions
|
|
2555
2357
|
? {
|
|
2556
2358
|
instructions: plugin.manifest.extensions.instructions
|
|
@@ -2585,11 +2387,6 @@ export function createPlatformManifestBase(plugin) {
|
|
|
2585
2387
|
name: plugin.manifest.name,
|
|
2586
2388
|
version: plugin.manifest.version,
|
|
2587
2389
|
description: plugin.manifest.description,
|
|
2588
|
-
compatibility: structuredClone(plugin.manifest.compatibility ?? {
|
|
2589
|
-
aw: {
|
|
2590
|
-
minVersion: '0.0.0',
|
|
2591
|
-
},
|
|
2592
|
-
}),
|
|
2593
2390
|
interface: plugin.manifest.interface,
|
|
2594
2391
|
};
|
|
2595
2392
|
}
|