feat(coding): add marketplace package trust primitives

This commit is contained in:
2026-08-28 12:57:50 +08:00
parent 4d8b1fcec0
commit 898e2b7bdd
7 changed files with 1386 additions and 16 deletions

View File

@@ -1,6 +1,9 @@
import { readFileSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import {
isValidSemVer,
} from './release-descriptor';
import {
AGENT_PLUGINS_SCHEMA_URL,
BUNDLED_CODING_PLUGIN_ADAPTER_IDS,
@@ -13,6 +16,10 @@ import {
DATA_SERVICE_TOOL_NAMES,
type AgentPluginsRootManifest,
type CodingPluginDefinition,
type CodingPluginAcquisitionMode,
type CodingPluginExecutionMode,
type CodingPluginPackageProvenance,
type CodingPluginRuntimeKind,
type CodingPluginSkillDefinition,
type CodingPluginToolDefinition,
type PluginToolMutation,
@@ -33,7 +40,6 @@ const SKILL_ID_PATTERN = /^[a-z][a-z0-9._-]{0,63}$/u;
const CAPABILITY_ID_PATTERN = /^[a-z][a-z0-9.-]{0,63}$/u;
const OPERATION_ID_PATTERN = /^[a-z][a-z0-9._-]{0,63}$/u;
const TOOL_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9._:-]{0,63}$/u;
const SEMVER_PATTERN = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u;
const MAX_DISPLAY_TEXT = 256;
const UNSUPPORTED_COMPONENT_KEYS = new Set([
@@ -103,6 +109,64 @@ const TOOL_KEYS = new Set([
]);
const SURFACE_KEYS = new Set(['projectSettings', 'previewRuntime']);
const V2_CAPABILITY_KEYS = new Set([
'schemaVersion',
'pluginId',
'contractVersion',
'scope',
'runtime',
'skills',
'tools',
]);
const V2_RUNTIME_KEYS = new Set(['kind', 'protocol']);
const V2_SKILL_KEYS = new Set(['id', 'entry', 'grants']);
const V2_TOOL_KEYS = new Set([
'name',
'label',
'description',
'capabilityId',
'operation',
'roles',
'mutation',
'projectWriteLease',
'permissions',
'executionMode',
'inputSchema',
'outputSchema',
]);
const HOSTED_PERMISSION_PATTERN = /^hosted\.[a-z][a-z0-9._-]{0,127}$/u;
const V2_PROPERTY_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/u;
const V2_SCHEMA_TYPES = new Set(['array', 'boolean', 'integer', 'number', 'object', 'string']);
const V2_SCHEMA_KEYS = new Set([
'type',
'additionalProperties',
'required',
'properties',
'items',
'minLength',
'maxLength',
'minimum',
'maximum',
'maxItems',
'const',
]);
export interface CodingPluginManifestParseOptions {
packageRoot?: string;
rootManifestPath?: string;
capabilityManifestPath?: string;
/** Metadata supplied by the trusted bundle/package-store owner. */
runtimeKind?: CodingPluginRuntimeKind;
acquisitionMode?: CodingPluginAcquisitionMode;
releaseId?: string | null;
provenance?: CodingPluginPackageProvenance;
}
export type CodingPluginLoadOptions = Pick<
CodingPluginManifestParseOptions,
'runtimeKind' | 'acquisitionMode' | 'releaseId' | 'provenance'
>;
type UnknownRecord = Record<string, unknown>;
export class CodingPluginManifestError extends Error {
@@ -248,6 +312,41 @@ function normalizeCandidatePath(
return relative.split(path.sep).join('/');
}
function normalizeV2CandidatePath(
packageRoot: string,
baseDirectory: string,
candidate: unknown,
filePath: string,
field: string,
): string {
if (typeof candidate !== 'string') fail(filePath, field, 'expected a relative path');
const portable = candidate.replaceAll('\\', '/');
if (portable !== candidate || portable.split('/').some((segment) => segment === '')) {
fail(filePath, field, 'path must use canonical relative separators');
}
if (portable.split('/').some((segment) => segment === '.')) {
fail(filePath, field, 'path must not contain dot segments');
}
return normalizeCandidatePath(packageRoot, baseDirectory, candidate, filePath, field);
}
function normalizeCapabilityManifestPath(
packageRoot: string,
candidate: unknown,
filePath: string,
field: string,
): string {
if (typeof candidate !== 'string') fail(filePath, field, 'expected a relative path');
const portable = candidate.replaceAll('\\', '/');
const segments = portable.split('/');
if (portable !== candidate || segments.some((segment, index) => (
segment === '' || segment === '..' || (segment === '.' && index !== 0)
))) {
fail(filePath, field, 'path must use the canonical capability manifest location');
}
return normalizeCandidatePath(packageRoot, packageRoot, candidate, filePath, field);
}
function validateSchemaNode(value: unknown, filePath: string, field: string, root = false): void {
const schema = assertRecord(value, filePath, field);
const allowed = root
@@ -305,6 +404,112 @@ function validateSchemaNode(value: unknown, filePath: string, field: string, roo
}
}
function finiteNumber(value: unknown, filePath: string, field: string): number {
if (typeof value !== 'number' || !Number.isFinite(value)) {
fail(filePath, field, 'must be a finite number');
}
return value;
}
function boundedInteger(value: unknown, filePath: string, field: string): number {
if (!Number.isSafeInteger(value) || (value as number) < 0) {
fail(filePath, field, 'must be a non-negative safe integer');
}
return value as number;
}
/**
* Schema-v2 deliberately accepts a small data-only subset. This validator is
* separate from the legacy schema-1 validator because the bundled Data Service
* contract predates the v2 requirement that strings, arrays, and numbers carry
* explicit bounds.
*/
function validateV2SchemaNode(
value: unknown,
filePath: string,
field: string,
depth = 1,
): void {
if (depth > 4) fail(filePath, field, 'schema exceeds the maximum depth of 4');
const schema = assertRecord(value, filePath, field);
assertExactKeys(schema, V2_SCHEMA_KEYS, filePath, field);
if (typeof schema.type !== 'string' || !V2_SCHEMA_TYPES.has(schema.type)) {
fail(filePath, `${field}.type`, 'unsupported bounded schema type');
}
const type = schema.type;
if ('const' in schema && (type !== 'boolean' || typeof schema.const !== 'boolean')) {
fail(filePath, `${field}.const`, 'only boolean constants are supported');
}
if ('additionalProperties' in schema) {
if (type !== 'object' || schema.additionalProperties !== false) {
fail(filePath, `${field}.additionalProperties`, 'only false is supported on object schemas');
}
}
if ('required' in schema && !Array.isArray(schema.required)) {
fail(filePath, `${field}.required`, 'must be an array');
}
if (type === 'object') {
if (schema.additionalProperties !== false) {
fail(filePath, `${field}.additionalProperties`, 'object schemas must close additional properties');
}
if (!('required' in schema)) {
fail(filePath, `${field}.required`, 'object schemas must declare required properties');
}
const properties = assertRecord(schema.properties, filePath, `${field}.properties`);
if (Object.keys(properties).length > 64) {
fail(filePath, `${field}.properties`, 'object schemas may contain at most 64 properties');
}
for (const [name, child] of Object.entries(properties)) {
if (!V2_PROPERTY_NAME_PATTERN.test(name)) {
fail(filePath, `${field}.properties.${name}`, 'property name is invalid');
}
validateV2SchemaNode(child, filePath, `${field}.properties.${name}`, depth + 1);
}
const required = schema.required === undefined
? []
: uniqueStrings(schema.required, filePath, `${field}.required`, V2_PROPERTY_NAME_PATTERN);
for (const name of required) {
if (!(name in properties)) fail(filePath, `${field}.required`, `required property is not declared: ${name}`);
}
for (const key of ['items', 'minLength', 'maxLength', 'minimum', 'maximum', 'maxItems']) {
if (key in schema) fail(filePath, `${field}.${key}`, `${key} is not valid for an object schema`);
}
return;
}
if (type === 'array') {
if (!('items' in schema)) fail(filePath, `${field}.items`, 'array schemas must declare items');
boundedInteger(schema.maxItems, filePath, `${field}.maxItems`);
validateV2SchemaNode(schema.items, filePath, `${field}.items`, depth + 1);
for (const key of ['additionalProperties', 'required', 'properties', 'minLength', 'maxLength', 'minimum', 'maximum']) {
if (key in schema) fail(filePath, `${field}.${key}`, `${key} is not valid for an array schema`);
}
return;
}
if ('items' in schema || 'properties' in schema || 'required' in schema
|| 'additionalProperties' in schema || 'maxItems' in schema) {
fail(filePath, field, 'nested schema keywords do not match the scalar type');
}
if (type === 'string') {
const maxLength = boundedInteger(schema.maxLength, filePath, `${field}.maxLength`);
if ('minLength' in schema) {
const minLength = boundedInteger(schema.minLength, filePath, `${field}.minLength`);
if (minLength > maxLength) fail(filePath, field, 'minLength cannot exceed maxLength');
}
for (const key of ['minimum', 'maximum']) {
if (key in schema) fail(filePath, `${field}.${key}`, `${key} is not valid for a string schema`);
}
return;
}
if (type === 'integer' || type === 'number') {
const minimum = finiteNumber(schema.minimum, filePath, `${field}.minimum`);
const maximum = finiteNumber(schema.maximum, filePath, `${field}.maximum`);
if (minimum > maximum) fail(filePath, field, 'minimum cannot exceed maximum');
for (const key of ['minLength', 'maxLength']) {
if (key in schema) fail(filePath, `${field}.${key}`, `${key} is not valid for a numeric schema`);
}
}
}
function validateDestructiveConfirmation(
schema: UnknownRecord,
filePath: string,
@@ -331,7 +536,7 @@ export function parseAgentPluginsRootManifest(
}
const name = stableId(root.name, filePath, 'name', /^[a-z][a-z0-9.-]{0,127}$/u);
const version = text(root.version, filePath, 'version', 128);
if (!SEMVER_PATTERN.test(version)) fail(filePath, 'version', 'must be a valid package semver');
if (!isValidSemVer(version)) fail(filePath, 'version', 'must be a valid package semver');
const description = text(root.description, filePath, 'description');
const author = assertRecord(root.author, filePath, 'author');
assertExactKeys(author, AUTHOR_KEYS, filePath, 'author');
@@ -466,14 +671,241 @@ function validateDataServiceDefinition(
}
}
function trustedMetadata(
options: CodingPluginManifestParseOptions,
declaredRuntimeKind: CodingPluginRuntimeKind,
packageRoot: string,
schemaVersion: 1 | 2,
filePath: string,
): Pick<CodingPluginDefinition, 'runtimeKind' | 'acquisitionMode' | 'releaseId' | 'provenance'> {
const runtimeKind = options.runtimeKind ?? declaredRuntimeKind;
if (runtimeKind !== declaredRuntimeKind) {
fail(filePath, 'trusted metadata.runtimeKind', 'does not match the package runtime declaration');
}
const expectedAcquisition: CodingPluginAcquisitionMode = schemaVersion === 1
? 'system_included'
: 'user_acquired';
const acquisitionMode = options.acquisitionMode ?? expectedAcquisition;
if (acquisitionMode !== expectedAcquisition) {
fail(filePath, 'trusted metadata.acquisitionMode', `must equal ${expectedAcquisition} for schema ${schemaVersion}`);
}
const releaseId = options.releaseId ?? null;
if (releaseId !== null && (typeof releaseId !== 'string' || releaseId.length === 0 || releaseId.length > 128)) {
fail(filePath, 'trusted metadata.releaseId', 'must be null or a bounded non-empty string');
}
if (schemaVersion === 1 && releaseId !== null) {
fail(filePath, 'trusted metadata.releaseId', 'bundled schema-1 definitions cannot carry a Release ID');
}
const defaultProvenance: CodingPluginPackageProvenance = {
source: schemaVersion === 1 ? 'bundled' : 'marketplace',
packageRoot: schemaVersion === 1 ? path.basename(packageRoot) : path.resolve(packageRoot),
};
const provenance = options.provenance ?? defaultProvenance;
if (provenance.source !== defaultProvenance.source
|| typeof provenance.packageRoot !== 'string'
|| provenance.packageRoot.length === 0
|| provenance.packageRoot.length > 1024) {
fail(filePath, 'trusted metadata.provenance', 'is not valid for this package source');
}
return {
runtimeKind,
acquisitionMode,
releaseId,
provenance: {
source: provenance.source,
packageRoot: provenance.packageRoot,
},
};
}
function parseV2Skill(
value: unknown,
index: number,
packageRoot: string,
capabilityDirectory: string,
filePath: string,
): CodingPluginSkillDefinition {
const skill = assertRecord(value, filePath, `skills[${index}]`);
assertExactKeys(skill, V2_SKILL_KEYS, filePath, `skills[${index}]`);
const id = stableId(skill.id, filePath, `skills[${index}].id`, SKILL_ID_PATTERN);
const entryPath = normalizeV2CandidatePath(
packageRoot,
capabilityDirectory,
skill.entry,
filePath,
`skills[${index}].entry`,
);
const grants = uniqueStrings(
skill.grants,
filePath,
`skills[${index}].grants`,
CAPABILITY_ID_PATTERN,
);
return { id, entryPath, grants };
}
function hostedPermissionMatchesPlugin(permission: string, pluginId: string): boolean {
if (!HOSTED_PERMISSION_PATTERN.test(permission)) return false;
const suffix = pluginId.startsWith('makelore.') ? pluginId.slice('makelore.'.length) : pluginId;
return permission.startsWith(`hosted.${suffix}.`);
}
function parseV2Tool(
value: unknown,
index: number,
pluginId: string,
filePath: string,
): CodingPluginToolDefinition {
const tool = assertRecord(value, filePath, `tools[${index}]`);
assertExactKeys(tool, V2_TOOL_KEYS, filePath, `tools[${index}]`);
const name = stableId(tool.name, filePath, `tools[${index}].name`, TOOL_NAME_PATTERN);
const label = text(tool.label, filePath, `tools[${index}].label`);
const description = text(tool.description, filePath, `tools[${index}].description`);
const capabilityId = stableId(tool.capabilityId, filePath, `tools[${index}].capabilityId`, CAPABILITY_ID_PATTERN);
const operation = stableId(tool.operation, filePath, `tools[${index}].operation`, OPERATION_ID_PATTERN);
const roles = uniqueStrings(tool.roles, filePath, `tools[${index}].roles`, /^[a-z]+$/u);
if (roles.length !== 1 || roles[0] !== 'parent') {
fail(filePath, `tools[${index}].roles`, 'distributed tools must be exactly parent-only');
}
const mutation = tool.mutation;
if (mutation !== 'read' && mutation !== 'write' && mutation !== 'destructive') {
fail(filePath, `tools[${index}].mutation`, 'unknown tool mutation');
}
const projectWriteLease = bool(tool.projectWriteLease, filePath, `tools[${index}].projectWriteLease`);
if (projectWriteLease) fail(filePath, `tools[${index}].projectWriteLease`, 'distributed tools cannot request a project write lease');
const permissions = uniqueStrings(
tool.permissions,
filePath,
`tools[${index}].permissions`,
/^[a-z][a-z0-9._-]{0,127}$/u,
);
if (permissions.some((permission) => !hostedPermissionMatchesPlugin(permission, pluginId))) {
fail(filePath, `tools[${index}].permissions`, 'permission is outside the plugin hosted namespace');
}
if (tool.executionMode !== 'synchronous' && tool.executionMode !== 'accepted') {
fail(filePath, `tools[${index}].executionMode`, 'must be synchronous or accepted');
}
validateV2SchemaNode(tool.inputSchema, filePath, `tools[${index}].inputSchema`);
validateV2SchemaNode(tool.outputSchema, filePath, `tools[${index}].outputSchema`);
const inputSchema = tool.inputSchema as UnknownRecord;
const outputSchema = tool.outputSchema as UnknownRecord;
if (inputSchema.type !== 'object' || inputSchema.additionalProperties !== false) {
fail(filePath, `tools[${index}].inputSchema`, 'tool input schema must be a closed object');
}
if (outputSchema.type !== 'object' || outputSchema.additionalProperties !== false) {
fail(filePath, `tools[${index}].outputSchema`, 'tool output schema must be a closed object');
}
if (mutation === 'destructive') validateDestructiveConfirmation(inputSchema, filePath, `tools[${index}].inputSchema`);
return {
name,
label,
description,
capabilityId,
operation,
roles: ['parent'],
mutation: mutation as PluginToolMutation,
projectWriteLease: false,
permissions,
executionMode: tool.executionMode as CodingPluginExecutionMode,
inputSchema: freezeDeep(structuredClone(inputSchema)),
outputSchema: freezeDeep(structuredClone(outputSchema)),
};
}
function parseV2CodingPluginManifest(
root: AgentPluginsRootManifest,
capability: UnknownRecord,
packageRoot: string,
capabilityManifestPath: string,
options: CodingPluginManifestParseOptions,
): CodingPluginDefinition {
assertExactKeys(capability, V2_CAPABILITY_KEYS, capabilityManifestPath, 'root');
rejectUnsupportedComponents(capability, capabilityManifestPath, 'root');
if (capability.schemaVersion !== 2) fail(capabilityManifestPath, 'schemaVersion', 'must equal 2');
const pluginId = stableId(capability.pluginId, capabilityManifestPath, 'pluginId', /^[a-z][a-z0-9.-]{0,127}$/u);
if (pluginId !== root.name) fail(capabilityManifestPath, 'pluginId', 'must match plugin.json name');
const contractVersion = positiveInteger(capability.contractVersion, capabilityManifestPath, 'contractVersion');
if (capability.scope !== 'project') fail(capabilityManifestPath, 'scope', 'distributed plugins must be project-scoped');
const runtime = assertRecord(capability.runtime, capabilityManifestPath, 'runtime');
assertExactKeys(runtime, V2_RUNTIME_KEYS, capabilityManifestPath, 'runtime');
const runtimeKind = runtime.kind;
if (runtimeKind !== 'skill_only' && runtimeKind !== 'platform_hosted') {
fail(capabilityManifestPath, 'runtime.kind', 'must be skill_only or platform_hosted');
}
if (runtimeKind === 'skill_only') {
if ('protocol' in runtime) fail(capabilityManifestPath, 'runtime.protocol', 'skill_only must not declare a hosted protocol');
} else if (runtime.protocol !== 'makelore-hosted.v1') {
fail(capabilityManifestPath, 'runtime.protocol', 'must equal makelore-hosted.v1');
}
const skillsValue = capability.skills;
if (!Array.isArray(skillsValue) || skillsValue.length === 0) {
fail(capabilityManifestPath, 'skills', 'must contain at least one Skill');
}
const capabilityDirectory = path.dirname(capabilityManifestPath);
const skills = skillsValue.map((value, index) => parseV2Skill(
value,
index,
packageRoot,
capabilityDirectory,
capabilityManifestPath,
));
if (new Set(skills.map((skill) => skill.id)).size !== skills.length) {
fail(capabilityManifestPath, 'skills', 'duplicate Skill identifier');
}
if (new Set(skills.map((skill) => skill.entryPath)).size !== skills.length) {
fail(capabilityManifestPath, 'skills', 'duplicate Skill entry path');
}
if (!Array.isArray(capability.tools)) fail(capabilityManifestPath, 'tools', 'must be an array');
if (runtimeKind === 'skill_only' && capability.tools.length !== 0) {
fail(capabilityManifestPath, 'tools', 'skill_only packages must declare an empty tools array');
}
if (runtimeKind === 'platform_hosted' && capability.tools.length === 0) {
fail(capabilityManifestPath, 'tools', 'platform_hosted packages must declare at least one tool');
}
const tools = capability.tools.map((value, index) => parseV2Tool(
value,
index,
pluginId,
capabilityManifestPath,
));
if (new Set(tools.map((tool) => tool.name)).size !== tools.length) {
fail(capabilityManifestPath, 'tools.name', 'duplicate tool identifier');
}
const mappingKeys = tools.map((tool) => `${tool.capabilityId}\u0000${tool.operation}`);
if (new Set(mappingKeys).size !== mappingKeys.length) {
fail(capabilityManifestPath, 'tools', 'duplicate capability operation mapping');
}
const grants = new Set(skills.flatMap((skill) => skill.grants));
for (const tool of tools) {
if (!grants.has(tool.capabilityId)) {
fail(capabilityManifestPath, `tools.${tool.name}.capabilityId`, 'tool capability has no Skill grant');
}
}
if (runtimeKind === 'skill_only' && skills.some((skill) => skill.grants.length > 0)) {
fail(capabilityManifestPath, 'skills', 'skill_only Skills must declare empty grants');
}
const metadata = trustedMetadata(options, runtimeKind, packageRoot, 2, capabilityManifestPath);
return freezeDeep({
id: pluginId,
version: root.version,
contractVersion,
displayName: root.name,
description: root.description,
...metadata,
scope: 'project' as const,
adapterId: '',
requiresBackend: runtimeKind === 'platform_hosted',
skills,
tools,
operations: tools.map(({ capabilityId, operation, name }) => ({ capabilityId, operation, toolName: name })),
surfaces: {},
});
}
export function parseCodingPluginManifest(
rootValue: unknown,
capabilityValue: unknown,
options: {
packageRoot?: string;
rootManifestPath?: string;
capabilityManifestPath?: string;
} = {},
options: CodingPluginManifestParseOptions = {},
): CodingPluginDefinition {
const packageRoot = path.resolve(options.packageRoot ?? path.dirname(options.capabilityManifestPath ?? '.'));
const rootManifestPath = path.resolve(
@@ -488,10 +920,32 @@ export function parseCodingPluginManifest(
|| capabilityRelativePath.startsWith(`..${path.sep}`) || path.isAbsolute(capabilityRelativePath)) {
fail(capabilityManifestPath, 'root', 'capability manifest must be inside the package root');
}
const declaredCapabilityPath = normalizeCapabilityManifestPath(
packageRoot,
root.extensions['com.makelore'].capabilityManifest,
rootManifestPath,
'extensions.com.makelore.capabilityManifest',
);
if (declaredCapabilityPath !== CAPABILITY_MANIFEST_RELATIVE_PATH) {
fail(
rootManifestPath,
'extensions.com.makelore.capabilityManifest',
`must point to ${CAPABILITY_MANIFEST_RELATIVE_PATH}`,
);
}
const capability = assertRecord(capabilityValue, capabilityManifestPath, 'root');
if (capability.schemaVersion === 2) {
return parseV2CodingPluginManifest(
root,
capability,
packageRoot,
capabilityManifestPath,
options,
);
}
if (capability.schemaVersion !== 1) fail(capabilityManifestPath, 'schemaVersion', 'must equal 1 or 2');
assertExactKeys(capability, CAPABILITY_KEYS, capabilityManifestPath, 'root');
rejectUnsupportedComponents(capability, capabilityManifestPath, 'root');
if (capability.schemaVersion !== 1) fail(capabilityManifestPath, 'schemaVersion', 'must equal 1');
const pluginId = stableId(capability.pluginId, capabilityManifestPath, 'pluginId', /^[a-z][a-z0-9.-]{0,127}$/u);
if (pluginId !== root.name) fail(capabilityManifestPath, 'pluginId', 'must match plugin.json name');
const contractVersion = positiveInteger(capability.contractVersion, capabilityManifestPath, 'contractVersion');
@@ -560,6 +1014,7 @@ export function parseCodingPluginManifest(
contractVersion,
displayName,
description,
...trustedMetadata(options, 'bundled_typed', packageRoot, 1, capabilityManifestPath),
scope: 'project' as const,
adapterId,
requiresBackend,
@@ -582,13 +1037,15 @@ function readJson(source: string, filePath: string): unknown {
}
}
export async function loadCodingPluginDefinition(packageRoot: string): Promise<CodingPluginDefinition> {
export async function loadCodingPluginDefinition(
packageRoot: string,
options: CodingPluginLoadOptions = {},
): Promise<CodingPluginDefinition> {
const root = path.resolve(packageRoot);
const pluginManifestPath = path.join(root, PACKAGE_MANIFEST_FILE);
const rootValue = readJson(await readFile(pluginManifestPath, 'utf8'), pluginManifestPath);
const rootManifest = parseAgentPluginsRootManifest(rootValue, pluginManifestPath);
const capabilityManifest = normalizeCandidatePath(
root,
const capabilityManifest = normalizeCapabilityManifestPath(
root,
rootManifest.extensions['com.makelore'].capabilityManifest,
pluginManifestPath,
@@ -603,6 +1060,7 @@ export async function loadCodingPluginDefinition(packageRoot: string): Promise<C
packageRoot: root,
rootManifestPath: pluginManifestPath,
capabilityManifestPath,
...options,
});
for (const skill of definition.skills) {
try {
@@ -618,13 +1076,15 @@ export async function loadCodingPluginDefinition(packageRoot: string): Promise<C
return definition;
}
export function loadCodingPluginDefinitionSync(packageRoot: string): CodingPluginDefinition {
export function loadCodingPluginDefinitionSync(
packageRoot: string,
options: CodingPluginLoadOptions = {},
): CodingPluginDefinition {
const root = path.resolve(packageRoot);
const pluginManifestPath = path.join(root, PACKAGE_MANIFEST_FILE);
const rootValue = readJson(readFileSync(pluginManifestPath, 'utf8'), pluginManifestPath);
const rootManifest = parseAgentPluginsRootManifest(rootValue, pluginManifestPath);
const capabilityManifest = normalizeCandidatePath(
root,
const capabilityManifest = normalizeCapabilityManifestPath(
root,
rootManifest.extensions['com.makelore'].capabilityManifest,
pluginManifestPath,
@@ -639,6 +1099,7 @@ export function loadCodingPluginDefinitionSync(packageRoot: string): CodingPlugi
packageRoot: root,
rootManifestPath: pluginManifestPath,
capabilityManifestPath,
...options,
});
for (const skill of definition.skills) {
try {
@@ -690,8 +1151,13 @@ export async function loadBundledCodingPluginDefinitions(
): Promise<readonly CodingPluginDefinition[]> {
const roots = resolveBundledCodingPluginRootPaths(resourcesRoot);
const definitions = await Promise.all(roots.map(async (root, index) => {
const definition = await loadCodingPluginDefinition(root);
const expectedRoot = BUNDLED_CODING_PLUGIN_ROOTS[index];
const definition = await loadCodingPluginDefinition(root, {
runtimeKind: 'bundled_typed',
acquisitionMode: 'system_included',
releaseId: null,
provenance: { source: 'bundled', packageRoot: expectedRoot },
});
if (definition.id !== `makelore.${expectedRoot}`) {
throw new CodingPluginManifestError(
path.join(root, PACKAGE_MANIFEST_FILE),
@@ -710,8 +1176,13 @@ export function loadBundledCodingPluginDefinitionsSync(
): readonly CodingPluginDefinition[] {
const roots = resolveBundledCodingPluginRootPaths(resourcesRoot);
const definitions = roots.map((root, index) => {
const definition = loadCodingPluginDefinitionSync(root);
const expectedRoot = BUNDLED_CODING_PLUGIN_ROOTS[index];
const definition = loadCodingPluginDefinitionSync(root, {
runtimeKind: 'bundled_typed',
acquisitionMode: 'system_included',
releaseId: null,
provenance: { source: 'bundled', packageRoot: expectedRoot },
});
if (definition.id !== `makelore.${expectedRoot}`) {
throw new CodingPluginManifestError(
path.join(root, PACKAGE_MANIFEST_FILE),