@lesliechan721/aw-plugin-core 0.1.0-beta.6 → 0.1.0-beta.60

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 (58) hide show
  1. package/dist/aw-capabilities.d.ts +27 -0
  2. package/dist/aw-capabilities.js +117 -0
  3. package/dist/aw-capabilities.js.map +1 -0
  4. package/dist/catalog-file-modes.d.ts +5 -0
  5. package/dist/catalog-file-modes.js +67 -0
  6. package/dist/catalog-file-modes.js.map +1 -0
  7. package/dist/catalog-workspace-resolution.d.ts +3 -0
  8. package/dist/catalog-workspace-resolution.js +34 -0
  9. package/dist/catalog-workspace-resolution.js.map +1 -0
  10. package/dist/generated/plugin-manifest-v3.d.ts +1532 -0
  11. package/dist/generated/{plugin-manifest-v1.js → plugin-manifest-v3.js} +1 -1
  12. package/dist/generated/plugin-manifest-v3.js.map +1 -0
  13. package/dist/index.d.ts +3 -2
  14. package/dist/index.js +2 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/managed-commands.d.ts +167 -0
  17. package/dist/managed-commands.js +463 -0
  18. package/dist/managed-commands.js.map +1 -0
  19. package/dist/manifest-contract.d.ts +3 -6
  20. package/dist/manifest-contract.js +271 -407
  21. package/dist/manifest-contract.js.map +1 -1
  22. package/dist/manifest-type-contract.d.ts +2 -2
  23. package/dist/manifest.d.ts +12 -8
  24. package/dist/manifest.js +18 -7
  25. package/dist/manifest.js.map +1 -1
  26. package/dist/native-runtime/reader-runtime.mjs +34 -0
  27. package/dist/native-runtime/reader.d.mts +6 -0
  28. package/dist/native-runtime/reader.mjs +11 -0
  29. package/dist/native-tools.d.ts +48 -0
  30. package/dist/native-tools.js +192 -0
  31. package/dist/native-tools.js.map +1 -0
  32. package/dist/plugin-authoring-capabilities.d.ts +1 -1
  33. package/dist/plugin-authoring-capabilities.js +133 -26
  34. package/dist/plugin-authoring-capabilities.js.map +1 -1
  35. package/dist/plugin-config.d.ts +47 -0
  36. package/dist/plugin-config.js +1355 -0
  37. package/dist/plugin-config.js.map +1 -0
  38. package/dist/plugin-requirements.d.ts +34 -0
  39. package/dist/plugin-requirements.js +300 -0
  40. package/dist/plugin-requirements.js.map +1 -0
  41. package/dist/plugin-script-contract.d.ts +11 -12
  42. package/dist/plugin-script-contract.js +44 -71
  43. package/dist/plugin-script-contract.js.map +1 -1
  44. package/dist/runtime-config.d.ts +38 -0
  45. package/dist/runtime-config.js +31 -0
  46. package/dist/runtime-config.js.map +1 -0
  47. package/dist/types.d.ts +266 -73
  48. package/dist/workspace.d.ts +37 -9
  49. package/dist/workspace.js +419 -191
  50. package/dist/workspace.js.map +1 -1
  51. package/package.json +16 -6
  52. package/schemas/plugin-manifest-v3.json +2655 -0
  53. package/dist/generated/plugin-manifest-v1.d.ts +0 -368
  54. package/dist/generated/plugin-manifest-v1.js.map +0 -1
  55. package/dist/plugin-release.d.ts +0 -30
  56. package/dist/plugin-release.js +0 -187
  57. package/dist/plugin-release.js.map +0 -1
  58. package/schemas/plugin-manifest-v1.json +0 -329
@@ -1,14 +1,18 @@
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';
8
+ import { loadPluginNativeToolClosure, normalizeNativeTools, validateNativeToolClosureFiles, validateNativeToolsManifest } from './native-tools.js';
6
9
  import { parsePermissionCommand } from './permission-command.js';
10
+ import { normalizeManagedCommands, normalizeManagedCommandPermission, validateManagedCommandManifest, validateManagedCommandSources } from './managed-commands.js';
7
11
  import { applyFileRules } from './file-rules.js';
8
12
  import { resolvePlatformEntries } from './platform-entries.js';
9
13
  import { validatePortableSkillDirectoriesRoot, validatePortableSkillName, } from './portable-skill-validation.js';
10
14
  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';
15
+ import { PLUGIN_SCRIPT_TIMEOUT_MAX_MS, PLUGIN_SCRIPT_TIMEOUT_MIN_MS, } from './plugin-script-contract.js';
12
16
  import { DEFAULT_SLASH_COMMANDS_PATH, resolveSlashCommands, } from './slash-commands.js';
13
17
  import { isSkillAuthorOnlyPath } from './skill-authoring.js';
14
18
  import { assertPluginInputTree, validateRuntimeExportSource } from './runtime-export-source.js';
@@ -29,7 +33,6 @@ const PLATFORM_PRODUCT_BY_NAME = {
29
33
  };
30
34
  const PLUGIN_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
31
35
  const DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/;
32
- export const DEFAULT_SOURCE_PLUGIN_AW_MIN_VERSION = '0.0.0';
33
36
  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
37
  export const STRICT_SEMVER_VALIDATION_CODE = 'invalidStrictSemVer';
35
38
  export class StrictSemVerValidationError extends Error {
@@ -220,14 +223,18 @@ function ensureStringArray(value, fieldName) {
220
223
  return value.map((entry, index) => ensureString(entry, `${fieldName}[${index}]`));
221
224
  }
222
225
  function normalizePermissionCommandArray(value, fieldName) {
223
- if (value === undefined) {
226
+ if (value === undefined)
224
227
  return undefined;
225
- }
226
- const entries = ensureStringArray(value, fieldName);
227
- for (const [index, entry] of entries.entries()) {
228
- parsePermissionCommand(entry, `${fieldName}[${index}]`);
229
- }
230
- return entries;
228
+ if (!Array.isArray(value))
229
+ throw new Error(`Invalid ${fieldName}: expected an array.`);
230
+ return value.map((entry, index) => {
231
+ const label = `${fieldName}[${index}]`;
232
+ if (typeof entry === 'string') {
233
+ parsePermissionCommand(entry, label);
234
+ return entry;
235
+ }
236
+ return normalizeManagedCommandPermission(entry, label);
237
+ });
231
238
  }
232
239
  function normalizeComparableStringArray(value) {
233
240
  if (!value) {
@@ -265,30 +272,26 @@ function assertCatalogChannel(value, fieldName) {
265
272
  }
266
273
  return channel;
267
274
  }
268
- function createPluginCompatibility(minVersion) {
269
- return {
270
- aw: {
271
- minVersion,
272
- },
273
- };
274
- }
275
- function normalizePluginCompatibility(value, fieldName, options) {
275
+ function normalizePluginRequirements(value, fieldName, options) {
276
276
  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.`);
277
+ if (options.allowMissing)
278
+ return undefined;
279
+ throw new Error(`Invalid ${fieldName}: expected an object.`);
281
280
  }
282
281
  const input = assertObject(value, fieldName);
283
282
  assertAllowedKeys(input, fieldName, ['aw']);
284
283
  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}.`);
284
+ assertAllowedKeys(aw, `${fieldName}.aw`, ['capabilities']);
285
+ if (aw.capabilities !== undefined && !Array.isArray(aw.capabilities)) {
286
+ throw new Error(`Invalid ${fieldName}.aw.capabilities: expected an array.`);
291
287
  }
288
+ return {
289
+ aw: {
290
+ ...(aw.capabilities === undefined
291
+ ? {}
292
+ : { capabilities: normalizeAwCapabilities(aw.capabilities, `${fieldName}.aw.capabilities`) }),
293
+ },
294
+ };
292
295
  }
293
296
  function normalizeMarketplaceProducts(value, fieldName) {
294
297
  if (value === undefined) {
@@ -619,15 +622,20 @@ function normalizeRuntimeExports(pluginRoot, value, fieldName) {
619
622
  return value.map((entry, index) => {
620
623
  const entryField = `${fieldName}[${index}]`;
621
624
  const input = assertObject(entry, entryField);
622
- assertAllowedKeys(input, entryField, ['name', 'source']);
625
+ assertAllowedKeys(input, entryField, ['name', 'source', 'requires']);
623
626
  const name = normalizeRuntimeExportName(input.name, `${entryField}.name`);
624
627
  if (names.has(name)) {
625
628
  throw new Error(`Invalid ${fieldName}: duplicate export name "${name}".`);
626
629
  }
627
630
  names.add(name);
631
+ const requires = normalizePluginRequirements(input.requires, `${entryField}.requires`, { allowMissing: false });
632
+ if (requires.aw.capabilities === undefined) {
633
+ throw new Error(`Invalid ${entryField}.requires.aw.capabilities: expected an array.`);
634
+ }
628
635
  return {
629
636
  name,
630
637
  source: normalizeCanonicalPluginFilePath(pluginRoot, input.source, `${entryField}.source`),
638
+ requires: { aw: { capabilities: requires.aw.capabilities } },
631
639
  };
632
640
  });
633
641
  }
@@ -838,12 +846,6 @@ function ensureBoolean(value, fieldName) {
838
846
  }
839
847
  return value;
840
848
  }
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
849
  function normalizeScriptTimeout(value, fieldName) {
848
850
  if (typeof value !== 'number' ||
849
851
  !Number.isInteger(value) ||
@@ -859,11 +861,6 @@ function assertNodeRunnablePath(value, fieldName) {
859
861
  throw new Error(`Invalid ${fieldName}: expected a Node runnable file extension (.js, .mjs, or .cjs).`);
860
862
  }
861
863
  }
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
864
  function assertNotInlineCommandLike(value, fieldName) {
868
865
  if (/\s|[;&|<>`]/.test(value) || value.includes('$(')) {
869
866
  throw new Error(`Invalid ${fieldName}: inline command strings are not allowed.`);
@@ -874,7 +871,7 @@ function normalizePluginScriptPath(pluginRoot, value, fieldName) {
874
871
  assertNotInlineCommandLike(rawPath, fieldName);
875
872
  return normalizePluginRelativePath(pluginRoot, rawPath, fieldName);
876
873
  }
877
- function normalizeScriptEntry(pluginRoot, value, fieldName, options = {}) {
874
+ function normalizeScriptEntry(pluginRoot, value, fieldName) {
878
875
  const input = assertObject(value, fieldName);
879
876
  if ('command' in input) {
880
877
  throw new Error(`Invalid ${fieldName}: inline command strings are not allowed.`);
@@ -884,69 +881,29 @@ function normalizeScriptEntry(pluginRoot, value, fieldName, options = {}) {
884
881
  }
885
882
  assertAllowedKeys(input, fieldName, ['runner', 'path', 'args']);
886
883
  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') {
884
+ if (runner !== 'node') {
891
885
  throw new Error(`Invalid ${fieldName}.runner: expected "node".`);
892
886
  }
893
887
  const scriptPath = normalizePluginScriptPath(pluginRoot, input.path, `${fieldName}.path`);
894
- if (runner === 'auto') {
895
- assertAutoRunnablePath(scriptPath, `${fieldName}.path`);
896
- }
897
888
  return {
898
- runner,
889
+ runner: 'node',
899
890
  path: scriptPath,
900
891
  args: input.args === undefined
901
892
  ? undefined
902
893
  : ensureStringArray(input.args, `${fieldName}.args`),
903
894
  };
904
895
  }
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) {
896
+ function normalizePreApplyScript(pluginRoot, value, fieldName) {
940
897
  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;
898
+ assertAllowedKeys(input, fieldName, ['path', 'timeoutMs']);
899
+ const scriptPath = normalizePluginScriptPath(pluginRoot, input.path, `${fieldName}.path`);
900
+ assertNodeRunnablePath(scriptPath, `${fieldName}.path`);
901
+ return {
902
+ path: scriptPath,
903
+ ...(input.timeoutMs === undefined
904
+ ? {}
905
+ : { timeoutMs: normalizeScriptTimeout(input.timeoutMs, `${fieldName}.timeoutMs`) }),
906
+ };
950
907
  }
951
908
  function normalizeScriptHelpers(pluginRoot, value, fieldName) {
952
909
  const input = assertObject(value, fieldName);
@@ -959,9 +916,7 @@ function normalizeScriptHelpers(pluginRoot, value, fieldName) {
959
916
  return { config };
960
917
  }
961
918
  function normalizeNodeBuildScript(pluginRoot, value, fieldName) {
962
- const entry = normalizeScriptEntry(pluginRoot, value, fieldName, {
963
- requireNodeRunner: true,
964
- });
919
+ const entry = normalizeScriptEntry(pluginRoot, value, fieldName);
965
920
  assertNodeRunnablePath(entry.path, `${fieldName}.path`);
966
921
  return {
967
922
  runner: 'node',
@@ -1060,9 +1015,7 @@ function normalizeScriptTests(value, fieldName) {
1060
1015
  };
1061
1016
  }
1062
1017
  function normalizeArtifactRenderScript(pluginRoot, value, fieldName) {
1063
- const entry = normalizeScriptEntry(pluginRoot, value, fieldName, {
1064
- requireNodeRunner: true,
1065
- });
1018
+ const entry = normalizeScriptEntry(pluginRoot, value, fieldName);
1066
1019
  assertNodeRunnablePath(entry.path, `${fieldName}.path`);
1067
1020
  return {
1068
1021
  runner: 'node',
@@ -1081,14 +1034,10 @@ function normalizeArtifactScripts(pluginRoot, value, fieldName) {
1081
1034
  }
1082
1035
  function normalizePluginScripts(pluginRoot, value, fieldName) {
1083
1036
  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`);
1037
+ assertAllowedKeys(input, fieldName, ['preApply', 'helpers', 'node', 'artifacts', 'tests']);
1038
+ const output = {};
1039
+ if (input.preApply !== undefined) {
1040
+ output.preApply = normalizePreApplyScript(pluginRoot, input.preApply, `${fieldName}.preApply`);
1092
1041
  }
1093
1042
  if (input.helpers !== undefined) {
1094
1043
  output.helpers = normalizeScriptHelpers(pluginRoot, input.helpers, `${fieldName}.helpers`);
@@ -1104,254 +1053,6 @@ function normalizePluginScripts(pluginRoot, value, fieldName) {
1104
1053
  }
1105
1054
  return output;
1106
1055
  }
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
1056
  const ARTIFACT_TARGETS = new Set([
1356
1057
  'codex.developerInstructionsBlock',
1357
1058
  'codex.agentRole',
@@ -1372,6 +1073,8 @@ const ARTIFACT_KIND_BY_TARGET = {
1372
1073
  'codex.skill': 'skill',
1373
1074
  'claude.skill': 'skill',
1374
1075
  'agent-skills.skill': 'skill',
1076
+ 'aw.command-set': 'commandSet',
1077
+ 'aw.command-package': 'commandPackage',
1375
1078
  };
1376
1079
  function normalizeArtifactRuntimeImports(value, fieldName, reservedImports) {
1377
1080
  if (value === undefined)
@@ -1394,13 +1097,24 @@ function normalizeArtifactRuntimeImports(value, fieldName, reservedImports) {
1394
1097
  const resolvedInput = assertObject(input.resolved, `${entryField}.resolved`);
1395
1098
  assertAllowedKeys(resolvedInput, `${entryField}.resolved`, [
1396
1099
  'path', 'digest', 'providerPluginId', 'providerVersion', 'source',
1100
+ 'requiredCapabilities', 'capabilitySetDigest',
1397
1101
  ]);
1102
+ if (!Array.isArray(resolvedInput.requiredCapabilities)) {
1103
+ throw new Error(`Invalid ${entryField}.resolved.requiredCapabilities: expected an array.`);
1104
+ }
1105
+ const requiredCapabilities = normalizeAwCapabilities(resolvedInput.requiredCapabilities, `${entryField}.resolved.requiredCapabilities`);
1106
+ const capabilitySetDigest = ensureDigestString(resolvedInput.capabilitySetDigest, `${entryField}.resolved.capabilitySetDigest`);
1107
+ if (capabilitySetDigest !== computeAwCapabilitySetDigest(requiredCapabilities)) {
1108
+ throw new Error(`Invalid ${entryField}.resolved.capabilitySetDigest: digest does not match requiredCapabilities.`);
1109
+ }
1398
1110
  resolved = {
1399
1111
  path: normalizePluginChildPath('', ensureString(resolvedInput.path, `${entryField}.resolved.path`), `${entryField}.resolved.path`),
1400
1112
  digest: ensureDigestString(resolvedInput.digest, `${entryField}.resolved.digest`),
1401
1113
  providerPluginId: ensureString(resolvedInput.providerPluginId, `${entryField}.resolved.providerPluginId`),
1402
1114
  providerVersion: assertStrictSemVer(resolvedInput.providerVersion, `${entryField}.resolved.providerVersion`),
1403
1115
  source: ensureString(resolvedInput.source, `${entryField}.resolved.source`),
1116
+ requiredCapabilities,
1117
+ capabilitySetDigest,
1404
1118
  };
1405
1119
  }
1406
1120
  return {
@@ -1458,7 +1172,7 @@ function normalizeArtifactStates(value, fieldName) {
1458
1172
  }
1459
1173
  function normalizeArtifactRefreshKeys(value, fieldName, schema) {
1460
1174
  const refreshKeys = ensureStringArray(value, fieldName);
1461
- const schemaKeys = new Set(Object.keys(schema?.schema ?? {}));
1175
+ const schemaKeys = new Set(Object.keys(schema?.fields ?? {}));
1462
1176
  const normalized = [];
1463
1177
  for (const [index, key] of refreshKeys.entries()) {
1464
1178
  if (!schemaKeys.has(key)) {
@@ -1470,6 +1184,51 @@ function normalizeArtifactRefreshKeys(value, fieldName, schema) {
1470
1184
  }
1471
1185
  return normalized;
1472
1186
  }
1187
+ function configFieldContainsSensitiveValue(field) {
1188
+ if (field.sensitive === true)
1189
+ return true;
1190
+ if (field.type !== 'object')
1191
+ return false;
1192
+ return ((field.properties
1193
+ ? Object.values(field.properties).some((child) => child.sensitive === true)
1194
+ : false)
1195
+ || (field.additionalProperties !== false && field.additionalProperties?.sensitive === true));
1196
+ }
1197
+ function assertSkillRefreshKeysAreNotSensitive(options) {
1198
+ const skillTarget = options.target === 'codex.skill'
1199
+ || options.target === 'claude.skill'
1200
+ || options.target === 'agent-skills.skill';
1201
+ if (!skillTarget)
1202
+ return;
1203
+ for (const [index, key] of options.refreshKeys.entries()) {
1204
+ const field = options.schema?.fields[key];
1205
+ if (field && configFieldContainsSensitiveValue(field)) {
1206
+ const native = options.delivery?.mode === 'codex-native';
1207
+ throw new Error(`${native ? 'codexNativeRenderSensitiveRefreshKey' : 'skillRenderSensitiveRefreshKey'}: ` +
1208
+ `Invalid ${options.fieldName}[${index}]: ` +
1209
+ `${native ? 'codex-native renderers' : 'Skill renderers'} cannot read sensitive config field "${key}".`);
1210
+ }
1211
+ }
1212
+ }
1213
+ function normalizeCodexSkillDelivery(value, fieldName) {
1214
+ const input = assertObject(value, fieldName);
1215
+ const mode = ensureString(input.mode, `${fieldName}.mode`);
1216
+ if (mode === 'aw-extension') {
1217
+ assertAllowedKeys(input, fieldName, ['mode']);
1218
+ return { mode };
1219
+ }
1220
+ if (mode === 'codex-native') {
1221
+ assertAllowedKeys(input, fieldName, ['mode', 'identity', 'renderProfile']);
1222
+ if (input.identity !== 'shared') {
1223
+ throw new Error(`Invalid ${fieldName}.identity: expected "shared".`);
1224
+ }
1225
+ if (input.renderProfile !== 'user') {
1226
+ throw new Error(`Invalid ${fieldName}.renderProfile: expected "user".`);
1227
+ }
1228
+ return { mode, identity: 'shared', renderProfile: 'user' };
1229
+ }
1230
+ throw new Error(`Invalid ${fieldName}.mode: expected "codex-native" or "aw-extension".`);
1231
+ }
1473
1232
  function normalizeDynamicArtifactKind(value, fieldName) {
1474
1233
  const kind = ensureString(value, fieldName);
1475
1234
  if (!DYNAMIC_ARTIFACT_KINDS.has(kind)) {
@@ -1510,16 +1269,27 @@ function normalizeArtifactTargetEntry(pluginRoot, entry, fieldName, schema, rese
1510
1269
  'projectManagedKind',
1511
1270
  'states',
1512
1271
  'refreshKeys',
1272
+ 'delivery',
1513
1273
  'requiresCodexProjectConfig',
1514
1274
  'roleKey',
1515
1275
  'skillKey',
1516
1276
  'sourceDirs',
1517
1277
  'distribution',
1518
1278
  'runtimeImports',
1279
+ 'runtimeConfig',
1519
1280
  ]);
1520
1281
  const id = ensureString(input.id, `${fieldName}.id`);
1521
1282
  validateArtifactId(id, `${fieldName}.id`);
1283
+ if (/^command-(?:set|package)\./u.test(id)) {
1284
+ throw new Error(`Invalid ${fieldName}.id: reserved managed command target prefix.`);
1285
+ }
1522
1286
  const target = normalizeArtifactTarget(input.target, `${fieldName}.target`);
1287
+ const delivery = target === 'codex.skill'
1288
+ ? normalizeCodexSkillDelivery(input.delivery, `${fieldName}.delivery`)
1289
+ : undefined;
1290
+ if (target !== 'codex.skill' && input.delivery !== undefined) {
1291
+ throw new Error(`Invalid ${fieldName}.delivery: delivery is only supported for codex.skill targets.`);
1292
+ }
1523
1293
  const portable = target === 'agent-skills.skill';
1524
1294
  const platform = portable ? undefined : normalizePlatformName(input.platform, `${fieldName}.platform`);
1525
1295
  const distribution = portable
@@ -1615,6 +1385,30 @@ function normalizeArtifactTargetEntry(pluginRoot, entry, fieldName, schema, rese
1615
1385
  if (!portable && input.runtimeImports !== undefined) {
1616
1386
  throw new Error(`Invalid ${fieldName}.runtimeImports: runtimeImports are only supported for agent-skills.skill targets.`);
1617
1387
  }
1388
+ const refreshKeys = normalizeArtifactRefreshKeys(input.refreshKeys, `${fieldName}.refreshKeys`, schema);
1389
+ assertSkillRefreshKeysAreNotSensitive({
1390
+ target,
1391
+ delivery,
1392
+ refreshKeys,
1393
+ schema,
1394
+ fieldName: `${fieldName}.refreshKeys`,
1395
+ });
1396
+ let runtimeConfig;
1397
+ if (input.runtimeConfig !== undefined) {
1398
+ if (target !== 'codex.skill' && target !== 'claude.skill') {
1399
+ throw new Error(`Invalid ${fieldName}.runtimeConfig: only native platform Skill targets support installed runtime config.`);
1400
+ }
1401
+ const runtime = assertObject(input.runtimeConfig, `${fieldName}.runtimeConfig`);
1402
+ assertAllowedKeys(runtime, `${fieldName}.runtimeConfig`, ['keys']);
1403
+ const keys = ensureStringArray(runtime.keys, `${fieldName}.runtimeConfig.keys`);
1404
+ if (keys.length === 0 || new Set(keys).size !== keys.length || keys.some((key) => !refreshKeys.includes(key))) {
1405
+ throw new Error(`Invalid ${fieldName}.runtimeConfig.keys: expected unique non-empty keys from refreshKeys.`);
1406
+ }
1407
+ if (!schema || getPluginConfigDependencyKeys(schema, keys).some((key) => !schema.fields[key] || configFieldContainsSensitiveValue(schema.fields[key]))) {
1408
+ throw new Error(`runtimeConfigKeyDenied: Invalid ${fieldName}.runtimeConfig.keys: dependency fields must be declared and non-sensitive.`);
1409
+ }
1410
+ runtimeConfig = { keys: [keys[0], ...keys.slice(1)] };
1411
+ }
1618
1412
  return {
1619
1413
  id,
1620
1414
  platform,
@@ -1624,12 +1418,14 @@ function normalizeArtifactTargetEntry(pluginRoot, entry, fieldName, schema, rese
1624
1418
  kind,
1625
1419
  projectManagedKind,
1626
1420
  states: normalizeArtifactStates(input.states, `${fieldName}.states`),
1627
- refreshKeys: normalizeArtifactRefreshKeys(input.refreshKeys, `${fieldName}.refreshKeys`, schema),
1421
+ refreshKeys,
1422
+ delivery,
1628
1423
  requiresCodexProjectConfig: input.requiresCodexProjectConfig === undefined
1629
1424
  ? undefined
1630
1425
  : ensureBoolean(input.requiresCodexProjectConfig, `${fieldName}.requiresCodexProjectConfig`),
1631
1426
  roleKey,
1632
1427
  skillKey,
1428
+ runtimeConfig,
1633
1429
  sourceDirs,
1634
1430
  runtimeImports: normalizeArtifactRuntimeImports(input.runtimeImports, `${fieldName}.runtimeImports`, reservedImports),
1635
1431
  };
@@ -1644,15 +1440,19 @@ function normalizeArtifacts(pluginRoot, value, fieldName, schema, reservedImport
1644
1440
  if (!Array.isArray(targetsInput)) {
1645
1441
  throw new Error(`Invalid ${fieldName}.targets: expected an array.`);
1646
1442
  }
1647
- const seen = new Set();
1443
+ const seenIdsBySurfaceScope = new Set();
1648
1444
  const seenSkillOwners = new Set();
1445
+ const seenNativeIds = new Set();
1446
+ const seenNativeSkillKeys = new Set();
1649
1447
  return targetsInput.map((entry, index) => {
1650
1448
  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}".`);
1449
+ for (const scope of target.scopes) {
1450
+ const surfaceScopeId = `${target.platform ?? target.distribution}:${scope}:${target.id}`;
1451
+ if (seenIdsBySurfaceScope.has(surfaceScopeId)) {
1452
+ throw new Error(`Invalid ${fieldName}.targets[${index}]: duplicate artifact id "${target.id}" for surface/scope "${target.platform ?? target.distribution}:${scope}".`);
1453
+ }
1454
+ seenIdsBySurfaceScope.add(surfaceScopeId);
1654
1455
  }
1655
- seen.add(key);
1656
1456
  if ((target.target === 'codex.skill' || target.target === 'claude.skill' || target.target === 'agent-skills.skill') && target.skillKey) {
1657
1457
  for (const scope of target.scopes) {
1658
1458
  const ownerKey = `${target.platform ?? target.distribution}:${scope}:${target.skillKey}`;
@@ -1662,11 +1462,60 @@ function normalizeArtifacts(pluginRoot, value, fieldName, schema, reservedImport
1662
1462
  seenSkillOwners.add(ownerKey);
1663
1463
  }
1664
1464
  }
1465
+ if (target.delivery?.mode === 'codex-native') {
1466
+ if (seenNativeIds.has(target.id)) {
1467
+ throw new Error(`Invalid ${fieldName}.targets[${index}]: duplicate codex-native artifact id "${target.id}" in the shared projection.`);
1468
+ }
1469
+ if (target.skillKey && seenNativeSkillKeys.has(target.skillKey)) {
1470
+ throw new Error(`Invalid ${fieldName}.targets[${index}]: duplicate codex-native skillKey "${target.skillKey}" in the shared projection.`);
1471
+ }
1472
+ seenNativeIds.add(target.id);
1473
+ if (target.skillKey)
1474
+ seenNativeSkillKeys.add(target.skillKey);
1475
+ }
1665
1476
  return target;
1666
1477
  });
1667
1478
  })();
1668
1479
  return { targets };
1669
1480
  }
1481
+ function canonicalScopeSet(scopes) {
1482
+ return [...scopes].sort().join(',');
1483
+ }
1484
+ function assertCodexSharedNativeScopeConsistency(manifest) {
1485
+ if (!supportsPlatform(manifest, 'codex'))
1486
+ return;
1487
+ const nativeComponents = [];
1488
+ if (manifest.skills) {
1489
+ nativeComponents.push({ key: 'skills', scopes: ['project', 'user'] });
1490
+ }
1491
+ if (manifest.mcpServers) {
1492
+ nativeComponents.push({ key: 'mcpServers', scopes: ['project', 'user'] });
1493
+ }
1494
+ if (resolvePlatformHookDefinitions(manifest, 'codex').length > 0) {
1495
+ nativeComponents.push({ key: 'hooks', scopes: ['project', 'user'] });
1496
+ }
1497
+ if (manifest.apps) {
1498
+ nativeComponents.push({ key: 'apps', scopes: ['project', 'user'] });
1499
+ }
1500
+ for (const [index, target] of (manifest.artifacts?.targets ?? []).entries()) {
1501
+ if (target.delivery?.mode === 'codex-native') {
1502
+ nativeComponents.push({
1503
+ key: `artifacts.targets[${index}]`,
1504
+ scopes: [...target.scopes],
1505
+ });
1506
+ }
1507
+ }
1508
+ const expected = nativeComponents[0];
1509
+ if (!expected)
1510
+ return;
1511
+ const expectedScopes = canonicalScopeSet(expected.scopes);
1512
+ const mismatch = nativeComponents.find((component) => canonicalScopeSet(component.scopes) !== expectedScopes);
1513
+ if (mismatch) {
1514
+ throw new Error(`codexSharedNativeScopeMismatch: Codex native component scope sets must match exactly; ` +
1515
+ `${expected.key} has {${expectedScopes}} but ${mismatch.key} has ` +
1516
+ `{${canonicalScopeSet(mismatch.scopes)}}.`);
1517
+ }
1518
+ }
1670
1519
  function normalizePlatformAgentOverrides(value, fieldName, normalizer) {
1671
1520
  const input = assertObject(value, fieldName);
1672
1521
  assertAllowedKeys(input, fieldName, ['claude', 'codex']);
@@ -1726,7 +1575,7 @@ function normalizePermissionEntry(entry, fieldName) {
1726
1575
  agentOverrides: input.agentOverrides === undefined
1727
1576
  ? undefined
1728
1577
  : normalizePlatformAgentOverrides(input.agentOverrides, `${fieldName}.agentOverrides`, (overrideInput, overrideFieldName) => {
1729
- assertAllowedKeys(overrideInput, overrideFieldName, ['name', 'allow', 'ask', 'deny']);
1578
+ assertAllowedKeys(overrideInput, overrideFieldName, overrideFieldName.endsWith('.claude') ? ['name', 'allow', 'ask', 'deny', 'sandbox'] : ['name', 'allow', 'ask', 'deny']);
1730
1579
  return {
1731
1580
  name: overrideInput.name === undefined
1732
1581
  ? undefined
@@ -1734,6 +1583,15 @@ function normalizePermissionEntry(entry, fieldName) {
1734
1583
  allow: normalizePermissionCommandArray(overrideInput.allow, `${overrideFieldName}.allow`),
1735
1584
  ask: normalizePermissionCommandArray(overrideInput.ask, `${overrideFieldName}.ask`),
1736
1585
  deny: normalizePermissionCommandArray(overrideInput.deny, `${overrideFieldName}.deny`),
1586
+ ...(overrideInput.sandbox === undefined ? {} : {
1587
+ sandbox: (() => {
1588
+ const sandbox = assertObject(overrideInput.sandbox, `${overrideFieldName}.sandbox`);
1589
+ assertAllowedKeys(sandbox, `${overrideFieldName}.sandbox`, ['excludedCommands']);
1590
+ if (!Array.isArray(sandbox.excludedCommands))
1591
+ throw new Error(`Invalid ${overrideFieldName}.sandbox.excludedCommands: expected an array.`);
1592
+ return { excludedCommands: sandbox.excludedCommands.map((value, index) => normalizeManagedCommandPermission(value, `${overrideFieldName}.sandbox.excludedCommands[${index}]`)) };
1593
+ })(),
1594
+ }),
1737
1595
  };
1738
1596
  }),
1739
1597
  };
@@ -2006,7 +1864,7 @@ export async function assertActualNativePluginCapability(pluginRoot, manifest, m
2006
1864
  }
2007
1865
  }
2008
1866
  if (!skills && !slashCommands && !mcpServers && !apps && !bundled) {
2009
- throw new Error('Invalid plugin manifest: portable-only plugins are not supported in V1. Declare at least one non-empty native capability.');
1867
+ throw new Error('Invalid plugin manifest: portable-only plugins are not supported. Declare at least one non-empty native capability.');
2010
1868
  }
2011
1869
  }
2012
1870
  export function validatePluginManifest(pluginRoot, rawManifest, options = {}) {
@@ -2020,7 +1878,7 @@ export function validatePluginManifest(pluginRoot, rawManifest, options = {}) {
2020
1878
  'version',
2021
1879
  'description',
2022
1880
  'dependencies',
2023
- 'compatibility',
1881
+ 'requires',
2024
1882
  'skills',
2025
1883
  'slashCommands',
2026
1884
  'mcpServers',
@@ -2033,18 +1891,19 @@ export function validatePluginManifest(pluginRoot, rawManifest, options = {}) {
2033
1891
  'buildConfig',
2034
1892
  'scripts',
2035
1893
  'config',
1894
+ 'managedCommands',
1895
+ 'nativeTools',
2036
1896
  ]);
2037
1897
  const manifest = {
2038
1898
  schemaVersion: PLUGIN_MANIFEST_SCHEMA_VERSION,
2039
1899
  name: ensureString(input.name, 'name'),
2040
1900
  version: assertStrictSemVer(input.version, 'plugin manifest.version'),
2041
1901
  description: ensureString(input.description, 'description'),
2042
- compatibility: normalizePluginCompatibility(input.compatibility, 'compatibility', {
2043
- allowMissing: (options.compatibilityMode ?? 'defaulted') === 'defaulted',
2044
- defaultMinVersion: options.defaultMinVersion,
2045
- }),
2046
1902
  marketplace: normalizeMarketplaceMeta(input.marketplace, 'marketplace'),
2047
1903
  };
1904
+ const requires = normalizePluginRequirements(input.requires, 'requires', { allowMissing: true });
1905
+ if (requires !== undefined)
1906
+ manifest.requires = requires;
2048
1907
  if (input.dependencies !== undefined) {
2049
1908
  const dependencies = ensureStringArray(input.dependencies, 'dependencies');
2050
1909
  if (dependencies.length > 64) {
@@ -2115,7 +1974,7 @@ export function validatePluginManifest(pluginRoot, rawManifest, options = {}) {
2115
1974
  throw new Error('Invalid buildConfig.cacheable: true requires scripts.node.build.');
2116
1975
  }
2117
1976
  if (input.config !== undefined) {
2118
- manifest.config = normalizePluginConfigSchema(input.config, 'config');
1977
+ manifest.config = normalizePluginConfigDefinition(input.config, 'config');
2119
1978
  }
2120
1979
  if (input.artifacts !== undefined) {
2121
1980
  manifest.artifacts = normalizeArtifacts(pluginRoot, input.artifacts, 'artifacts', manifest.config, options.reservedImports);
@@ -2124,7 +1983,16 @@ export function validatePluginManifest(pluginRoot, rawManifest, options = {}) {
2124
1983
  && !manifest.distributions?.agentSkills) {
2125
1984
  throw new Error('Invalid artifacts.targets: agent-skills targets require distributions.agentSkills declaration.');
2126
1985
  }
1986
+ if (input.managedCommands !== undefined) {
1987
+ manifest.managedCommands = normalizeManagedCommands(input.managedCommands);
1988
+ validateManagedCommandManifest(manifest);
1989
+ }
1990
+ if (input.nativeTools !== undefined) {
1991
+ manifest.nativeTools = normalizeNativeTools(input.nativeTools);
1992
+ validateNativeToolsManifest(manifest);
1993
+ }
2127
1994
  assertManifestPlatformSupport(manifest.name, manifest);
1995
+ assertCodexSharedNativeScopeConsistency(manifest);
2128
1996
  return manifest;
2129
1997
  }
2130
1998
  async function validateDeclaredNativeAssets(pluginRoot, manifest) {
@@ -2204,6 +2072,14 @@ async function validateSourceSkillInventory(pluginRoot, manifest) {
2204
2072
  .filter((skillKey) => Boolean(skillKey))
2205
2073
  .sort();
2206
2074
  const dynamicSkillNameSet = new Set(dynamicSkillNames);
2075
+ for (const [index, target] of (manifest.artifacts?.targets ?? []).entries()) {
2076
+ if (target.target === 'codex.skill'
2077
+ && target.delivery?.mode === 'codex-native'
2078
+ && target.skillKey
2079
+ && staticSkillNameSet.has(target.skillKey)) {
2080
+ throw new Error(`Invalid artifacts.targets[${index}].skillKey: native dynamic Skill "${target.skillKey}" conflicts with a static Skill of the same name.`);
2081
+ }
2082
+ }
2207
2083
  const portableSkillsRoot = manifest.distributions?.agentSkills?.skills
2208
2084
  ? path.join(pluginRoot, stripDotSlash(manifest.distributions.agentSkills.skills))
2209
2085
  : null;
@@ -2289,18 +2165,7 @@ async function validateSourceJsonNativeAsset(pluginRoot, manifest, field) {
2289
2165
  }
2290
2166
  export async function validateSourcePluginManifest(pluginRoot, rawManifest, options = {}) {
2291
2167
  await assertPluginInputTree(pluginRoot);
2292
- const distributionsInput = rawManifest !== null
2293
- && typeof rawManifest === 'object'
2294
- && !Array.isArray(rawManifest)
2295
- ? rawManifest.distributions
2296
- : undefined;
2297
- const requiresExplicitMinVersion = distributionsInput !== null
2298
- && typeof distributionsInput === 'object'
2299
- && !Array.isArray(distributionsInput)
2300
- && Object.hasOwn(distributionsInput, 'agentSkills');
2301
2168
  const manifest = validatePluginManifest(pluginRoot, rawManifest, {
2302
- compatibilityMode: requiresExplicitMinVersion ? 'required' : 'defaulted',
2303
- defaultMinVersion: DEFAULT_SOURCE_PLUGIN_AW_MIN_VERSION,
2304
2169
  reservedImports: options.reservedImports,
2305
2170
  });
2306
2171
  const parsedVersion = parseStrictSemVer(manifest.version, 'plugin manifest.version');
@@ -2312,6 +2177,9 @@ export async function validateSourcePluginManifest(pluginRoot, rawManifest, opti
2312
2177
  }
2313
2178
  await validateDeclaredNativeAssets(pluginRoot, manifest);
2314
2179
  await validateDeclaredAssetPathsExist(pluginRoot, manifest);
2180
+ await validateManagedCommandSources(pluginRoot, manifest);
2181
+ validateNativeToolsManifest(manifest, { source: true });
2182
+ await loadPluginNativeToolClosure(pluginRoot, manifest);
2315
2183
  await Promise.all([
2316
2184
  validateSourceJsonNativeAsset(pluginRoot, manifest, 'mcpServers'),
2317
2185
  validateSourceJsonNativeAsset(pluginRoot, manifest, 'apps'),
@@ -2337,12 +2205,14 @@ export async function readPluginManifest(pluginRoot, options = {}) {
2337
2205
  ? await validateSourcePluginManifest(pluginRoot, rawManifest, {
2338
2206
  reservedImports: options.reservedImports,
2339
2207
  })
2340
- : validatePluginManifest(pluginRoot, rawManifest, {
2341
- compatibilityMode: 'required',
2342
- reservedImports: options.reservedImports,
2343
- });
2344
- if (mode === 'bundle')
2208
+ : validatePluginManifest(pluginRoot, rawManifest, { reservedImports: options.reservedImports });
2209
+ if (mode === 'bundle') {
2210
+ await validateManagedCommandSources(pluginRoot, manifest);
2211
+ const nativeTools = await loadPluginNativeToolClosure(pluginRoot, manifest);
2212
+ if (nativeTools)
2213
+ await validateNativeToolClosureFiles(pluginRoot, nativeTools);
2345
2214
  await assertActualNativePluginCapability(pluginRoot, manifest, 'bundle');
2215
+ }
2346
2216
  return {
2347
2217
  rootDir: pluginRoot,
2348
2218
  manifestPath,
@@ -2438,9 +2308,6 @@ function assertCatalogChannelVersionContract(options) {
2438
2308
  }
2439
2309
  function normalizeCatalogManifestEntry(value, fieldName) {
2440
2310
  const input = assertObject(value, fieldName);
2441
- if (Object.hasOwn(input, 'dependencies')) {
2442
- throw new Error(`Invalid ${fieldName}: "dependencies" is unsupported.`);
2443
- }
2444
2311
  assertAllowedKeys(input, fieldName, [
2445
2312
  'name',
2446
2313
  'version',
@@ -2450,7 +2317,8 @@ function normalizeCatalogManifestEntry(value, fieldName) {
2450
2317
  'tags',
2451
2318
  'strict',
2452
2319
  'policy',
2453
- 'compatibility',
2320
+ 'dependencies',
2321
+ 'requires',
2454
2322
  'source',
2455
2323
  'artifactDigest',
2456
2324
  'manifestDigest',
@@ -2462,7 +2330,11 @@ function normalizeCatalogManifestEntry(value, fieldName) {
2462
2330
  category: ensureString(input.category, `${fieldName}.category`),
2463
2331
  strict: ensureBoolean(input.strict, `${fieldName}.strict`),
2464
2332
  policy: normalizeMarketplacePolicy(input.policy, `${fieldName}.policy`),
2465
- compatibility: normalizePluginCompatibility(input.compatibility, `${fieldName}.compatibility`, {
2333
+ dependencies: input.dependencies === undefined
2334
+ ? []
2335
+ : normalizeComparableStringArray(ensureStringArray(input.dependencies, `${fieldName}.dependencies`))
2336
+ .map((dependency, index) => ensurePluginName(dependency, `${fieldName}.dependencies[${index}]`)),
2337
+ requires: normalizePluginRequirements(input.requires, `${fieldName}.requires`, {
2466
2338
  allowMissing: false,
2467
2339
  }),
2468
2340
  source: normalizeCatalogRelativePath(input.source, `${fieldName}.source`),
@@ -2486,8 +2358,8 @@ export function normalizeCatalogManifestBase(input, fieldName) {
2486
2358
  fieldName: `${fieldName}.catalogVersion`,
2487
2359
  kind: 'catalog',
2488
2360
  });
2489
- const compatibility = normalizePluginCompatibility(input.compatibility, `${fieldName}.compatibility`, { allowMissing: false });
2490
- return { catalogVersion, channel, compatibility };
2361
+ const requires = normalizePluginRequirements(input.requires, `${fieldName}.requires`, { allowMissing: false });
2362
+ return { catalogVersion, channel, requires };
2491
2363
  }
2492
2364
  export function normalizeCatalogManifestEntries(rawEntries, base, fieldName) {
2493
2365
  if (!Array.isArray(rawEntries)) {
@@ -2508,16 +2380,10 @@ export function normalizeCatalogManifestEntries(rawEntries, base, fieldName) {
2508
2380
  kind: 'plugin',
2509
2381
  });
2510
2382
  }
2511
- assertPluginMinVersionWithinCatalogBounds({
2512
- pluginName: entry.name,
2513
- pluginMinVersion: entry.compatibility.aw.minVersion,
2514
- catalogMinVersion: base.compatibility.aw.minVersion,
2515
- fieldName: `${fieldName}.entries.${entry.name}.compatibility.aw.minVersion`,
2516
- });
2517
2383
  }
2518
2384
  return entries;
2519
2385
  }
2520
- export function createBundleSourceManifest(plugin) {
2386
+ export function createBundleSourceManifest(plugin, requiredCapabilities) {
2521
2387
  const scripts = plugin.manifest.scripts
2522
2388
  ? structuredClone(plugin.manifest.scripts)
2523
2389
  : undefined;
@@ -2543,9 +2409,12 @@ export function createBundleSourceManifest(plugin) {
2543
2409
  return {
2544
2410
  ...plugin.manifest,
2545
2411
  buildConfig,
2546
- compatibility: plugin.manifest.compatibility
2547
- ? structuredClone(plugin.manifest.compatibility)
2548
- : createPluginCompatibility(DEFAULT_SOURCE_PLUGIN_AW_MIN_VERSION),
2412
+ distributions: plugin.manifest.distributions
2413
+ ? structuredClone(plugin.manifest.distributions)
2414
+ : undefined,
2415
+ requires: requiredCapabilities === undefined
2416
+ ? structuredClone(plugin.manifest.requires)
2417
+ : { aw: { capabilities: normalizeAwCapabilities(requiredCapabilities) } },
2549
2418
  extensions: plugin.manifest.extensions
2550
2419
  ? {
2551
2420
  instructions: plugin.manifest.extensions.instructions
@@ -2580,11 +2449,6 @@ export function createPlatformManifestBase(plugin) {
2580
2449
  name: plugin.manifest.name,
2581
2450
  version: plugin.manifest.version,
2582
2451
  description: plugin.manifest.description,
2583
- compatibility: structuredClone(plugin.manifest.compatibility ?? {
2584
- aw: {
2585
- minVersion: '0.0.0',
2586
- },
2587
- }),
2588
2452
  interface: plugin.manifest.interface,
2589
2453
  };
2590
2454
  }