@lesliechan721/aw-plugin-core 0.1.0-beta.4 → 0.1.0-beta.40

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.
Files changed (46) hide show
  1. package/dist/aw-capabilities.d.ts +27 -0
  2. package/dist/aw-capabilities.js +115 -0
  3. package/dist/aw-capabilities.js.map +1 -0
  4. package/dist/catalog-workspace-resolution.d.ts +3 -0
  5. package/dist/catalog-workspace-resolution.js +34 -0
  6. package/dist/catalog-workspace-resolution.js.map +1 -0
  7. package/dist/generated/plugin-manifest-v3.d.ts +1350 -0
  8. package/dist/generated/{plugin-manifest-v1.js → plugin-manifest-v3.js} +1 -1
  9. package/dist/generated/plugin-manifest-v3.js.map +1 -0
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.js +1 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/manifest-contract.d.ts +3 -6
  14. package/dist/manifest-contract.js +227 -406
  15. package/dist/manifest-contract.js.map +1 -1
  16. package/dist/manifest-type-contract.d.ts +2 -2
  17. package/dist/manifest.d.ts +11 -8
  18. package/dist/manifest.js +17 -7
  19. package/dist/manifest.js.map +1 -1
  20. package/dist/plugin-authoring-capabilities.d.ts +21 -0
  21. package/dist/plugin-authoring-capabilities.js +210 -0
  22. package/dist/plugin-authoring-capabilities.js.map +1 -0
  23. package/dist/plugin-config.d.ts +39 -0
  24. package/dist/plugin-config.js +1320 -0
  25. package/dist/plugin-config.js.map +1 -0
  26. package/dist/plugin-requirements.d.ts +34 -0
  27. package/dist/plugin-requirements.js +294 -0
  28. package/dist/plugin-requirements.js.map +1 -0
  29. package/dist/plugin-script-contract.d.ts +11 -12
  30. package/dist/plugin-script-contract.js +42 -64
  31. package/dist/plugin-script-contract.js.map +1 -1
  32. package/dist/runtime-config.d.ts +38 -0
  33. package/dist/runtime-config.js +31 -0
  34. package/dist/runtime-config.js.map +1 -0
  35. package/dist/types.d.ts +249 -61
  36. package/dist/workspace.d.ts +54 -29
  37. package/dist/workspace.js +497 -269
  38. package/dist/workspace.js.map +1 -1
  39. package/package.json +8 -4
  40. package/schemas/plugin-manifest-v3.json +2122 -0
  41. package/dist/generated/plugin-manifest-v1.d.ts +0 -368
  42. package/dist/generated/plugin-manifest-v1.js.map +0 -1
  43. package/dist/plugin-release.d.ts +0 -30
  44. package/dist/plugin-release.js +0 -187
  45. package/dist/plugin-release.js.map +0 -1
  46. 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 { getPluginConfigDependencyKeys, 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 { inferLifecycleScriptRunnerFromPath, isPluginLifecycleOsSelector, isPluginScriptRunner, PLUGIN_AUTO_RUNNER_EXTENSIONS, PLUGIN_LIFECYCLE_EVENTS, PLUGIN_SCRIPT_TIMEOUT_DEFAULT_MS, PLUGIN_SCRIPT_TIMEOUT_MAX_MS, PLUGIN_SCRIPT_TIMEOUT_MIN_MS, resolveLifecycleScriptForOs, } from './plugin-script-contract.js';
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 createPluginCompatibility(minVersion) {
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 createPluginCompatibility(assertStrictSemVer(options.defaultMinVersion ?? DEFAULT_SOURCE_PLUGIN_AW_MIN_VERSION, `${fieldName}.aw.minVersion`));
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`, ['minVersion']);
286
- return createPluginCompatibility(assertStrictSemVer(aw.minVersion, `${fieldName}.aw.minVersion`));
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, options = {}) {
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 (!isPluginScriptRunner(runner)) {
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 normalizeLifecycleScriptDefinition(pluginRoot, value, fieldName) {
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, [...PLUGIN_LIFECYCLE_EVENTS]);
942
- const output = {};
943
- for (const event of PLUGIN_LIFECYCLE_EVENTS) {
944
- if (input[event] === undefined) {
945
- continue;
946
- }
947
- output[event] = normalizeLifecycleScriptDefinition(pluginRoot, input[event], `${fieldName}.${event}`);
948
- }
949
- return output;
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, ['timeoutMs', 'lifecycle', 'helpers', 'node', 'artifacts', 'tests']);
1085
- const output = {
1086
- timeoutMs: input.timeoutMs === undefined
1087
- ? PLUGIN_SCRIPT_TIMEOUT_DEFAULT_MS
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?.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,51 @@ 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 assertSkillRefreshKeysAreNotSensitive(options) {
1190
+ const skillTarget = options.target === 'codex.skill'
1191
+ || options.target === 'claude.skill'
1192
+ || options.target === 'agent-skills.skill';
1193
+ if (!skillTarget)
1194
+ return;
1195
+ for (const [index, key] of options.refreshKeys.entries()) {
1196
+ const field = options.schema?.fields[key];
1197
+ if (field && configFieldContainsSensitiveValue(field)) {
1198
+ const native = options.delivery?.mode === 'codex-native';
1199
+ throw new Error(`${native ? 'codexNativeRenderSensitiveRefreshKey' : 'skillRenderSensitiveRefreshKey'}: ` +
1200
+ `Invalid ${options.fieldName}[${index}]: ` +
1201
+ `${native ? 'codex-native renderers' : 'Skill renderers'} cannot read sensitive config field "${key}".`);
1202
+ }
1203
+ }
1204
+ }
1205
+ function normalizeCodexSkillDelivery(value, fieldName) {
1206
+ const input = assertObject(value, fieldName);
1207
+ const mode = ensureString(input.mode, `${fieldName}.mode`);
1208
+ if (mode === 'aw-extension') {
1209
+ assertAllowedKeys(input, fieldName, ['mode']);
1210
+ return { mode };
1211
+ }
1212
+ if (mode === 'codex-native') {
1213
+ assertAllowedKeys(input, fieldName, ['mode', 'identity', 'renderProfile']);
1214
+ if (input.identity !== 'shared') {
1215
+ throw new Error(`Invalid ${fieldName}.identity: expected "shared".`);
1216
+ }
1217
+ if (input.renderProfile !== 'user') {
1218
+ throw new Error(`Invalid ${fieldName}.renderProfile: expected "user".`);
1219
+ }
1220
+ return { mode, identity: 'shared', renderProfile: 'user' };
1221
+ }
1222
+ throw new Error(`Invalid ${fieldName}.mode: expected "codex-native" or "aw-extension".`);
1223
+ }
1473
1224
  function normalizeDynamicArtifactKind(value, fieldName) {
1474
1225
  const kind = ensureString(value, fieldName);
1475
1226
  if (!DYNAMIC_ARTIFACT_KINDS.has(kind)) {
@@ -1510,16 +1261,24 @@ function normalizeArtifactTargetEntry(pluginRoot, entry, fieldName, schema, rese
1510
1261
  'projectManagedKind',
1511
1262
  'states',
1512
1263
  'refreshKeys',
1264
+ 'delivery',
1513
1265
  'requiresCodexProjectConfig',
1514
1266
  'roleKey',
1515
1267
  'skillKey',
1516
1268
  'sourceDirs',
1517
1269
  'distribution',
1518
1270
  'runtimeImports',
1271
+ 'runtimeConfig',
1519
1272
  ]);
1520
1273
  const id = ensureString(input.id, `${fieldName}.id`);
1521
1274
  validateArtifactId(id, `${fieldName}.id`);
1522
1275
  const target = normalizeArtifactTarget(input.target, `${fieldName}.target`);
1276
+ const delivery = target === 'codex.skill'
1277
+ ? normalizeCodexSkillDelivery(input.delivery, `${fieldName}.delivery`)
1278
+ : undefined;
1279
+ if (target !== 'codex.skill' && input.delivery !== undefined) {
1280
+ throw new Error(`Invalid ${fieldName}.delivery: delivery is only supported for codex.skill targets.`);
1281
+ }
1523
1282
  const portable = target === 'agent-skills.skill';
1524
1283
  const platform = portable ? undefined : normalizePlatformName(input.platform, `${fieldName}.platform`);
1525
1284
  const distribution = portable
@@ -1615,6 +1374,30 @@ function normalizeArtifactTargetEntry(pluginRoot, entry, fieldName, schema, rese
1615
1374
  if (!portable && input.runtimeImports !== undefined) {
1616
1375
  throw new Error(`Invalid ${fieldName}.runtimeImports: runtimeImports are only supported for agent-skills.skill targets.`);
1617
1376
  }
1377
+ const refreshKeys = normalizeArtifactRefreshKeys(input.refreshKeys, `${fieldName}.refreshKeys`, schema);
1378
+ assertSkillRefreshKeysAreNotSensitive({
1379
+ target,
1380
+ delivery,
1381
+ refreshKeys,
1382
+ schema,
1383
+ fieldName: `${fieldName}.refreshKeys`,
1384
+ });
1385
+ let runtimeConfig;
1386
+ if (input.runtimeConfig !== undefined) {
1387
+ if (target !== 'codex.skill' && target !== 'claude.skill') {
1388
+ throw new Error(`Invalid ${fieldName}.runtimeConfig: only native platform Skill targets support installed runtime config.`);
1389
+ }
1390
+ const runtime = assertObject(input.runtimeConfig, `${fieldName}.runtimeConfig`);
1391
+ assertAllowedKeys(runtime, `${fieldName}.runtimeConfig`, ['keys']);
1392
+ const keys = ensureStringArray(runtime.keys, `${fieldName}.runtimeConfig.keys`);
1393
+ if (keys.length === 0 || new Set(keys).size !== keys.length || keys.some((key) => !refreshKeys.includes(key))) {
1394
+ throw new Error(`Invalid ${fieldName}.runtimeConfig.keys: expected unique non-empty keys from refreshKeys.`);
1395
+ }
1396
+ if (!schema || getPluginConfigDependencyKeys(schema, keys).some((key) => !schema.fields[key] || configFieldContainsSensitiveValue(schema.fields[key]))) {
1397
+ throw new Error(`runtimeConfigKeyDenied: Invalid ${fieldName}.runtimeConfig.keys: dependency fields must be declared and non-sensitive.`);
1398
+ }
1399
+ runtimeConfig = { keys: [keys[0], ...keys.slice(1)] };
1400
+ }
1618
1401
  return {
1619
1402
  id,
1620
1403
  platform,
@@ -1624,12 +1407,14 @@ function normalizeArtifactTargetEntry(pluginRoot, entry, fieldName, schema, rese
1624
1407
  kind,
1625
1408
  projectManagedKind,
1626
1409
  states: normalizeArtifactStates(input.states, `${fieldName}.states`),
1627
- refreshKeys: normalizeArtifactRefreshKeys(input.refreshKeys, `${fieldName}.refreshKeys`, schema),
1410
+ refreshKeys,
1411
+ delivery,
1628
1412
  requiresCodexProjectConfig: input.requiresCodexProjectConfig === undefined
1629
1413
  ? undefined
1630
1414
  : ensureBoolean(input.requiresCodexProjectConfig, `${fieldName}.requiresCodexProjectConfig`),
1631
1415
  roleKey,
1632
1416
  skillKey,
1417
+ runtimeConfig,
1633
1418
  sourceDirs,
1634
1419
  runtimeImports: normalizeArtifactRuntimeImports(input.runtimeImports, `${fieldName}.runtimeImports`, reservedImports),
1635
1420
  };
@@ -1644,15 +1429,19 @@ function normalizeArtifacts(pluginRoot, value, fieldName, schema, reservedImport
1644
1429
  if (!Array.isArray(targetsInput)) {
1645
1430
  throw new Error(`Invalid ${fieldName}.targets: expected an array.`);
1646
1431
  }
1647
- const seen = new Set();
1432
+ const seenIdsBySurfaceScope = new Set();
1648
1433
  const seenSkillOwners = new Set();
1434
+ const seenNativeIds = new Set();
1435
+ const seenNativeSkillKeys = new Set();
1649
1436
  return targetsInput.map((entry, index) => {
1650
1437
  const target = normalizeArtifactTargetEntry(pluginRoot, entry, `${fieldName}.targets[${index}]`, schema, reservedImports);
1651
- const key = `${target.id}:${target.target}`;
1652
- if (seen.has(key)) {
1653
- throw new Error(`Invalid ${fieldName}.targets[${index}]: duplicate artifact id + target "${key}".`);
1438
+ for (const scope of target.scopes) {
1439
+ const surfaceScopeId = `${target.platform ?? target.distribution}:${scope}:${target.id}`;
1440
+ if (seenIdsBySurfaceScope.has(surfaceScopeId)) {
1441
+ throw new Error(`Invalid ${fieldName}.targets[${index}]: duplicate artifact id "${target.id}" for surface/scope "${target.platform ?? target.distribution}:${scope}".`);
1442
+ }
1443
+ seenIdsBySurfaceScope.add(surfaceScopeId);
1654
1444
  }
1655
- seen.add(key);
1656
1445
  if ((target.target === 'codex.skill' || target.target === 'claude.skill' || target.target === 'agent-skills.skill') && target.skillKey) {
1657
1446
  for (const scope of target.scopes) {
1658
1447
  const ownerKey = `${target.platform ?? target.distribution}:${scope}:${target.skillKey}`;
@@ -1662,11 +1451,60 @@ function normalizeArtifacts(pluginRoot, value, fieldName, schema, reservedImport
1662
1451
  seenSkillOwners.add(ownerKey);
1663
1452
  }
1664
1453
  }
1454
+ if (target.delivery?.mode === 'codex-native') {
1455
+ if (seenNativeIds.has(target.id)) {
1456
+ throw new Error(`Invalid ${fieldName}.targets[${index}]: duplicate codex-native artifact id "${target.id}" in the shared projection.`);
1457
+ }
1458
+ if (target.skillKey && seenNativeSkillKeys.has(target.skillKey)) {
1459
+ throw new Error(`Invalid ${fieldName}.targets[${index}]: duplicate codex-native skillKey "${target.skillKey}" in the shared projection.`);
1460
+ }
1461
+ seenNativeIds.add(target.id);
1462
+ if (target.skillKey)
1463
+ seenNativeSkillKeys.add(target.skillKey);
1464
+ }
1665
1465
  return target;
1666
1466
  });
1667
1467
  })();
1668
1468
  return { targets };
1669
1469
  }
1470
+ function canonicalScopeSet(scopes) {
1471
+ return [...scopes].sort().join(',');
1472
+ }
1473
+ function assertCodexSharedNativeScopeConsistency(manifest) {
1474
+ if (!supportsPlatform(manifest, 'codex'))
1475
+ return;
1476
+ const nativeComponents = [];
1477
+ if (manifest.skills) {
1478
+ nativeComponents.push({ key: 'skills', scopes: ['project', 'user'] });
1479
+ }
1480
+ if (manifest.mcpServers) {
1481
+ nativeComponents.push({ key: 'mcpServers', scopes: ['project', 'user'] });
1482
+ }
1483
+ if (resolvePlatformHookDefinitions(manifest, 'codex').length > 0) {
1484
+ nativeComponents.push({ key: 'hooks', scopes: ['project', 'user'] });
1485
+ }
1486
+ if (manifest.apps) {
1487
+ nativeComponents.push({ key: 'apps', scopes: ['project', 'user'] });
1488
+ }
1489
+ for (const [index, target] of (manifest.artifacts?.targets ?? []).entries()) {
1490
+ if (target.delivery?.mode === 'codex-native') {
1491
+ nativeComponents.push({
1492
+ key: `artifacts.targets[${index}]`,
1493
+ scopes: [...target.scopes],
1494
+ });
1495
+ }
1496
+ }
1497
+ const expected = nativeComponents[0];
1498
+ if (!expected)
1499
+ return;
1500
+ const expectedScopes = canonicalScopeSet(expected.scopes);
1501
+ const mismatch = nativeComponents.find((component) => canonicalScopeSet(component.scopes) !== expectedScopes);
1502
+ if (mismatch) {
1503
+ throw new Error(`codexSharedNativeScopeMismatch: Codex native component scope sets must match exactly; ` +
1504
+ `${expected.key} has {${expectedScopes}} but ${mismatch.key} has ` +
1505
+ `{${canonicalScopeSet(mismatch.scopes)}}.`);
1506
+ }
1507
+ }
1670
1508
  function normalizePlatformAgentOverrides(value, fieldName, normalizer) {
1671
1509
  const input = assertObject(value, fieldName);
1672
1510
  assertAllowedKeys(input, fieldName, ['claude', 'codex']);
@@ -1909,11 +1747,8 @@ export function validateMarketplaceSourceMeta(rawValue) {
1909
1747
  assertAllowedKeys(ownerInput, 'marketplace source metadata.owner', ['name', 'url']);
1910
1748
  const interfaceInput = assertObject(input.interface, 'marketplace source metadata.interface');
1911
1749
  assertAllowedKeys(interfaceInput, 'marketplace source metadata.interface', ['displayName']);
1912
- const catalogInput = input.catalog === undefined
1913
- ? null
1914
- : assertObject(input.catalog, 'marketplace source metadata.catalog');
1915
- if (catalogInput)
1916
- assertAllowedKeys(catalogInput, 'marketplace source metadata.catalog', ['version']);
1750
+ const catalogInput = assertObject(input.catalog, 'marketplace source metadata.catalog');
1751
+ assertAllowedKeys(catalogInput, 'marketplace source metadata.catalog', ['version']);
1917
1752
  return {
1918
1753
  owner: {
1919
1754
  name: ensureString(ownerInput.name, 'marketplace source metadata.owner.name'),
@@ -1923,9 +1758,7 @@ export function validateMarketplaceSourceMeta(rawValue) {
1923
1758
  displayName: ensureString(interfaceInput.displayName, 'marketplace source metadata.interface.displayName'),
1924
1759
  },
1925
1760
  catalog: {
1926
- version: catalogInput === null
1927
- ? '0.1.0'
1928
- : assertCatalogVersion(catalogInput.version, 'marketplace source metadata.catalog.version'),
1761
+ version: assertCatalogVersion(catalogInput.version, 'marketplace source metadata.catalog.version'),
1929
1762
  },
1930
1763
  };
1931
1764
  }
@@ -2011,7 +1844,7 @@ export async function assertActualNativePluginCapability(pluginRoot, manifest, m
2011
1844
  }
2012
1845
  }
2013
1846
  if (!skills && !slashCommands && !mcpServers && !apps && !bundled) {
2014
- throw new Error('Invalid plugin manifest: portable-only plugins are not supported in V1. Declare at least one non-empty native capability.');
1847
+ throw new Error('Invalid plugin manifest: portable-only plugins are not supported. Declare at least one non-empty native capability.');
2015
1848
  }
2016
1849
  }
2017
1850
  export function validatePluginManifest(pluginRoot, rawManifest, options = {}) {
@@ -2025,7 +1858,7 @@ export function validatePluginManifest(pluginRoot, rawManifest, options = {}) {
2025
1858
  'version',
2026
1859
  'description',
2027
1860
  'dependencies',
2028
- 'compatibility',
1861
+ 'requires',
2029
1862
  'skills',
2030
1863
  'slashCommands',
2031
1864
  'mcpServers',
@@ -2044,12 +1877,11 @@ export function validatePluginManifest(pluginRoot, rawManifest, options = {}) {
2044
1877
  name: ensureString(input.name, 'name'),
2045
1878
  version: assertStrictSemVer(input.version, 'plugin manifest.version'),
2046
1879
  description: ensureString(input.description, 'description'),
2047
- compatibility: normalizePluginCompatibility(input.compatibility, 'compatibility', {
2048
- allowMissing: (options.compatibilityMode ?? 'defaulted') === 'defaulted',
2049
- defaultMinVersion: options.defaultMinVersion,
2050
- }),
2051
1880
  marketplace: normalizeMarketplaceMeta(input.marketplace, 'marketplace'),
2052
1881
  };
1882
+ const requires = normalizePluginRequirements(input.requires, 'requires', { allowMissing: true });
1883
+ if (requires !== undefined)
1884
+ manifest.requires = requires;
2053
1885
  if (input.dependencies !== undefined) {
2054
1886
  const dependencies = ensureStringArray(input.dependencies, 'dependencies');
2055
1887
  if (dependencies.length > 64) {
@@ -2120,7 +1952,7 @@ export function validatePluginManifest(pluginRoot, rawManifest, options = {}) {
2120
1952
  throw new Error('Invalid buildConfig.cacheable: true requires scripts.node.build.');
2121
1953
  }
2122
1954
  if (input.config !== undefined) {
2123
- manifest.config = normalizePluginConfigSchema(input.config, 'config');
1955
+ manifest.config = normalizePluginConfigDefinition(input.config, 'config');
2124
1956
  }
2125
1957
  if (input.artifacts !== undefined) {
2126
1958
  manifest.artifacts = normalizeArtifacts(pluginRoot, input.artifacts, 'artifacts', manifest.config, options.reservedImports);
@@ -2130,6 +1962,7 @@ export function validatePluginManifest(pluginRoot, rawManifest, options = {}) {
2130
1962
  throw new Error('Invalid artifacts.targets: agent-skills targets require distributions.agentSkills declaration.');
2131
1963
  }
2132
1964
  assertManifestPlatformSupport(manifest.name, manifest);
1965
+ assertCodexSharedNativeScopeConsistency(manifest);
2133
1966
  return manifest;
2134
1967
  }
2135
1968
  async function validateDeclaredNativeAssets(pluginRoot, manifest) {
@@ -2209,6 +2042,14 @@ async function validateSourceSkillInventory(pluginRoot, manifest) {
2209
2042
  .filter((skillKey) => Boolean(skillKey))
2210
2043
  .sort();
2211
2044
  const dynamicSkillNameSet = new Set(dynamicSkillNames);
2045
+ for (const [index, target] of (manifest.artifacts?.targets ?? []).entries()) {
2046
+ if (target.target === 'codex.skill'
2047
+ && target.delivery?.mode === 'codex-native'
2048
+ && target.skillKey
2049
+ && staticSkillNameSet.has(target.skillKey)) {
2050
+ throw new Error(`Invalid artifacts.targets[${index}].skillKey: native dynamic Skill "${target.skillKey}" conflicts with a static Skill of the same name.`);
2051
+ }
2052
+ }
2212
2053
  const portableSkillsRoot = manifest.distributions?.agentSkills?.skills
2213
2054
  ? path.join(pluginRoot, stripDotSlash(manifest.distributions.agentSkills.skills))
2214
2055
  : null;
@@ -2294,18 +2135,7 @@ async function validateSourceJsonNativeAsset(pluginRoot, manifest, field) {
2294
2135
  }
2295
2136
  export async function validateSourcePluginManifest(pluginRoot, rawManifest, options = {}) {
2296
2137
  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
2138
  const manifest = validatePluginManifest(pluginRoot, rawManifest, {
2307
- compatibilityMode: requiresExplicitMinVersion ? 'required' : 'defaulted',
2308
- defaultMinVersion: DEFAULT_SOURCE_PLUGIN_AW_MIN_VERSION,
2309
2139
  reservedImports: options.reservedImports,
2310
2140
  });
2311
2141
  const parsedVersion = parseStrictSemVer(manifest.version, 'plugin manifest.version');
@@ -2342,10 +2172,7 @@ export async function readPluginManifest(pluginRoot, options = {}) {
2342
2172
  ? await validateSourcePluginManifest(pluginRoot, rawManifest, {
2343
2173
  reservedImports: options.reservedImports,
2344
2174
  })
2345
- : validatePluginManifest(pluginRoot, rawManifest, {
2346
- compatibilityMode: 'required',
2347
- reservedImports: options.reservedImports,
2348
- });
2175
+ : validatePluginManifest(pluginRoot, rawManifest, { reservedImports: options.reservedImports });
2349
2176
  if (mode === 'bundle')
2350
2177
  await assertActualNativePluginCapability(pluginRoot, manifest, 'bundle');
2351
2178
  return {
@@ -2443,9 +2270,6 @@ function assertCatalogChannelVersionContract(options) {
2443
2270
  }
2444
2271
  function normalizeCatalogManifestEntry(value, fieldName) {
2445
2272
  const input = assertObject(value, fieldName);
2446
- if (Object.hasOwn(input, 'dependencies')) {
2447
- throw new Error(`Invalid ${fieldName}: "dependencies" is unsupported.`);
2448
- }
2449
2273
  assertAllowedKeys(input, fieldName, [
2450
2274
  'name',
2451
2275
  'version',
@@ -2455,7 +2279,8 @@ function normalizeCatalogManifestEntry(value, fieldName) {
2455
2279
  'tags',
2456
2280
  'strict',
2457
2281
  'policy',
2458
- 'compatibility',
2282
+ 'dependencies',
2283
+ 'requires',
2459
2284
  'source',
2460
2285
  'artifactDigest',
2461
2286
  'manifestDigest',
@@ -2467,7 +2292,11 @@ function normalizeCatalogManifestEntry(value, fieldName) {
2467
2292
  category: ensureString(input.category, `${fieldName}.category`),
2468
2293
  strict: ensureBoolean(input.strict, `${fieldName}.strict`),
2469
2294
  policy: normalizeMarketplacePolicy(input.policy, `${fieldName}.policy`),
2470
- compatibility: normalizePluginCompatibility(input.compatibility, `${fieldName}.compatibility`, {
2295
+ dependencies: input.dependencies === undefined
2296
+ ? []
2297
+ : normalizeComparableStringArray(ensureStringArray(input.dependencies, `${fieldName}.dependencies`))
2298
+ .map((dependency, index) => ensurePluginName(dependency, `${fieldName}.dependencies[${index}]`)),
2299
+ requires: normalizePluginRequirements(input.requires, `${fieldName}.requires`, {
2471
2300
  allowMissing: false,
2472
2301
  }),
2473
2302
  source: normalizeCatalogRelativePath(input.source, `${fieldName}.source`),
@@ -2491,8 +2320,8 @@ export function normalizeCatalogManifestBase(input, fieldName) {
2491
2320
  fieldName: `${fieldName}.catalogVersion`,
2492
2321
  kind: 'catalog',
2493
2322
  });
2494
- const compatibility = normalizePluginCompatibility(input.compatibility, `${fieldName}.compatibility`, { allowMissing: false });
2495
- return { catalogVersion, channel, compatibility };
2323
+ const requires = normalizePluginRequirements(input.requires, `${fieldName}.requires`, { allowMissing: false });
2324
+ return { catalogVersion, channel, requires };
2496
2325
  }
2497
2326
  export function normalizeCatalogManifestEntries(rawEntries, base, fieldName) {
2498
2327
  if (!Array.isArray(rawEntries)) {
@@ -2513,16 +2342,10 @@ export function normalizeCatalogManifestEntries(rawEntries, base, fieldName) {
2513
2342
  kind: 'plugin',
2514
2343
  });
2515
2344
  }
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
2345
  }
2523
2346
  return entries;
2524
2347
  }
2525
- export function createBundleSourceManifest(plugin) {
2348
+ export function createBundleSourceManifest(plugin, requiredCapabilities) {
2526
2349
  const scripts = plugin.manifest.scripts
2527
2350
  ? structuredClone(plugin.manifest.scripts)
2528
2351
  : undefined;
@@ -2548,9 +2371,12 @@ export function createBundleSourceManifest(plugin) {
2548
2371
  return {
2549
2372
  ...plugin.manifest,
2550
2373
  buildConfig,
2551
- compatibility: plugin.manifest.compatibility
2552
- ? structuredClone(plugin.manifest.compatibility)
2553
- : createPluginCompatibility(DEFAULT_SOURCE_PLUGIN_AW_MIN_VERSION),
2374
+ distributions: plugin.manifest.distributions
2375
+ ? structuredClone(plugin.manifest.distributions)
2376
+ : undefined,
2377
+ requires: requiredCapabilities === undefined
2378
+ ? structuredClone(plugin.manifest.requires)
2379
+ : { aw: { capabilities: normalizeAwCapabilities(requiredCapabilities) } },
2554
2380
  extensions: plugin.manifest.extensions
2555
2381
  ? {
2556
2382
  instructions: plugin.manifest.extensions.instructions
@@ -2585,11 +2411,6 @@ export function createPlatformManifestBase(plugin) {
2585
2411
  name: plugin.manifest.name,
2586
2412
  version: plugin.manifest.version,
2587
2413
  description: plugin.manifest.description,
2588
- compatibility: structuredClone(plugin.manifest.compatibility ?? {
2589
- aw: {
2590
- minVersion: '0.0.0',
2591
- },
2592
- }),
2593
2414
  interface: plugin.manifest.interface,
2594
2415
  };
2595
2416
  }